Pandas GroupBy:聚合、Transform、Apply 全面指南(2025)
Updated on
split-apply-combine 是 Pandas 的核心能力,但很多人难以在 agg、transform、apply 之间做选择,或被排序、缺失分组、SettingWithCopy 等问题困扰。
PAS 结构:
- 问题: 分组计算让人摸不着头脑、速度慢,而你只想要总计或按行的比例。
- 激化: 滥用
apply或忘记as_index=False会带来奇怪的形状、重复列和缓慢的管道。 - 解决: 几个固定模式即可——
agg做汇总,transform做逐行特征,apply仅在必要时;同时明确排序与缺失组的选项。
快速参考:如何选择方法
| 方法 | 返回形状 | 适用场景 | 示例 |
|---|---|---|---|
agg / aggregate | 每组一行 | 汇总、多指标输出 | df.groupby("team").agg(avg_score=("score","mean")) |
transform | 与输入同长 | 给每行添加分组特征 | df["z"] = df.groupby("team")["score"].transform("zscore") |
apply | 灵活 | agg/transform 无法覆盖的复杂逻辑 | df.groupby("team").apply(custom_fn) |
示例数据
import pandas as pd
data = {
"team": ["A", "A", "B", "B", "B", "C"],
"player": ["x1", "x2", "y1", "y2", "y3", "z1"],
"score": [9, 7, 8, 6, 10, 3],
"minutes": [30, 25, 28, 32, 20, 15],
}
df = pd.DataFrame(data)聚合模式 (agg)
summary = (
df.groupby("team", as_index=False)
.agg(
avg_score=("score", "mean"),
max_score=("score", "max"),
minutes_played=("minutes", "sum"),
)
)- 用 named aggregation 保持列名清晰。
as_index=False保留team为列(便于合并与绘图)。sort=False在意顺序时可保留输入顺序。
同一列的多重聚合
df.groupby("team", as_index=False).agg(
score_mean=("score", "mean"),
score_std=("score", "std"),
score_count=("score", "size"),
)行级特征 (transform)
transform 保持原始行数,把分组指标回填到每行。
df["score_pct_of_team"] = (
df["score"] / df.groupby("team")["score"].transform("sum")
)每组 Z 分数:
df["score_z"] = (
df.groupby("team")["score"]
.transform(lambda s: (s - s.mean()) / s.std(ddof=0))
)适用场景:
- 相对于分组总量的比例
- 归一化/标准化分数
- 分组标记(如
rank、cumcount)
何时使用 apply
apply 灵活但更慢;仅当 agg/transform 不够时使用。
def top_n(group, n=2):
return group.nlargest(n, "score")
top_players = df.groupby("team").apply(top_n, n=1).reset_index(drop=True)使用 apply 的场景:
- 每组返回的行数可变。
- 需要无法矢量化的 Python 逻辑。
缺失分组与排序
result = (
df.groupby("team", dropna=False, sort=False)
.agg(avg_score=("score", "mean"))
)dropna=False保留NaN分组标签。sort=False避免重新排序——当顺序有含义时很重要。
多键与索引分组
multi = (
df.groupby(["team", "player"], as_index=False)
.agg(score_mean=("score", "mean"))
)按索引层分组:
df2 = df.set_index(["team", "player"])
df2.groupby(level="team")["score"].mean()常见坑位
| 问题 | 处理 |
|---|---|
形状出乎意料(Series vs DataFrame) | 设置 as_index=False 或事后 reset_index()。 |
| 合并后列名冲突 | 用 named aggregation 控制输出列名。 |
apply 过慢 | 改用 agg/transform 或矢量化方法。 |
| 输出缺失分类 | 保持 observed=False(默认)或确保分类;dropna=False 保留 NaN。 |
导出与可视化提示
- 聚合后排序展示:
summary.sort_values("avg_score", ascending=False)。 - 制图可先 pivot:
summary.pivot(index="team", values="avg_score")。
相关指南
关键要点
- 汇总用
agg,行级特征用transform,apply尽量少用。 - 用
as_index、reset_index控制形状。 - 用
sort、dropna管理顺序与缺失标签。 - 优先矢量化方法,让管道保持高速。