数据无法向量化时pandas iterrows()的提速替代方案咨询
你最开始的代码耗时呈指数级增长,本质是写了O(n²)复杂度的双重遍历:外层用iterrows()逐行迭代的同时,内层又调用了一次df.apply(..., axis=1)做全表逐行计算,数据量每翻10倍,计算量会涨100倍,再叠加上pandas逐行操作的固有开销,耗时感知会非常明显。就算是你更新后的单轮iterrows()写法,本身也存在大量不必要的性能损耗,远没有达到最优效率。
另外明确说明:手动把数据拆成更小的分组对单线程计算没有任何提速效果,反而会增加拆分、拼接的额外开销,只有做并行计算的时候才需要按CPU核心数做任务分片。
最高优先级:绕开pandas对象做原生列表循环
这是纯Python循环场景下速度最快的写法,比你现在的iterrows()写法快10~50倍。核心思路是提前把需要用到的列转成Python原生列表,循环过程完全不碰pandas对象的索引、属性访问逻辑,所有计算结果存在普通列表里,最后一次性赋值给DataFrame,避免逐行写表的开销:# 提前提取需要的字段为原生列表 col1_vals = df["col1"].tolist() col2_vals = df["col2"].tolist() # 其他需要传入some_func的字段同理提前提取 result = [] for c1, c2 in zip(col1_vals, col2_vals): result.append(some_func(c1, c2)) # 一次性写入新列 df["new_col"] = result次选:用
itertuples()替代iterrows()
如果不想提前拆列表,直接把迭代方法换成itertuples()就能获得10~20倍的提速。itertuples()返回的是轻量命名元组,不会像iterrows()那样为每一行构造新的Series对象、做重复类型推断,访问字段值的开销极低:result = [] # index=True会保留行索引,方便需要定位行的场景 for row in df.itertuples(index=True): res = some_func(row.col1, row.col2) # 直接用列名当属性访问即可 result.append(res) df["new_col"] = result复杂计算场景:用并行apply替代单线程循环
如果some_func()是计算密集型逻辑(比如涉及复杂数值计算、字符串处理),可以用并行计算库把逐行计算任务分发到多个CPU核心跑,比单线程循环再快数倍,写法不需要大改:# 以swifter为例,会自动判断是否可以向量化,无法向量化时自动启用并行计算 import swifter df["new_col"] = df.swifter.apply(lambda x: some_func(x["col1"], x["col2"]), axis=1)注意用并行计算时,
some_func()必须是纯函数,不依赖外部可变状态,否则会出现计算结果错误。长期优化方向:尽量用向量化操作替代循环
如果后续可以把some_func()的逻辑拆成支持numpy数组运算的片段,直接用pandas/numpy原生向量化操作实现,速度会比纯Python循环快100~1000倍,是pandas数据处理的最优方案。
- 绝对不要在逐行循环内部嵌套全表级别的
apply、iterrows操作,这是导致你最初代码耗时指数级上涨的核心错误。 - 尽量不要在循环内部用
df.loc[]逐行赋值,逐行修改DataFrame的开销远大于最后一次性整列赋值。 - 非必要不要用
iterrows(),这个API本身的固有开销在数据量大于1万行之后就会变得非常明显。
内容的提问来源于stack exchange,提问作者loamoza

