iterrows() 极慢是因为每行都新建 pd.series,触发索引、dtype 推断和内存分配,违背 numpy 向量化设计;10 万行耗时 0.39 秒,而向量化写法仅需 2 毫秒,差距百倍。

iterrows() 为什么慢到反直觉
因为它根本不是“遍历行”,而是每轮都新建一个 pd.Series 对象:带完整索引、做 dtype 推断、触发 Python 层对象构造和内存分配。这和 Pandas 底层的 NumPy 向量化设计完全冲突——相当于开着法拉利在乡间土路上挂一档爬行。
实测 10 万行 × 5 列 DataFrame,iterrows() 耗时约 0.39 秒,而等效逻辑用向量化写法(如 df['a'] + df['b'])通常不到 2 毫秒。差距不是几倍,是百倍量级。
- 每次迭代都要对整行做类型检查、索引对齐、缺失值处理
- 返回的
Series是新对象,无法被 Numba JIT 编译 - 如果你在循环里还调
row['col'].upper()或df.loc[index, 'x'] = ...,性能雪上加霜
itertuples() 快在哪?但别直接抄代码
itertuples() 不构造 Series,它返回轻量级命名元组(或普通 tuple),字段直接映射列名,走的是 C 层迭代路径。10 万行下比 iterrows() 快 7–8 倍是常态。
但直接写 for row in df.itertuples(): 很可能没真正提速,因为默认参数埋了坑:
- 必须加
index=False:否则第一项永远是索引值,容易误读且多一次拷贝 - 列名含空格或特殊字符(如
"user id")时,row.user id会报错,实际字段名变成_1、_2—— 提前清理列名或改用位置访问row[1] - 需要属性访问(
row.col_name)就得设name='Row';若只图快,用name=None返回普通 tuple,row[0]访问更快
什么情况必须扔掉所有 for 循环
只要逻辑不依赖「上一行结果」(比如状态累积、滚动条件判断),90% 的 iterrows() 场景都能向量化。这不是优化技巧,是底层执行路径的根本切换。
- 条件赋值 → 改用
np.where(df['a'] > 0, df['b'] * 2, df['c'])或布尔索引df.loc[df['a'] > 0, 'new'] = ... - 字符串操作 → 全部走
str访问器:df['email'].str.contains('@')、df['name'].str.strip().str.title() - 时间运算 →
pd.to_datetime(df['ts']) + pd.Timedelta('1D'),别在循环里解析再加 - 数值函数 → 直接传 Series 给 NumPy:
np.log1p(df['x']),不是df['x'].apply(np.log1p)
真绕不开逐行逻辑时,怎么选最不慢的
如果业务规则复杂到无法拆解(比如风控中的多步状态机、嵌套 JSON 解析),硬要循环,优先级是:df.values > itertuples(index=False, name=None) > iterrows()。
-
df.values返回纯 NumPy 数组,无索引、无列名、无 dtype 检查,适合数值密集型计算,但你要自己记清第 0 列是啥 - 只读少数几列?用
zip(df['a'], df['b'], df['c']),比itertuples()更轻量,尤其当 DataFrame 列数超 50 时 - 数值计算为主 + 数据量大?把逻辑抽成函数,用
numba.jit编译,输入必须是np.ndarray
最后提醒一句:itertuples() 再快,也还是 Python 循环。当数据量超过 50 万行,且逻辑允许,转向 Polars 或 DuckDB 这类真正为行处理优化的引擎,比调优 itertuples() 参数实在得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











