groupby().apply()接收的是dataframe而非series,需显式取列;单列聚合优先用agg(),跨列逻辑才用apply();返回类型决定输出结构,内置方法比lambda快5–10倍。

groupby().apply() 是最直接的写法,但要注意函数接收的是 DataFrame 而非 Series
当你对 df.groupby('col') 后调用 apply(),传入的 lambda 函数默认接收到的是每个分组对应的子 DataFrame,不是单列。如果只想要某列做计算,得显式取列,比如:lambda x: x['value'].sum(),而不是 lambda x: x.sum()(后者会对所有数值列求和)。
常见错误现象:结果多出一维索引、返回值类型意外(如得到 Series 而非标量)、报 AttributeError: 'Series' object has no attribute 'xxx'——这往往是因为误以为 x 是 Series。
- 若只需单列聚合,优先考虑
agg()或transform(),更安全高效 - 若需跨列逻辑(比如“本组最大值减最小值”),
apply()才是合理选择 - 函数返回标量 → 结果是
Series;返回DataFrame或Series且长度不等于原分组 → 结果自动升维为DataFrame
用 agg() + lambda 更适合单列聚合,且性能更好
agg() 在语义和性能上都更适合“对每列应用一个聚合函数”。它允许你对不同列指定不同 lambda,也支持字符串简写(如 'mean')。
例如:df.groupby('category')['score'].agg(lambda x: (x.max() - x.min()) / x.mean()),这里 x 就是 Series,可直接用 .max()、.min() 等方法。
- 避免在
agg()中写返回多个值的 lambda(如lambda x: (x.mean(), x.std())),会报错或结果异常 - 要同时算多个指标?改用元组列表:
df.groupby('a')['b'].agg([('avg', 'mean'), ('std', 'std')]) - 如果 lambda 里需要访问其他列,说明你其实该用
apply(),因为agg()只作用于单列
apply() 里 return 类型决定输出结构,别让 pandas 自动“猜”
apply() 的行为高度依赖 lambda 的返回值类型和长度。pandas 会尝试“推断”你想要什么结构,但这个推断常出错。
比如:df.groupby('id').apply(lambda g: g.iloc[0]) 返回每个分组第一行,结果是 DataFrame;而 df.groupby('id').apply(lambda g: g['val'].sum()) 返回 Series(索引为分组键)。
- 想强制返回
Series:用pd.Series显式构造,例如lambda g: pd.Series({'sum': g.x.sum(), 'count': len(g)}) - 想保持原始索引?加参数
include_groups=False(pandas ≥ 2.1)或手动重置索引 - 返回字典会被转成
Series,但键名变成列名——这点容易被忽略,导致后续列名混乱
性能敏感时,避免 apply() 做简单聚合,优先用内置方法
写 lambda x: x.mean() 和直接写 'mean' 表面效果一样,但前者慢 5–10 倍,因为绕过了 pandas 底层优化路径。
尤其当数据量大或分组数多时,这种差异会放大。内置方法('sum'、'nunique'、np.std 等)经过 Cython 加速,而 lambda 总是走 Python 解释器路径。
- 检查是否真需要 lambda:多数统计需求都有对应字符串别名或 NumPy 函数
- 复杂逻辑无法避免 lambda 时,考虑先用
reset_index()拆成普通 DataFrame 处理,再 merge 回去,有时反而更快 - 用
%timeit对比实际耗时,别凭感觉判断“应该差不多”
真正麻烦的不是语法怎么写,而是搞清你传进去的 x 到底是 DataFrame 还是 Series,以及你 return 的东西 pandas 会怎么 reshape。这两点错了,结果就悄无声息地变样了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











