跳到主要内容

Pandas 惯用法

优秀的 Pandas 代码应当清晰展示数据变换的步骤,并优先使用最精确的操作方法。

变换风格

import pandas as pd

orders = pd.DataFrame({
"customer_id": ["A", "A", "B"], "status": ["paid", "paid", "pending"],
"gross": [100, 50, 80], "fee": [10, 5, 8],
})
result = (
orders.loc[lambda x: x["status"].eq("paid")]
.assign(net=lambda x: x["gross"] - x["fee"])
.groupby("customer_id", as_index=False)
.agg(total_net=("net", "sum"), orders=("net", "size"))
.sort_values("total_net", ascending=False)
)

当链式调用变得难以阅读、复用或调试时,将其拆分为具名步骤。方法链旨在提升可读性,而非保证性能。

操作选型

场景推荐方法
标量算术或类型化操作向量化表达式 / 访问器 (Accessor)
对每个值进行映射或查找Series.map / DataFrame.map
聚合计算(降维)agg
返回与输入同形状的结果transform
对行或列应用自定义函数apply
在链中插入整表处理函数pipe
def require_positive(frame, column):
if frame[column].isna().any() or not frame[column].ge(0).all():
raise ValueError(f"{column} must be present and non-negative")
return frame

clean = orders.pipe(require_positive, "gross")

优先向量化,慎用 apply

# 优先使用向量化操作
df = pd.DataFrame({"numerator": [10.0, 0.0], "denominator": [2.0, 0.0]})
df["ratio"] = df["numerator"].div(df["denominator"].replace(0, float("nan")))

# 仅当逻辑确实依赖整行数据且无法向量化时,才使用 apply
def classify_row(row):
if pd.isna(row["ratio"]):
return "undefined"
return "high" if row["ratio"] >= 4 else "low"

df["label"] = df.apply(classify_row, axis="columns")

逐行 apply 会为每一行构造 Series 对象并反复调用 Python 解释器,开销较大。请先确保表达式逻辑正确且清晰,并在具有代表性的数据上验证后,再进行性能基准测试。

修改规则

优先使用返回新对象的表达式和单步 .loc 赋值。避免链式赋值(Chained Assignment),也不要依赖 inplace=True 作为性能优化手段。

看清返回对象

方法链返回一行:客户 A、total_net=135orders=2;原始 orders 仍有三行,也没有 net 列。pipe 直接返回函数的返回值。这里 require_positive 只检查数据并返回传入的表格,因此 clean.equals(orders) 为真;数据值没有被清洗。不要依赖跨版本的对象身份:pandas 3.0 会把浅副本传给函数。

除法示例明确把分母零映射为缺失,结果为 [5.0, NaN],标签为 highundefined。若不处理,非零数除以零可能得到无穷大。这个小分类函数只是演示逐行回调的输入输出,简单逻辑也能向量化。DataFrame.map 要求 pandas 2.1 或更新版本。DataFrame.apply 默认逐列(axis=0),axis=1 则逐行;输出形状取决于回调返回什么。

参考资料

探索关联

这篇笔记还没有文档关联。

同主题的其他笔记 (54)

打开关联网络