pandas groupby 后 apply 比 agg/transform 慢在哪里?

11次阅读

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

pandas groupby 后 apply 比 agg/transform 慢在哪里?

为什么 groupby().apply() 慢得离谱?

根本原因就一个:它在 Python 层面逐组调用函数,完全绕过了 pandas 底层的 C/Fortran 向量化引擎。哪怕你只写 Lambda x: x.mean()apply 也会把每个子 SeriesDataFrame 拷贝出来、交由 Python 解释器执行——这和直接用 .agg('mean') 调用底层优化过的 C 函数,性能差 5–20 倍很常见。

  • apply 本质是“封装了 for 循环”的接口,不管函数多简单,都触发解释器开销
  • aggtransform 优先走内置路径(如 'sum''first'),能直接映射到底层向量化实现
  • 自定义函数传给 aggtransform 时也会变慢,但比 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。

text=ZqhQzanResources