iterrows() 性能差因每次返回开销大的 series,而 itertuples() 返回轻量命名元组,实测快 5–10 倍;应优先向量化,避免遍历,必要时预分配结果再批量赋值。

为什么 iterrows() 和 itertuples() 性能差别这么大
因为 iterrows() 每次返回一个 Series,而 Series 构造开销大、类型推断慢、索引对齐成本高;itertuples() 直接返回命名元组(namedtuple),几乎不拷贝数据,也不做 dtype 转换。实测 10 万行 DataFrame 上,itertuples() 通常比 iterrows() 快 5–10 倍。
注意:itertuples() 默认把索引作为第一个字段(名字是 Index),如果不需要索引,加参数 index=False;如果列名不合法(如含空格、以数字开头),会自动重命名为 _0、_1 等——这点容易导致后续代码出错但不报异常。
- 用
itertuples(index=False)避免索引字段干扰逻辑 - 列名含特殊字符时,优先改列名(
df.columns = [...]),而不是依赖下划线命名 - 如果必须用索引且列名合法,用
itertuples(name='Row')可读性更好
什么时候该放弃遍历,直接用向量化
90% 的“需要按行处理”的场景,其实根本不用遍历——比如条件赋值、字符串拼接、数值计算,numpy 或 pandas 原生方法都更快更稳。
例如:想给每行生成 full_name = first_name + ' ' + last_name,写 df.apply(lambda x: x['first_name'] + ' ' + x['last_name'], axis=1) 是错的;正确做法是 df['first_name'] + ' ' + df['last_name'],速度差一个数量级。
- 字符串操作优先用
.str访问器(df['col'].str.upper()) - 数值条件判断用
np.where()或布尔索引(df.loc[df['x'] > 0, 'y'] = 1) - 复杂逻辑若真无法向量化,先用
numba.jit编译纯 Python 函数,再配合df.values数组传入
apply(axis=1) 的隐藏陷阱
apply(func, axis=1) 表面像“向量化”,实际仍是逐行调用 Python 函数,性能接近 iterrows(),还多一层封装开销。更糟的是,它会尝试推断返回值类型,可能触发隐式 object dtype,后续计算变慢甚至出错。
常见错误现象:apply 后发现新列是 object 类型,但原意是 float64;或者返回 list 导致整个列变成嵌套结构,len(df['col'].iloc[0]) 报错。
- 务必用
result_type='expand'或'reduce'显式控制返回结构 - 强制指定输出 dtype:用
astype()链式调用,或在函数里明确返回np.float64(x) - 调试时加
print(type(row), row.dtype)到函数里,确认输入是否被意外转成object
真要遍历且需修改原 DataFrame 时怎么办
遍历本身不修改 df,所有“边遍历边 df.loc[i, 'col'] = ...”的写法,在大表上极其慢(索引查找 + 写入锁),且可能触发 SettingWithCopyWarning。
正确路径只有一条:先收集结果(列表或数组),再一次性写回。例如计算每行得分后更新一列:
results = []
for row in df.itertuples(index=False):
score = row.math * 0.4 + row.chinese * 0.6
results.append(score)
df['score'] = results # 一次性赋值,快且安全
- 避免在循环里拼接
pd.DataFrame或反复df.append()(已弃用且极慢) - 如果结果是多列,用
zip(*results)拆包,或直接构造np.array(results)后赋值 - 内存敏感场景,用
np.empty(len(df), dtype=np.float64)预分配数组,再索引赋值
真正瓶颈往往不在“怎么遍历”,而在“为什么非得遍历”。检查列类型是否统一、缺失值是否已处理、逻辑能否拆解为多个向量化步骤——这些比选哪个遍历函数影响更大。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











