View on GitHub

我的极简博客

记录学习与生活

翻译-10分钟入门Pandas


本文是Pandas的简短介绍,主要面向新用户。您可以在代码示例集(Cookbook)中查看更复杂的用法。

通常,我们按如下方式导入:

import numpy as np
import pandas as pd

Pandas 中的基本数据结构

Pandas 提供了两类用于处理数据的基本结构:

  1. Series:一个一维的标签化数组,可保存任何类型的数据(整数、字符串、Python对象等)。
  2. DataFrame:一个二维的数据结构,可以像二维数组或包含行和列的表格一样保存数据。

对象创建

创建Series,传入一个值列表,Pandas 会自动创建一个默认的整数索引(RangeIndex)。

s = pd.Series([1, 3, 5, np.nan, 6, 8])
s
# 输出:
# 0    1.0
# 1    3.0
# 2    5.0
# 3    NaN
# 4    6.0
# 5    8.0
# dtype: float64

创建DataFrame,通过传递一个NumPy数组,并指定一个由date_range()生成的日期时间索引和标签化的列名。

dates = pd.date_range("20130101", periods=6)
dates
# 输出:
# DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',
#                '2013-01-05', '2013-01-06'],
#               dtype='datetime64[ns]', freq='D')

df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list("ABCD"))
df
# 输出示例:
#                    A         B         C         D
# 2013-01-01  0.469112 -0.282863 -1.509059 -1.135632
# 2013-01-02  1.212112 -0.173215  0.119209 -1.044236
# ...

通过传递一个字典对象来创建DataFrame,其中键是列标签,值是列数据。

df2 = pd.DataFrame(
    {
        "A": 1.0,
        "B": pd.Timestamp("20130102"),
        "C": pd.Series(1, index=list(range(4)), dtype="float32"),
        "D": np.array([3] * 4, dtype="int32"),
        "E": pd.Categorical(["test", "train", "test", "train"]),
        "F": "foo",
    }
)
df2
# 输出:
#      A          B    C  D      E    F
# 0  1.0 2013-01-02  1.0  3   test  foo
# 1  1.0 2013-01-02  1.0  3  train  foo
# 2  1.0 2013-01-02  1.0  3   test  foo
# 3  1.0 2013-01-02  1.0  3  train  foo

生成的DataFrame的列具有不同的数据类型(dtypes)

df2.dtypes
# 输出:
# A           float64
# B     datetime64[s]
# C           float32
# D             int32
# E          category
# F            object
# dtype: object

查看数据

使用DataFrame.head()DataFrame.tail()分别查看框架的顶部和底部行:

df.head() # 默认查看前5行
df.tail(3) # 查看最后3行

显示DataFrame.indexDataFrame.columns

df.index
df.columns

使用DataFrame.to_numpy()返回底层数据的NumPy表示(不包含索引或列标签):

df.to_numpy()
# 输出示例:
# array([[ 0.4691, -0.2829, -1.5091, -1.1356],
#        [ 1.2121, -0.1732,  0.1192, -1.0442],
#        ... ])

注意NumPy数组整个数组只有一个dtype,而pandas DataFrame每列可以有一个dtype。 当你调用DataFrame.to_numpy()时,Pandas会找到一个能够容纳DataFrame中所有dtype的NumPy数据类型。如果公共数据类型是object,则DataFrame.to_numpy()将需要复制数据。

describe()显示数据的快速统计摘要:

df.describe()

转置数据:

df.T

DataFrame.sort_index()按轴排序:

df.sort_index(axis=1, ascending=False) # 按列名降序排序

DataFrame.sort_values()按值排序:

df.sort_values(by="B") # 按列“B”的值升序排序

数据选择

通过 [] 选择

对于DataFrame,传递单个标签会选择一列,并产生一个Series(等同于df.A):

df["A"]

对于DataFrame,传递一个切片:会选择匹配的行:

df[0:3] # 选择前3行
df["20130102":"20130104"] # 通过标签切片选择行(包含两端)

按标签选择

使用DataFrame.loc()DataFrame.at()

选择匹配标签的行:

df.loc[dates[0]]

选择所有行(:)和特定的列标签:

df.loc[:, ["A", "B"]]

对于标签切片,两端都包含

df.loc["20130102":"20130104", ["A", "B"]]

选择单个行和列标签返回一个标量:

df.loc[dates[0], "A"]

使用at快速访问标量(效果与上述方法相同):

df.at[dates[0], "A"]

按位置选择

使用DataFrame.iloc()DataFrame.iat()

通过传递的整数位置选择:

df.iloc[3] # 选择第4行(0基索引)

整数切片的行为类似于NumPy/Python:

df.iloc[3:5, 0:2] # 选择行切片和列切片

通过整数位置列表选择:

df.iloc[[1, 2, 4], [0, 2]] # 选择不连续的行和列

显式地对行进行切片:

df.iloc[1:3, :] # 选择第2到第3行,所有列

显式地对列进行切片:

df.iloc[:, 1:3] # 选择所有行,第2到第3列

显式获取一个值:

df.iloc[1, 1]

使用iat快速访问标量:

df.iat[1, 1]

布尔索引

选择df.A大于0的行:

df[df["A"] > 0]

DataFrame中选择满足布尔条件的值:

df[df > 0] # 大于0的值保留,其他变为NaN

使用isin()方法进行过滤:

df2 = df.copy()
df2["E"] = ["one", "one", "two", "three", "four", "three"]
df2[df2["E"].isin(["two", "four"])]

数据设置

设置新列会自动按索引对齐数据:

s1 = pd.Series([1, 2, 3, 4, 5, 6], index=pd.date_range("20130102", periods=6))
df["F"] = s1 # 新增列‘F’

按标签设置值:

df.at[dates[0], "A"] = 0

按位置设置值:

df.iat[0, 1] = 0

通过分配NumPy数组进行设置:

df.loc[:, "D"] = np.array([5] * len(df))

带有设置的where操作:

df2 = df.copy()
df2[df2 > 0] = -df2 # 将所有大于0的值替换为其相反数

缺失数据

对于NumPy数据类型,np.nan代表缺失数据。默认情况下,它不参与计算。

重新索引(Reindexing) 允许您更改/添加/删除指定轴上的索引。这会返回一个数据副本:

df1 = df.reindex(index=dates[0:4], columns=list(df.columns) + ["E"])
df1.loc[dates[0] : dates[1], "E"] = 1
df1

DataFrame.dropna()删除任何有缺失数据的行:

df1.dropna(how="any")

DataFrame.fillna()填充缺失数据:

df1.fillna(value=5)

isna()获取值为nan的布尔掩码:

pd.isna(df1)

操作

统计

操作通常排除缺失数据。

计算每列的平均值:

df.mean() # 默认按列计算

计算每行的平均值:

df.mean(axis=1) # 按行计算

用户自定义函数

DataFrame.agg()DataFrame.transform()分别应用归约广播结果的用户定义函数。

df.agg(lambda x: np.mean(x) * 5.6) # 聚合,对每列应用函数
df.transform(lambda x: x * 101.2) # 转换,对每个元素应用函数

值计数

s = pd.Series(np.random.randint(0, 7, size=10))
s.value_counts() # 统计每个唯一值出现的次数

字符串方法

Series配备了一组字符串处理方法(在str属性中),可以轻松地对数组的每个元素进行操作。

s = pd.Series(["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"])
s.str.lower() # 将每个字符串转换为小写

合并

连接(Concat)

使用concat()沿行方向连接pandas对象:

df = pd.DataFrame(np.random.randn(10, 4))
pieces = [df[:3], df[3:7], df[7:]]
pd.concat(pieces) # 将拆分的片段重新连接

注意: 向DataFrame添加列相对较快。然而,添加行需要复制,可能代价高昂。我们建议将预构建的记录列表传递给DataFrame构造函数,而不是通过迭代追加记录来构建DataFrame

连接(Join)

merge()支持沿特定列的SQL风格连接类型。

left = pd.DataFrame({"key": ["foo", "foo"], "lval": [1, 2]})
right = pd.DataFrame({"key": ["foo", "foo"], "rval": [4, 5]})
pd.merge(left, right, on="key")

在唯一键上进行merge()

left = pd.DataFrame({"key": ["foo", "bar"], "lval": [1, 2]})
right = pd.DataFrame({"key": ["foo", "bar"], "rval": [4, 5]})
pd.merge(left, right, on="key")

分组(Grouping)

“分组”是指涉及以下一个或多个步骤的过程:

  1. 拆分:根据某些条件将数据拆分为组。
  2. 应用:将函数独立应用于每个组。
  3. 合并:将结果组合成数据结构。

按列标签分组,选择列标签,然后对结果组应用sum()函数:

df = pd.DataFrame({
    "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
    "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
    "C": np.random.randn(8),
    "D": np.random.randn(8)
})
df.groupby("A")[["C", "D"]].sum()

按多个列标签分组会形成MultiIndex(多层索引):

df.groupby(["A", "B"]).sum()

重塑

堆叠(Stack)

stack()方法“压缩”DataFrame列中的一个层级:

arrays = [["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
          ["one", "two", "one", "two", "one", "two", "one", "two"]]
index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=["A", "B"])
df2 = df[:4]
stacked = df2.stack(future_stack=True)
stacked

对于“堆叠”后的DataFrameSeries(索引为MultiIndex),stack()的逆操作是unstack(),默认情况下它解堆最后一个层级:

stacked.unstack()
stacked.unstack(1) # 解堆第二层
stacked.unstack(0) # 解堆第一层

数据透视表(Pivot Tables)

pivot_table()通过指定values(值)、index(索引)和columns(列)来透视一个DataFrame

df = pd.DataFrame({
    "A": ["one", "one", "two", "three"] * 3,
    "B": ["A", "B", "C"] * 4,
    "C": ["foo", "foo", "foo", "bar", "bar", "bar"] * 2,
    "D": np.random.randn(12),
    "E": np.random.randn(12)
})
pd.pivot_table(df, values="D", index=["A", "B"], columns=["C"])

时间序列

Pandas具有简单、强大且高效的功能,用于在执行频率转换期间进行重采样操作(例如,将秒数据转换为5分钟数据)。

rng = pd.date_range("1/1/2012", periods=100, freq="s")
ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng)
ts.resample("5Min").sum() # 每5分钟求和

Series.tz_localize()将时间序列本地化到一个时区:

rng = pd.date_range("3/6/2012 00:00", periods=5, freq="D")
ts = pd.Series(np.random.randn(len(rng)), rng)
ts_utc = ts.tz_localize("UTC") # 本地化为UTC时区

Series.tz_convert()将已有时区信息的时间序列转换为另一个时区:

ts_utc.tz_convert("US/Eastern") # 从UTC转换到美国东部时间

向时间序列添加非固定持续时间(BusinessDay):

rng + pd.offsets.BusinessDay(5) # 增加5个工作日

分类数据

Pandas可以在DataFrame中包含分类数据。

将原始成绩转换为分类数据类型:

df = pd.DataFrame({"id": [1, 2, 3, 4, 5, 6], "raw_grade": ["a", "b", "b", "a", "a", "e"]})
df["grade"] = df["raw_grade"].astype("category")
df["grade"]

将类别重命名为更有意义的名称:

new_categories = ["very good", "good", "very bad"]
df["grade"] = df["grade"].cat.rename_categories(new_categories)

对类别重新排序,并同时添加缺失的类别:

df["grade"] = df["grade"].cat.set_categories(["very bad", "bad", "medium", "good", "very good"])

按分类列排序遵循类别中的顺序,而不是词法顺序:

df.sort_values(by="grade")

使用observed=False按分类列分组也会显示空类别:

df.groupby("grade", observed=False).size()

绘图

我们使用引用matplotlib API的标准约定:

import matplotlib.pyplot as plt
plt.close("all") # 关闭图形窗口

ts = pd.Series(np.random.randn(1000), index=pd.date_range("1/1/2000", periods=1000))
ts = ts.cumsum()
ts.plot() # 绘制Series

df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index, columns=["A", "B", "C", "D"])
df = df.cumsum()
df.plot() # 绘制DataFrame,所有列
plt.legend(loc='best')

数据的导入和导出

CSV

写入CSV文件:使用DataFrame.to_csv()

df.to_csv("foo.csv")

从CSV文件读取:使用read_csv()

pd.read_csv("foo.csv")

Parquet

写入Parquet文件

df.to_parquet("foo.parquet")

从Parquet文件读取:使用read_parquet()

pd.read_parquet("foo.parquet")

Excel

写入Excel文件:使用DataFrame.to_excel()

df.to_excel("foo.xlsx", sheet_name="Sheet1")

从Excel文件读取:使用read_excel()

pd.read_excel("foo.xlsx", "Sheet1", index_col=None, na_values=["NA"])

常见陷阱(Gotchas)

如果你尝试对SeriesDataFrame执行布尔操作,可能会看到如下异常:

if pd.Series([False, True, False]):
     print("I was true")
# 抛出 ValueError: The truth value of a Series is ambiguous...

错误提示很明确:Series的真值是模糊的。应使用a.emptya.bool()a.item()a.any()a.all()来判断。