外链论坛

 找回密码
 立即注册
搜索
查看: 21|回复: 0

从小白到大师,这儿有一份Pandas入门指南

[复制链接]

2948

主题

3万

回帖

9997万

积分

论坛元老

Rank: 8Rank: 8

积分
99979419
发表于 2024-10-3 19:07:12 | 显示全部楼层 |阅读模式

选自Medium

作者:Rudolf Höhn设备之心编译

参与:李诗萌、张倩

在本文中,作者从 Pandas 的简介起始循序渐进讲解了 Pandas 的发展状况、内存优化等问题。这是一篇最佳实践教程,既适合用过 Pandas 的读者,适合没用过但想要上手的小白。

经过本文,你将有望发掘一到多种用 pandas 编码的新办法

本文包含以下内容:

Pandas 发展状况;内存优化;索引;办法链;随机提示。

在阅读本文时,我意见你阅读每一个你不认识的函数的文档字符串(docstrings)。简单的 Google 搜索和几秒钟 Pandas 文档的阅读,都会使你的阅读体验更加愉快。

Pandas 的定义和状况

什么是 Pandas?

Pandas 是一个「开源的、有 BSD 开源协议的库,它为 Python 编程语言供给了高性能、易于运用的数据架构以及数据分析工具」。总之,它供给了被叫作为 DataFrame 和 Series(对哪些运用 Panel 的人来讲,它们已然被弃用了)的数据抽象,经过管理索引来快速访问数据、执行分析和转换运算,乃至能够绘图(用 matplotlib 后端)。

Pandas 的当前最新版本是 v0.25.0 (

https://github.com/pandas-dev/pandas/releases/tag/v0.25.0)

Pandas 正在逐步升级到 1.0 版,而为了达到这一目的,它改变了非常多人们习以为常的细节。Pandas 的核心研发者之一 Marc Garcia 发布了一段非常有趣的演讲——「走向 Pandas 1.0」。

演讲链接:

https://www.youtube.com/watch?v=hK6o_TDXXN8

用一句话来总结,Pandas v1.0 重点改善了稳定性(如时间序列)并删除了未运用的代码库(如 SparseDataFrame)。

数据

咱们起始吧!选取「1985 到 2016 年间每一个国家的自s率」做为玩具数据集。这个数据集足够简单,但足以让你上手 Pandas。

数据集链接:

https://www.kaggle.com/russellyates88/suicide-rates-overview-1985-to-2016

在深入科研代码之前,倘若你想重现结果,要先执行下面的代码准备数据,保证列名和类型是正确的。

import pandas as pdimport numpy as npimport os# to download https://www.kaggle.com/russellyates88/suicide-rates-overview-1985-to-2016 data_path = path/to/folder/df = (pd.read_csv(filepath_or_buffer=os.path.join(data_path, master.csv)) .rename(columns={suicides/100k pop : suicides_per_100k, gdp_for_year ($) : gdp_year, gdp_per_capita ($) : gdp_capita, country-year : country_year}) .assign(gdp_year=lambda _df: _df[gdp_year].str.replace(,,).astype(np.int64)) )

提示:倘若你读取了一个大文件,在 read_csv(

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html)中参数设定为 chunksize=N,这会返回一个能够输出 DataFrame 对象的迭代器。

这儿有些关于这个数据集的描述:

>>> df.columnsIndex([country, year, sex, age, suicides_no, population, suicides_per_100k, country_year, HDI for year, gdp_year, gdp_capita, generation], dtype=object)

这儿有 101 个国家、年份从 1985 到 2016、两种性别、六个年代以及六个年龄组。有有些得到这些信息的办法

能够用 unique() 和 nunique() 获取列内独一的值(或独一值的数量);

>>> df[generation].unique()array([Generation X, Silent, G.I. Generation, Boomers, Millenials, Generation Z], dtype=object)>>> df[country].nunique()101

能够用 describe() 输出每一列区别的统计数据(例如最小值、最大值、平均值、总数等),倘若指定 include=all,会针对每一列目的输出独一元素的数量和显现最多元素的数量;

能够用 head() 和 tail() 来可视化数据框的一小部分。

经过这些办法,你能够快速认识正在分析的表格文件。

内存优化

在处理数据之前,认识数据并为数据框的每一列选取合适的类型是很重要的一步。

在内部,Pandas 将数据框存储为区别类型的 numpy 数组(例如一个 float64 矩阵,一个 int32 矩阵)。

有两种能够大幅降低内存消耗的办法

import pandas as pd def mem_usage(df: pd.DataFrame) -> str: """This method styles the memory usage of a DataFrame to be readable as MB. Parameters ---------- df: pd.DataFrame Data frame to measure. Returns ------- str Complete memory usage as a string formatted for MB. """ return f{df.memory_usage(deep=True).sum() / 1024 ** 2 : 3.2f} MB def convert_df(df: pd.DataFrame, deep_copy: bool = True) -> pd.DataFrame: """Automatically converts columns that are worth stored as ``categorical`` dtype. Parameters ---------- df: pd.DataFrame Data frame to convert. deep_copy: bool Whether or not to perform a deep copy of the original data frame. Returns ------- pd.DataFrame Optimized copy of the input data frame. """ return df.copy(deep=deep_copy).astype({ col: category for col in df.columns if df[col].nunique() / df[col].shape[0] < 0.5})

Pandas 提出了一种叫做 memory_usage() 的办法,这种办法能够分析数据框的内存消耗。在代码中,指定 deep=True 来保证思虑到了实质的系统运用状况

memory_usage():https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.memory_usage.html

认识列的类型(

https://pandas.pydata.org/pandas-docs/stable/getting_started/basics.html#basics-dtypes)很重要。它能够经过两种简单的办法节省高达 90% 的内存运用认识数据框运用的类型;认识数据框能够运用哪种类型来减少内存的运用(例如,price 这一列值在 0 到 59 之间,只带有一位小数,运用 float64 类型可能会产生不必要的内存开销)

除了降低数值类型的体积(用 int32 而不是 int64)外,Pandas 还提出了归类类型:

https://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html

倘若你是用 R 语言的研发人员,你可能觉得它和 factor 类型是同样的。

这种归类类型准许用索引替换重复值,还能够实质值存在其他位置。教科书中的例子是国家。和多次存储相同的字符串「瑞士」或「波兰」比起来,为何不简单地用 0 和 1 替换它们,并存储在字典中呢?

categorical_dict = {0: Switzerland, 1: Poland}

Pandas 做了几乎相同的工作,同期添加了所有的办法能够实质运用这种类型,并且仍然能够表示国家的名叫作

回到 convert_df() 办法倘若这一列中的独一少于 50%,它会自动将列类型转换成 category。这个数是任意的,然则由于数据框中类型的转换寓意着在 numpy 数组间移动数据,因此呢咱们得到的必须比失去的多。

接下来瞧瞧数据中会出现什么。

>>> mem_usage(df)10.28 MB>>> mem_usage(df.set_index([country, year, sex, age]))5.00 MB>>> mem_usage(convert_df(df))1.40 MB>>> mem_usage(convert_df(df.set_index([country, year, sex, age])))1.40 MB

经过运用「智能」转换器,数据框运用的内存几乎减少了 10 倍(准确地说是 7.34 倍)。

索引

Pandas 是强大的,但需要付出有些代价。当你加载 DataFrame 时,它会创建索引并将数据存储在 numpy 数组中。这是什么意思?一旦加载了数据框,只要正确管理索引,就能够快速地拜访数据。

拜访数据的办法重点有两种,分别是经过索引和查找拜访按照详细状况,你只能选取其中一种。但在大都数状况中,索引(和多索引)都是最好的选取咱们来看下面的例子:

>>> %%time>>> df.query(country == "Albania" and year == 1987 and sex == "male" and age == "25-34 years")CPU times: user 7.27 ms, sys: 751 µs, total: 8.02 ms# ==================>>> %%time>>> mi_df.loc[Albania, 1987, male, 25-34 years]CPU times: user 459 µs, sys: 1 µs, total: 460 µs

什么?加速 20 倍?

你要问自己了,创建这个多索引要多长期

%%timemi_df = df.set_index([country, year, sex, age])CPU times: user 10.8 ms, sys: 2.2 ms, total: 13 ms

经过查找拜访数据的时间是 1.5 倍。倘若你只想检索一次数据(这种状况很少出现),查找是正确的办法。否则,你必定保持用索引,CPU 会为此感激你的。

.set_index(drop=False) 准许不删除用作新索引的列。

.loc[]/.iloc[] 办法能够很好地读取数据框,但没法修改数据框。倘若需要手动构建(例如运用循环),那就要思虑其他的数据结构了(例如字典、列表等),在准备好所有数据后,创建 DataFrame。否则,针对 DataFrame 中的每一个新行,Pandas 都会更新索引,这可不是简单的哈希映射。

>>> (pd.DataFrame({a:range(2), b: range(2)}, index=[a, a]) .loc[a]) a ba 0 0a 1 1

因此呢,未排序的索引能够降低性能。为了检测索引是不是已经排序并对它排序,重点有两种办法

%%time>>> mi_df.sort_index()CPU times: user 34.8 ms, sys: 1.63 ms, total: 36.5 ms>>> mi_df.index.is_monotonicTrue

更加多详情请参阅:

Pandas 高级索引用户指南:https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html;Pandas 库中的索引代码:https://github.com/pandas-dev/pandas/blob/master/pandas/core/indexing.py。

办法

运用 DataFrame 的办法链是链接多个返回 DataFrame 办法行径因此呢它们都是来自 DataFrame 类的办法。在此刻的 Pandas 版本中,运用办法链是为了不存储中间变量并避免显现如下状况

import numpy as npimport pandas as pddf = pd.DataFrame({a_column: [1, -999, -999], powerless_column: [2, 3, 4], int_column: [1, 1, -1]}) df[a_column] = df[a_column].replace(-999, np.nan) df[power_column] = df[powerless_column] ** 2 df[real_column] = df[int_column].astype(np.float64) df = df.apply(lambda _df: _df.replace(4, np.nan)) df = df.dropna(how=all)

用下面的链替换:

df = (pd.DataFrame({a_column: [1, -999, -999], powerless_column: [2, 3, 4], int_column: [1, 1, -1]}) .assign(a_column=lambda _df: _df[a_column].replace(-999, np.nan)) .assign(power_column=lambda _df: _df[powerless_column] ** 2) .assign(real_column=lambda _df: _df[int_column].astype(np.float64)) .apply(lambda _df: _df.replace(4, np.nan)) .dropna(how=all) )

说实话,第二段代码更美丽更简洁。

办法链的工具箱是由于区别办法例如 apply、assign、loc、query、pipe、groupby 以及 agg)构成的,这些办法的输出都是 DataFrame 对象或 Series 对象(或 DataFrameGroupBy)。

认识它们最好的办法便是实质运用。举个简单的例子:

(df .groupby(age) .agg({generation:unique}) .rename(columns={generation:unique_generation})# Recommended from v0.25# .agg(unique_generation=(generation, unique)))

得到每一个年龄范围中所有独一年代标签的简单链

在得到的数据框中,「年龄」列是索引。

除了认识到「X 代」覆盖了三个年龄组外,分解这条链。第1步是对年龄组分组。这一办法返回了一个 DataFrameGroupBy 对象,在这个对象中,经过选取组的独一年代标签聚合了每一组。

在这种状况下,聚合办法是「unique」办法,但它能够接受任何(匿名)函数。

在 0.25 版本中,Pandas 引入了运用 agg 的新办法

https://dev.pandas.io/whatsnew/v0.25.0.html#

groupby-aggregation-with-relabeling。 (df .groupby([country, year]) .agg({suicides_per_100k: sum}) .rename(columns={suicides_per_100k:suicides_sum})# Recommended from v0.25# .agg(suicides_sum=(suicides_per_100k, sum)) .sort_values(suicides_sum, ascending=False) .head(10))

用排序值(sort_values)和 head 得到自s率排前十的国家和年份

(df .groupby([country, year]) .agg({suicides_per_100k: sum}) .rename(columns={suicides_per_100k:suicides_sum})# Recommended from v0.25# .agg(suicides_sum=(suicides_per_100k, sum)) .nlargest(10, columns=suicides_sum))

用排序值 nlargest 得到自s率排前十的国家和年份

在这些例子中,输出都是同样的:有两个指标(国家和年份)的 MultiIndex 的 DataFrame,还有包括排序后的 10 个最大值的新列 suicides_sum。

「国家」和「年份」列是索引。

nlargest(10) 比 sort_values(ascending=False).head(10) 更有效。

另一个有趣的办法是 unstack:

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.unstack.html,这种办法准许转动索引水平。 (mi_df .loc[(Switzerland, 2000)] .unstack(sex) [[suicides_no, population]])

「age」是索引,列「suicides_no」和「population」都有第二个水平列「sex」。

下一个办法 pipe 是最通用的办法之一。这种办法准许管道运算(就像在 shell 脚本中)执行比链更加多的运算。

管道的一个简单但强大的用法是记录区别的信息。

def log_head(df, head_count=10): print(df.head(head_count)) return df def log_columns(df): print(df.columns) return df def log_shape(df): print(fshape = {df.shape}) return df

和 pipe 一块运用区别记录函数。

举个例子,咱们想验证和 year 列相比,country_year 是不是正确:

(df .assign(valid_cy=lambda _serie: _serie.apply( lambda _row: re.split(r(?=\d{4}), _row[country_year])[1] == str(_row[year]), axis=1)) .query(valid_cy == False) .pipe(log_shape))

用来验证「country_year」列中年份的管道。

管道的输出是 DataFrame,但它能够在标准输出(console/REPL)中打印。

shape = (0, 13)

能够在一条链中用区别的 pipe。

(df .pipe(log_shape) .query(sex == "female") .groupby([year, country]) .agg({suicides_per_100k:sum}) .pipe(log_shape) .rename(columns={suicides_per_100k:sum_suicides_per_100k_female})# Recommended from v0.25# .agg(sum_suicides_per_100k_female=(suicides_per_100k, sum)) .nlargest(n=10, columns=[sum_suicides_per_100k_female]))

女性自s数量最高的国家和年份。

生成的 DataFrame 如下所示:

索引是「年份」和「国家」。

标准输出的打印如下所示:

shape = (27820, 12)shape = (2321, 1)

除了记录到掌控台外,pipe 还能够直接在数据框的列上应用函数。

from sklearn.preprocessing import MinMaxScaler def norm_df(df, columns): return df.assign(**{col: MinMaxScaler().fit_transform(df[[col]].values.astype(float)) for col in columns}) for sex in [male, female]: print(sex) print( df .query(fsex == "{sex}") .groupby([country]) .agg({suicides_per_100k: sum, gdp_year: mean}) .rename(columns={suicides_per_100k:suicides_per_100k_sum, gdp_year: gdp_year_mean}) # Recommended in v0.25 # .agg(suicides_per_100k=(suicides_per_100k_sum, sum), # gdp_year=(gdp_year_mean, mean)) .pipe(norm_df, columns=[suicides_per_100k_sum, gdp_year_mean]) .corr(method=spearman) ) print(\n)

自s数量是不是和 GDP 的下降关联是不是和性别关联

上面的代码在掌控台中的打印如下所示:

male suicides_per_100k_sum gdp_year_meansuicides_per_100k_sum 1.000000 0.421218gdp_year_mean 0.421218 1.000000 female suicides_per_100k_sum gdp_year_meansuicides_per_100k_sum 1.000000 0.452343gdp_year_mean 0.452343 1.000000

深入科研代码。norm_df() 将一个 DataFrame 和用 MinMaxScaling 扩展列的列表当做输入。运用字典理解,创建一个字典 {column_name: method, …},而后将其解压为 assign() 函数的参数 (colunmn_name=method, …)。

在这种特殊状况下,min-max 缩放不会改变对应的输出:

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html,它仅用于参数。

在(遥远的?)将来,缓式评定(lazy evaluation)可能出此刻办法链中,因此在链上做有些投资可能是一个好想法。

最后(随机)的技巧

下面的提示特别有用,但不适用于前面的任何部分:

itertuples() 能够有效地遍历数据框的行;

>>> %%time>>> for row in df.iterrows(): continueCPU times: user 1.97 s, sys: 17.3 ms, total: 1.99 s>>> for tup in df.itertuples(): continueCPU times: user 55.9 ms, sys: 2.85 ms, total: 58.8 ms

重视:tup 是一个 namedtuple

join() 用了 merge();在 Jupyter 笔记本中,在代码块的开头写上 %%time,能够有效地测绘时间;UInt8 类:

https://pandas.pydata.org/pandas-docs/stable/user_guide/gotchas.html#support-for-integer-na支持带有整数的 NaN 值;

记住,任何密集的 I/O(例如展开大型 CSV 存储)用低级办法都会执行得更好(尽可能多地用 Python 的核心函数)。

还有有些本文触及到的有用的办法和数据结构,这些办法和数据结构都很值得花时间去理解:

数据透视表:

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.pivot.html?source=

post_page---------------------------

时间序列/日期功能:

https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html?source=

post_page---------------------------;

绘图:

https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html?source=

post_page---------------------------。

总结

期盼能够由于这篇简短的文案,更好地理解 Pandas 背面的工作原理,以及 Pandas 库的发展状况。本文还展示了区别的用于优化数据框内存以及快速分析数据的工具。期盼此刻的你来讲,索引和查询的概念能更加清晰。最后,你还能够试着用办法链写更长的链。

这儿还有有些笔记:

https://github.com/unit8co/medium-pandas-wan?source=

post_page---------------------------

除了文中的所有代码外,还包含简单数据索引数据框(df)和多索引数据框(mi_df)性能的按时指标。

熟能生巧,因此继续修炼技能,并帮忙咱们创立一个更好的世界吧。

PS:有时候纯用 Numpy 会更快。

原文链接:

https://medium.com/unit8-machine-learning-publication/from-pandas-wan-to-pandas-master-4860cf0ce442




上一篇:运用Python的LDA主题建模(附链接)
下一篇:“稻花香里说丰年”网络主题活动圆满落幕,全网总传播量1.97亿+!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

站点统计|Archiver|手机版|小黑屋|外链论坛 ( 非经营性网站 )|网站地图

GMT+8, 2024-11-24 00:57 , Processed in 0.147990 second(s), 21 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.