groupby().apply() 慢的根本原因是它在 Python 层逐组调用函数,绕过 pandas 底层 C/Fortran 向量化引擎,导致拷贝子对象并承受解释器开销,性能比 agg 低 5–20 倍。

为什么 groupby().apply() 慢得离谱?
根本原因就一个:它在 Python 层面逐组调用函数,完全绕过了 pandas 底层的 C/Fortran 向量化引擎。哪怕你只写 lambda x: x.mean(),apply 也会把每个子 Series 或 DataFrame 拷贝出来、交由 Python 解释器执行——这和直接用 .agg('mean') 调用底层优化过的 C 函数,性能差 5–20 倍很常见。
-
apply本质是“封装了 for 循环”的接口,不管函数多简单,都触发解释器开销 -
agg和transform优先走内置路径(如'sum'、'first'),能直接映射到底层向量化实现 - 自定义函数传给
agg或transform时也会变慢,但比apply好——因为至少避免了额外的子对象构造和索引重建
apply vs agg vs transform:该选谁?
看你要的是“聚合结果”还是“扩展结果”,以及函数是否可向量化:
- 要单个汇总值(如每组均值、计数)→ 无条件用
agg,字符串名('mean')比函数对象快得多 - 要保持原长度、广播回原位置(如每组内标准化)→ 用
transform,但别传复杂 lambda;transform('zscore')可以,transform(lambda x: (x - x.mean()) / x.std())就不行 - 真需要逐组做不可拆解的逻辑(比如拼接字符串 + 条件判断 + 外部 API 调用)→ 才轮到
apply,且务必加result_type='expand'或提前预设返回结构,避免 pandas 自动推断耗时
一个典型踩坑:你以为在用 agg,其实掉进了 apply 的坑
这种写法:df.groupby('key').agg({'col': lambda x: x.max() - x.min()}),看着像 agg,实则等价于 apply——因为用了 lambda,pandas 无法识别为内置函数,只能退化为 Python 循环。
- 正确提速写法:
df.groupby('key')['col'].agg(['min', 'max']).apply(lambda row: row['max'] - row['min'], axis=1)(先批量算,再轻量计算) - 或更优:
df.groupby('key')['col'].agg(lambda x: x.max() - x.min())—— 注意这里是 SeriesGroupBy.agg,不是 DataFrameGroupBy.agg,路径更短,开销略小 - 别在
agg字典里混用字符串和函数,会强制统一走慢路径
真正影响性能的,往往不是函数本身,而是分组对象的“惰性”程度
pandas 的 groupby 是惰性求值的,但 apply 会立刻触发分组切片和对象构造,而 agg 在多数内置场景下可延迟到最终计算才真正分组。
- 高频误操作:链式写
df.groupby(...).apply(...).sort_values(...)→ 每次都重算分组 - 推荐做法:先存
g = df.groupby(['a', 'b']),再分别调g.agg(...)、g.transform(...)—— 分组只做一次,后续复用 - 如果必须用
apply,加engine='numba'(仅限数值列+简单函数)或改用swifter包自动 fallback,但别指望翻倍提升
真正卡住你的,往往不是“会不会写”,而是没意识到 apply 这个名字背后藏着一层 Python 循环胶水——它不报错,不警告,只默默拖慢你整个 pipeline。










