pandas快速入门

Pandas Cheat Sheet
10 minutes to pandas
Wes McKinney: pandas in 10 minutes
Essential Basic Function

1
2
import numpy as np
import pandas as pd

数据类型

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一维数组多了行标签,除数字索引及切片外还可用标签访问(类似字典),要求数据类型一致。注意,相比二维表格DataFrameSeries并没有列标签columns,只有行标签s.index及整体的名字s.name
    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 > iter
    注1:上面示例代码中的Series属于时间序列,其索引为pd.date_range()所生成的特殊索引(DatetimeIndex)。 to_datetime(), to_timestamp(), to_period(), at_time()
    注2:对DataFrame执行迭代操作不是好想法,推荐使用矢量化操作或借助map等辅助函数来应用自定义函数。

数据访问可使用索引(标签)、数字索引切片或布尔数组,默认为列筛选,但数字索引切片及布尔数组会作为行筛选,更复杂数据访问可使用loc(),iloc()方法,具体参考数据筛选。获取所有数据的values属性推荐替换为Series.arrayDataFrame.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
    3
    df = 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
    8
    df2 = 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.indexdf.index.array
  • 列标签:df.custom-columnsdf.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
    16
    df[: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
    6
    df2.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
    2
    df[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可实现更为复杂的数据合并,具体参考函数文档。
    joinhow参数指定合并策略,可取left, right, outer, inner,分别对应取左侧索引、右侧索引、两者并集、两者交集,注意其中取并集(outer)时会对索引重新排序,其余情况都会保持索引次序。
    1
    2
    3
    4
    5
    6
    7
    df_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
    2
    df.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
    6
    df.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 聚合操作应用单个函数时,效果与 apply()/map() 是相同
# 更常见用法是一次应用多个函数,避免逐一执行的繁琐(函数各自独立而非串联)
# 其中 agg() 与 tansform() 的主要区别在于后者会尽量保持输出与输入形状一致
# 从名字上理解,前者主要用于元素的聚合分析,后者主要用于元素的逐个变换
# 直接用于DataFrame/Series时,后者只能应用逐元素操作,无法执行mean等行列操作
# 与groupby分组配合时,后者则只能应用mean等行列操作,而无法应用逐元素操作
# 且结果会在组内广播以保持输出尺寸不变,此外此时不能再同时应用多个函数!
df = pd.DataFrame(dict(A=[1, 1, 0, 1], B=[1, 2, 3, 4], C=[1, -1, 1, -1]))
df.agg('mean') # 返回Series
df.agg(['mean']) # 返回DataFrame,结果作为其中一行,标签为函数名
df.A.agg(['mean']) # 返回Series
df.agg(['mean', 'sum'])

df.transform('mean') # ValueError
df.transform('abs') # 尺寸不变
df.transform(['abs']) # 尺寸不变,列标签多了次级标签(MultiIndex)
df.transform(['abs', 'sqrt'])

df.groupby('A').mean()
df.groupby('A').agg('mean') # 以A列取值为行标签的DataFrame
df.groupby('A').agg(['mean'])
df.groupby('A').agg(['mean', 'sum'])

df.groupby('A').transform('mean') # 尺寸不变,每组结果在组内广播
df.groupby('A').transform('abs') # ValueError
df.groupby('A').transform(['mean']) # TypeError

# 以字典格式传入函数,可限定定应用函数的数据列
df.agg({"A": "mean", "B": "sum"}) # 返回Series,行标签为对应数据列的标签
df.agg({"A": ["max", "min"], "B": "sum"}) # 返回DataFrame,函数X数据列
df.groupby('A').agg(dict(A='sum', B=['mean', 'prod'])) # MultiIndex

df.transform({"A": np.abs, "B": "sqrt"}) # 返回DataFrame,只包含指定的列
df.transform({"A": np.abs, "B": ["sqrt", lambda x: x + 1]}) # MultiIndex
df.groupby('A').transform({"A": np.abs}) # TypeError

缺值处理

  • 移除: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
    11
    pd.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
  • numexprbottleneck模块可加速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
    4
    s = 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()查看(单位为字节)。