遍历二维DataFrame所有单元格速度极慢,问题出在哪?
我从CSV读取DataFrame,按两列做pivot后尝试遍历每个单元格处理。实际代码包含复杂数值逻辑,但速度慢到离谱,于是写了个仅读取再赋值回原单元格的测试代码,排除处理逻辑的影响,聚焦访问/赋值的低效:
import pandas as pd from tqdm import tqdm def test_speed(): df = pd.read_csv('https://media.githubusercontent.com/media/datablist/sample-csv-files/main/files/customers/customers-100.csv') df = pd.pivot(df, index='Index', columns='Country') original = df.copy() inputs = df.columns.values # process each row at a time for row_index in tqdm(range(df.shape[0])): row_data = df.iloc[row_index] # get the row for input_idx in range(len(inputs)): # for each column label, get the value val = row_data[inputs[input_idx]] # just assign them back to that cell df.iloc[row_index, input_idx] = val print(f'Are they equal? {original.equals(df)}') if __name__ == '__main__': test_speed()
运行结果显示每秒仅处理22行:
100%|██████████| 100/100 [00:04<00:00, 22.63it/s] Are they equal? True
对比之下,用DictReader遍历CSV每秒能处理约5000行;如果在pivot前处理,速度能到每秒1600行。怀疑赋值时会重建索引,但我并没有修改pivot用的行/列,想找个既能保留pivot结构又能提升性能的方法。
解决方案:避免逐单元格操作,利用Pandas向量化特性
核心原因
Pandas的iloc逐单元格赋值会触发多次内部索引校验、数据结构更新,尤其是pivot后的多级索引DataFrame,每次赋值的额外开销会被大幅放大,导致整体速度暴跌。
优化方案1:转NumPy数组处理后转回
将pivot后的DataFrame数据转换为NumPy数组,在数组层面完成逐元素操作(NumPy的内存布局更高效,访问/赋值开销远低于Pandas单元格操作),处理完成后再重新封装回原DataFrame结构:
import pandas as pd from tqdm import tqdm def test_speed_optimized(): df = pd.read_csv('https://media.githubusercontent.com/media/datablist/sample-csv-files/main/files/customers/customers-100.csv') df = pd.pivot(df, index='Index', columns='Country') original = df.copy() # 转换为NumPy数组 arr = df.to_numpy() # 遍历数组处理(此处模拟原逻辑,实际替换为你的复杂数值处理) for row_idx in tqdm(range(arr.shape[0])): for col_idx in range(arr.shape[1]): val = arr[row_idx, col_idx] arr[row_idx, col_idx] = val # 将处理后的数组转回原DataFrame结构 df_processed = pd.DataFrame(arr, index=df.index, columns=df.columns) print(f'Are they equal? {original.equals(df_processed)}') if __name__ == '__main__': test_speed_optimized()
该方案可将速度提升至每秒数千行,是保留pivot结构前提下的高效选择。
优化方案2:使用Pandas向量化API替代遍历
如果你的复杂数值逻辑可以用Pandas向量化函数实现(比如applymap、transform,或结合NumPy向量化操作),完全不需要手动遍历:
# 示例:用applymap处理每个单元格,替换lambda为你的自定义处理函数 df_processed = df.applymap(lambda x: x)
向量化操作是Pandas的最优实践,内部采用C级循环,性能比Python级遍历高几个数量级。
优化方案3:用itertuples减少行访问开销(次选)
如果必须逐行处理,itertuples比iloc取行的效率更高——它返回元组而非Series,减少了对象创建的开销。注意建议先收集所有处理结果,最后一次性赋值,避免逐单元格修改:
results = [] for row in tqdm(df.itertuples(index=True), total=df.shape[0]): row_vals = [] # 多级列名需要拼接为属性名访问 for col in df.columns: col_attr = f"{col[0]}_{col[1]}" val = getattr(row, col_attr) # 此处替换为你的处理逻辑 row_vals.append(val) results.append(row_vals) # 一次性赋值回DataFrame df.loc[:] = results
内容的提问来源于stack exchange,提问作者Edy Bourne

