pandas快速入门
Pandas Cheat Sheet
10 minutes to pandas
Wes McKinney: pandas in 10 minutes
Essential Basic Function
1 | import numpy as np |
数据类型
Pandas基础的数据类型为索引(标签)Index
和带标签的数组Series
/DataFrame
。
Index
:虽然名字是索引,但不同于Numpy数组的数字索引,其本质是标签(可类比字典的key),二维表格的行、列索引分别为df.index
,df.custom-columns
。DataFrame
:二维数据表,相比Numpy二维数组多了行&列标签,不同列的数据类型可不同。大致可理解为由Index:Series
构成的字典:key为列标签,value为Series
列向量,可类似字典对列进行增删和更新。df[val]
访问时val
默认理解为列标签(key
);用作可迭代对象时是对列标签(df.custom-columns
)迭代;df.items()
方法类似字典,为键值对(Index:Series
)生成器。Series
:一维(列)标签向量,相比Numpy一维数组多了行标签,除数字索引及切片外还可用标签访问(类似字典),要求数据类型一致。注意,相比二维表格DataFrame
,Series
并没有列标签columns
,只有行标签s.index
及整体的名字s.name
。注1:上面示例代码中的Series属于时间序列,其索引为1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# pd.Index(data=None, dtype=None)
# pd.Series(data=None, index=None, name=None, dtype=None)
pd.Series([0, 1, 3], index=['i', 'j', 'k'], name='vec')
s = pd.Series(pd.date_range("1/3/2000", periods=8))
ts = pd.Series(np.random.randn(8), index=s) # 时间序列
# pd.DataFrame(data=None, index=None, columns=None)
df = pd.DataFrame({'col1': np.random.randn(8)})
for col in df:
print(col)
print(df[col])
for col, value in df.items():
print(col)
print(value)
# df.iterrows(), df.itertuples() 行迭代器
# vectorization > list comprehensions > iterpd.date_range()
所生成的特殊索引(DatetimeIndex
)。to_datetime()
,to_timestamp()
,to_period()
,at_time()
注2:对DataFrame执行迭代操作不是好想法,推荐使用矢量化操作或借助map等辅助函数来应用自定义函数。
数据访问可使用索引(标签)、数字索引切片或布尔数组,默认为列筛选,但数字索引切片及布尔数组会作为行筛选,更复杂数据访问可使用loc()
,iloc()
方法,具体参考数据筛选。获取所有数据的values
属性推荐替换为Series.array
和DataFrame.to_numpy()
,前者使用扩展的PandasArray
,不拷贝数据,后者则返回Numpy数组,必要时会拷贝数据。
注意:df.to_numpy() DataFrame转换为Numpy数组时需留意,如果数据都是浮点数,则不存在问题(无需拷贝数据);如果存在多种数据类型,则需转换为同一数据类型,最终可能只有Python对象能实现这一要求(且需拷贝数据)。
数组创建
- 数据I/O:
pd.read_<file_type>()
读、df.to_<file_type>()
写
常见的文件格式有csv
,excel
,json
,html
等文本格式及hdf
,sql
等二进制格式。 - 表数据:Numpy数组 + 行指标index + 列标签columns
1
2
3df = pd.DataFrame(np.random.randn(6, 4),
index=pd.date_range("20220220", periods=6),
columns=[0, 'A', 'B', 'C']) - 列数据:字典类型 (以列标签为key,列数据为value)
1
2
3
4
5
6
7
8df2 = pd.DataFrame(
{"A": 1.0, # 浮点数
"B": pd.Timestamp("20220220"), # 时间戳
"C": pd.Series(1, index=list(range(4)), dtype="float32"), # Series
"D": np.array([3] * 4, dtype="int32"), # Numpy数组
"E": pd.Categorical(["test", "train", "test", "train"]), # 类别数据
"F": "foo", # 字符串
}) - 多重索引/层级索引
pd.MultiIndex
行列标签
- 行指标:
df.index
、df.index.array
- 列标签:
df.custom-columns
、df.custom-columns.array
- 修改:
df.set_index(<col>)
指定某列数据为行指标,指定多列时生成MultiIndex
df.reset_index()
将索引恢复默认,原索引会被重置为数据列(MultiIndex变为多列数据) - 改名:
df.rename(mapper=None, index=None, columns=None, axis=None)
修改行/列标签名
df.rename_axis()
修改表头(axis)名,即 行标签所在列 或 列标签所在行 的名字
数据筛选
- 行选择:
df.head/df.tail(n)
头尾、df.nlargest/df.nsmallest(n, column_labels)
df[...]
切片 | 布尔数组/逻辑判断df[0:3]
,df2[df2['E'].isin(['test'])]
- 列选择:
df[...]
标签(列表) 、df.<col>
属性访问、df.query()
布尔数组/逻辑判断
df.select_dtypes(include=None, exclude=None)
基于数据类型进行列选择
df.pop(<col>)
返回指定列,同时将该列从原数据表中移除
df[...]
只有当括号中内容为切片或布尔数组时才会被视为行索引,其他任何数字、字符串、列表等都会视为列标签(参照字典的key值)。在普通的数字索引切片之外,pandas还支持直接对索引标签切片,但不同于数字指标切片,标签的切片操作会同时包含头和尾。
此外,符合语法规则前提下,列标签会被自动转为属性,可由属性操作符访问,如df.A == df['A']
。但需注意,若列标签不符合Python变量命名规则,如为数字或含有空格等特殊字符,则无法转为属性。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16df[:2] # 行选择(数字索引切片)
df[:'20220222'] # 行选择(索引标签切片)
df[df.index.isin(df2['B'])] # 行选择(逻辑判断)
df[0] # 列选择(标签)
df[[0, 'A']] # 列选择(标签列表)
df.A == df['A'] # 列选择(属性访问)
# 访问多列数据会返回DataFrame,需按DataFrame规则继续筛选数据
# 访问单独的列会返回Series,可按Series访问规则进一步定位元素
df[0][0]
df[0][1:3]
df['A']['20220221']
df['A'][:'20220223']
# Pandas常见数据类型有'number', 'object', 'category'(字符串为'object')
# 时序相关的'datetime', 'timedelta', 'datetimetz'等
df.select_dtypes(exclude=['object']) # 排除字符串数据(及其他'object'类型) - 行 + 列:
df.at[], df.iat[]
、df.loc[], df.iloc[]
、df.filter()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# df.iat[], df.iloc[] 通过数字指标(index)筛选数据
df.iat[0, 0]
df.iloc[:2]
df.iloc[:, [0, 2]]
df.iloc[:2, [0, 2]]
df.iloc[df[0]>0, [0, 3]] # ValueError,不可用逻辑判断
df.iloc[(df[0]>0).array, [0, 3]] # 但可以用布尔数组
# df.at[], df.loc[] 通过行指标/列标签筛选数据(也可用布尔数组/逻辑判断)
df.at[df.index[0], df.custom-columns[0]]
df.at['20220220', 0] # df.at[pd.Timestamp('20220220'), 0]
df.loc[:'20220221'] # df.loc[pd.date_range('20220220', periods=2)]
df.loc[:, [0, 'B']]
df.loc[df[0]>0, 0:'B']
# df.filter(items=None, like:'str'=None, regex:'str'=None, axis=None)
df.filter(like='2022-02-20', axis=0)
# `axis`指定筛选行或列(默认为列),根据行指标/列标签规则筛选数据
# `items`(list-like) 指定具体行指标/列标签
# `like`(str) 筛选含特定字符的行指标/列标签
# `regex`(str) 用于设置正则表达式
注:.at, .iat, .loc, .iloc
是经过专门优化的pandas方法,虽然不如Python/Numpy的索引和切片方便直观,但生产环境代码还是更推荐前者。
数据分组
- 分组:
df.groupby()
分组函数返回的是一个特殊的DataFrameGroupBy
对象,通常需要跟后续数据分析操作或应用其他自定义函数操作,并最终返回普通DataFrame
/Series
对象。DataFrameGroupBy
对象的列标签属性为SeriesGroupBy
对象。1
2
3
4
5
6df2.groupby('E').size() # 注意这里size为方法,DataFrame的size为属性!!
df2.groupby('E').E.count() # df2.groupby('E').E 为`SeriesGroupBy`对象
df2.groupby('E').count()
df2.groupby('E').agg(<func>)
# 总结:要得到某列数据为行标签的统计结果,则先根据该列数据分组,再统计
df2.groupby('E').rank(axis=0, method='average')
注1:显然sum()等统计操作返回值形状跟分组数有关,此时返回的DataFrame
/Series
的行标签为用于分组的列数据。但也有些操作返回值与原始输入是同尺寸的,如秩序量rank()、平移操作或累积运算等,这些方法应用于分组对象上时,虽然操作是分组执行的,但最后返回值仍保持分组前形状(行标签也保持不变)。
注2:基于多列数据分组时会出现多重索引/层级索引(MultiIndex
),df.rest_index()
可将索引恢复默认,将多重索引重置为数据列。
- 窗口:
df.rolling(window, win_type=None, axis=0)
滚动/滑动窗口
df.expanding(min_periods=1, axis=0)
可扩展窗口,用于累积运算
类似分组函数,窗口函数返回的也是特殊的窗口对象,通常需要跟后续数据分析等函数操作,最终返回DataFrame
对象。1
2df[0].rolling(window=2).mean() # 滑动平均
df[0].expanding().sum() # 累积求和 df[0].cumsum()
数组变形
- 形状:
len(df)
行数、df.shape
形状、df.size
元素数 - 转置:
df.T
Series需先变为DateFrame再转置s.to_frame().T
(否则转置返回自身) - 排序:
df.sort_index(axis=0, ascending=True)
,df.sort_values(by, axis=0)
- 拼接:
pd.concat([df1, df2], axis=0)
行列拼接、df.append()
追加记录(行) - 合并:
df.join(df2, on=None, how='left')
,df.combine()
,pd.merge()/df.merge()
join
合并时需指定df2
索引所对齐的列(on
),默认为索引对索引;combine
为逐列合并,merge
可实现更为复杂的数据合并,具体参考函数文档。
join
的how
参数指定合并策略,可取left, right, outer, inner
,分别对应取左侧索引、右侧索引、两者并集、两者交集,注意其中取并集(outer
)时会对索引重新排序,其余情况都会保持索引次序。1
2
3
4
5
6
7df_1 = pd.DataFrame({'Id': [0, 1, 2, 3, 4],
'Score':[90, 89, 40, 98, 80]})
df_2 = pd.DataFrame({'Id': [0, 1, 2, 3, 4],
'Grade':['A', 'B+', 'D', 'A+', 'B']})
df_1.join(df_2, lsuffix='_l', rsuffix='_r') # 按索引对齐(存在列冲突)
df_1.set_index('Id').join(df_2.set_index('Id')) # Id转为索引,并按索引对齐
df_1.join(df_2.set_index('Id'), on='Id') # 按Id对齐:df_2(Id->Index), on='Id' - 堆叠:
df.stack(level=-1)
合并数据至一列(列标签变为次级行索引)、pd.unstack()
- 透视:
df.pivot/df.pivot_table(index=None, columns=None, values=None,)
参数index
,columns
,values
的值都是原表的列标签,指定列作为数据透视表行标签、列标签及取值,此外pivot_table
函数还支持设定聚合函数aggfunc
(pivot
不支持),默认为求均值('mean'
)。 - 重排:
df.reindex()
基础对齐函数(标签增删重排及缺值填充等)、df.reindex_like()
df.align(df2, join='outer', axis=None)
数据表对齐重排,策略参见join
- 移除:
df.drop()
行/列、df.drop_duplicates(column_labels)
(指定列的)重复行1
2df.drop(columns=['label1', 'label2']) == df.drop(['label1', 'label2'], axis=1)
df.drop(index=['label1', 'label2']) == df.drop(['label1', 'label2'], axis=0) - 平移:
df.shift(periods=1, freq=None, axis=0)
其中freq
参数专用于时间序列
注1:向DataFrame中添加列是相对快速的,添加行则需要执行拷贝,因此尽量避免逐行追加(.append()
)记录的操作。
注2:对未对齐的表数据进行操作时会暗含对齐重排操作,此时预先主动的执行重排(.reindex()
)有助于提升性能。
数据分析
- 概述:
df.describe()
1
2# df.count()有效值(非Nan)总数, df.mean(), df.std()
# df.min(), df.max(), df.quantile([0.25, 0.5, .75]) - 统计:
df.median()
,df.var()
,df.sem()
,df.clip()
,df.idxmax()
,df.idxmin()
df.sum()
,df.cumsum()
,df.cumprod()
,df.cummax()
,df.cummin()
… - 运算:
df.add()
,df.sub()
,df.mul()
,df.div()
,df.mod()
,df.pow()
,df.abs()
- 计数:
.nunique()
(行/列)非重复值总数、.value_counts()
非重复值计数(直方图) - 采样:
df.sample(n=None, frac=None)
、df.resample(rule, axis=0)
时间序列采样 - 分箱:
pd.cut()
基于值分箱,pd.qcut()
基于分位数分箱1
2
3
4
5
6
7
8
9
10
11
12
13# pd.cut(data:1d-array like, bins: int|list_of_bins, lables=None)
# --> Categorical, Series, or 1d-array
pd.cut(df[0], bins=3, labels=False) # Series
pd.cut(df[0], bins=3, labels=['low', 'middle', 'high']) # Series
pd.cut(range(10), bins=3, labels=False) # 1d-array
pd.cut(range(10), bins=3, labels=['low', 'middle', 'high']) # Categorical
# pd.qcut(data:1d-array like, q: int|list_of_quantiles, lables=None)
# --> Categorical, Series, or 1d-array
pd.qcut(df[0], q=3, labels=False) # Series
pd.qcut(df[0], q=3, labels=['low', 'middle', 'high']) # Series
pd.qcut(range(10), q=3, labels=False) # 1d-array
pd.qcut(range(10), q=3, labels=['low', 'middle', 'high']) # Categorical - 计算:
df.assign(<label>=<callable>|<value>, ...)
添加新列(或覆盖现有列)1
2
3
4
5
6df.assign(D = lambda x: x.sum(axis=1)) == df.assign(D = df.sum(axis=1))
df.assign(D = df[0] + df.A**2 + np.sqrt(df.B.abs()))
def counts(df):
return df.count(axis=1)
df.assign(mean=df.mean(axis=1), D=counts)
df2.assign(E=df2["E"].str.upper())
外部函数
- 表级操作:
df.pipe(func, *args, **kwargs)
便于外部函数嵌入pandas函数串联序列 - 行列操作:
df.apply(func, axis=0, args=(), **kwds)
- 聚合操作:
df.agg(func, axis=0, *args, **kwargs)
,df.transform()
函数并联 - 元素操作:
df.map(func)
,,df.applymap(func)
s.map(func-like)
pandas v2.10 (Aug 30, 2023): the DataFrame.map() been added and DataFrame.applymap() has been deprecated.
1 | # 聚合操作应用单个函数时,效果与 apply()/map() 是相同 |
缺值处理
- 移除:
df.dropna(axis=0, how='any', subset=None, thresh=None)
- 填充:
df.fillna(value=None, method=None, axis=None)
- 替换:
df.replace(to_replace=None, value=None, regex=False)
df.replace()
可用于应对"Unknown", “Undisclosed”, "Invalid"等非Nan的缺失值。参数to_replace
可以是要被替换的值(数字/字符串)或正则表达式,可以是单值或列表;此外,to_replace
还可以是字典类型,具体参考帮助函数文档。
注意事项
- method chaining: 大部分pandas方法都会返回DataFrame类型,进而可直接调用后续方法,串联为数据处理的函数序列:
1
2
3
4
5
6
7
8
9
10
11pd.melt(df)\ # 融合所有标签,数据合并为一列(variable: value)
.rename(columns={'variable':'var','value':'val'})\
.query('val >= 0')
df1 = pd.DataFrame(np.random.randn(6, 4),
index=pd.date_range("20220222", periods=3),
columns=list('ABCD'))
df.append(df1)\ # 追加行记录
.merge(df, how='outer',indicator=True)\
.query('_merge=="left_only"')\
.drop(columns=['_merge']) - 字符串:Series对象有str方法,可用于对列数据矢量化执行常见字符串操作
s.str.<func>
。 - 绘图:pandas可直接调用基础绘图函数
df.plot(kind='<func>')
/df.plot.<func>()
,此外pandas.plotting
模块提供了更多高级绘图函数:scatter_matrix
,andrews_curves
,parallel_coordinates
,lag_plot
,autocorrelation_plot
,bootstrap_plot
,radviz
numexpr
和bottleneck
模块可加速pandas执行速度,推荐安装。- 判断相等:
==
判断两个Numpy数组/DataFrame相等是逐元素比较,返回布尔数组。要判断两个数组是否相等,可用(A==A).all(),但需要注意的是np.nan==np.nan是False的,因此当含有Nan时,即便两数组相等也将返回False。Numpy中使用np.equal_array()
可解决该问题,需设参数equal_nan=True,而pandas中DataFrame的equals方法则直接默认Nan是相等的。 in
v.s..isin()
:Python操作符in
是基于字典key进行判断的,而不是值。1
2
3
4s = pd.Series(range(5), index=list("abcde"))
2 in s # False
'b' in s # True
s.isin([2]) # True- 逻辑翻转:与Numpy相同,Pandas布尔数组可通过位操作翻转~A,但0,1整型数组不能用位翻转!(
~0 == -1, ~1 == -2
),可先转换为布尔类型,再取反~A.astype(bool)
,或者使用np.logical_not(A)
直接对数组执行逻辑运算。 - pandas中var()为总体方差的无偏估计(除以N-1),Numpy中var为样本方差(除以N)
- 受限于pandas处理Nan的实现,Nan被作为浮点型处理,因此当出现Nan时,整型DataFrame会变为浮点型,布尔型DataFrame则会变为
object
类型。 df.info()
可查看内存占用信息,默认不包含object
类型数据占用空间;
df.info(memory_usage="deep")
可获取全部占用,但会更耗时;
各列的内存占用可使用df.memory_usage()
查看(单位为字节)。