Skip to content

Pandas GroupBy:聚合、Transform、Apply 全面指南(2025)

Updated on

split-apply-combine 是 Pandas 的核心能力,但很多人难以在 aggtransformapply 之间做选择,或被排序、缺失分组、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))
)

适用场景:

  • 相对于分组总量的比例
  • 归一化/标准化分数
  • 分组标记(如 rankcumcount

何时使用 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,行级特征用 transformapply 尽量少用。
  • as_indexreset_index 控制形状。
  • sortdropna 管理顺序与缺失标签。
  • 优先矢量化方法,让管道保持高速。