apply比df['a']+df['b']慢50–200倍,因其本质是python层for循环:每行新建series、做类型推断、对齐索引,绕过numpy向量化加速。

因为它本质是 Python 层的 for 循环,不是向量化操作——每行都新建 Series、做类型推断、对齐索引,开销远超你的业务逻辑本身。
apply 为什么比 df['A'] + df['B'] 慢 50–200 倍
你写的 df.apply(lambda row: row['A'] + row['B'], axis=1) 看似简洁,但 pandas 实际做了三件事:把每一行转成一个 Series 对象(含索引、dtype、name)、调用你的 lambda、再把返回值 infer 类型并重新对齐索引。这三步全是 Python 层开销,完全绕过了 NumPy 的 C 语言连续内存访问和 SIMD 加速。
- 每行构造
Series:对象创建/销毁成本高,尤其当列数多时 - 类型推断:返回值可能是
int、float或None,pandas 要逐个检查再统一降级为objectdtype - 索引对齐:即使你只返回标量,pandas 也要把它塞回原 DataFrame 的对应位置,涉及哈希查找和拷贝
axis=1 下尤其慢的底层原因
在 axis=1 时,pandas 不是“传入一行数据”,而是“为每一行新建一个带完整元信息的 Series”。这意味着:10 万行 = 10 万个 Series 实例 + 10 万次类型重推 + 10 万次索引重建。
-
raw=True可跳过Series构造,改传numpy.ndarray,提速约 3–5×,但依然逃不开逐行调度 - 若函数里有
if/else、.str.contains()或正则,性能会进一步断崖下跌——这些操作本身就不向量化 - 别指望
swifter在axis=1下自动救场:它采样后可能误判为“适合 Dask”,结果因字符串列含NaN报TypeError: expected bytes, got float
真正能提速的替代写法(按优先级)
别优化 apply,直接换掉它。以下写法经金融风控、IoT 日志等生产环境实测有效:
- 能向量化就坚决不用
apply:df['A'].values + df['B'].values(纯 NumPy 数组运算,无索引、无类型检查) - 不能向量化时,绕过 pandas 行对象:
[row.A + row.B for row in df.itertuples()]或list(map(lambda x: x[1] + x[2], df[['A', 'B']].values)) - 必须用自定义逻辑且数据量 > 50 万行?自己控进程:
concurrent.futures.ProcessPoolExecutor,但别传整个DataFrame,切分后传list或numpy.ndarray
groupby().apply() 和 rolling().apply() 别白试 swifter
swifter 对 groupby().apply()、rolling().apply() 完全无效——它的路由逻辑压根不覆盖这些场景。这类操作本身已带聚合语义,加速要靠预分组、改用 agg + 内置函数,或直接换 Polars。
-
groupby().apply()慢,往往是因为你在每个组内又写了apply,形成嵌套解释器循环 - 若逻辑真复杂(比如组内状态机),先用
groupby().indices拿到行号切片,再用多进程处理各子集 - 注意全局变量不会自动同步到子进程,lookup 字典、配置项必须显式传入或封装进闭包
真正卡住你的从来不是“怎么写 apply”,而是没意识到它根本不是为性能设计的——它是个灵活的逃生舱,不是引擎。当数据过十万行,还依赖 apply,相当于开着拖拉机跑高速,油门踩到底也追不上别人换挡。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











