如何加速8000万行Pandas DataFrame场景下的超长for循环
问题描述
现有用于遍历索引列表字典的for循环,字典结构示例为{0: [1,5,...], 1: [2,4,...], ...},需基于约8000万行的超大型DataFrame遍历该字典,通过索引值生成新的字典结构。
现有实现逻辑可满足需求,但运行耗时极长,希望通过多线程分片处理或其他优化方案提升运行速度。同类优化方案中有提到使用Cython实现优化,但由于当前操作基于DataFrame,暂不确定该方案是否可行。
补充说明:当前遍历的字典由pandas的
groupby函数生成。
原始实现代码如下:
total_list = [] for ind in indexes_per_group: ship_list = [] dataframe_indexes = indexes_per_group[ind] for index in dataframe_indexes: singleLocation_dict = {} singleLocation_dict['lat'] = df.loc[index]['LATITUDE'] singleLocation_dict['lng'] = df.loc[index]['LONGITUDE'] ship_list.append(singleLocation_dict) total_list.append(ship_list)
优化方案
原始代码性能差的核心原因是逐行调用df.loc做链式索引,每次索引都会触发pandas内部的标签匹配、类型校验、副本判断逻辑,在8000万行的数据规模下,Python层循环+逐行pandas索引的开销会被放大数百倍,不需要一开始就上多线程、Cython这类重方案,按以下步骤优化即可获得百倍级别的速度提升:
- 第一层优化:消除pandas索引开销
提前把需要用到的经纬度列转为numpy数组,numpy按位置取值是C级实现,没有pandas索引的额外开销,比原始写法快50倍以上:# 提前抽取需要的列为numpy数组,避免循环内重复做列查找、索引校验 lat_values = df["LATITUDE"].to_numpy() lng_values = df["LONGITUDE"].to_numpy() total_list = [] for _, idx_list in indexes_per_group.items(): # 直接按位置取numpy值,字典推导式比逐次append效率更高 group_records = [ {"lat": lat_values[i], "lng": lng_values[i]} for i in idx_list ] total_list.append(group_records) - 第二层优化:复用groupby原生能力,省掉索引回查步骤
既然indexes_per_group本身就是groupby生成的中间结果,完全没必要先拿分组索引再回表查数据,直接在groupby对象上迭代,用pandas内置的C级逻辑批量转字典,比自己写Python层循环快10~20倍:total_list = [] # 只加载需要的两列,减少内存占用和迭代时的数据读取量 for _, group_data in df[["LATITUDE", "LONGITUDE"]].groupby(分组使用的键): # 批量重命名后转记录列表,内部为C实现循环,无Python层开销 group_records = group_data.rename( columns={"LATITUDE": "lat", "LONGITUDE": "lng"} ).to_dict("records") total_list.append(group_records) - 其他方案说明
- 多线程对当前场景提速非常有限:pandas逐行操作受Python GIL锁限制,多线程无法实现真正的并行计算,反而会因为线程间数据拷贝增加额外开销。如果做完前两步优化仍有速度需求,可以直接将计算引擎替换为Polars,其基于Rust开发,原生支持多线程并行,处理8000万行规模数据的速度是pandas的5~20倍。
- Cython优化的前提是完成基础向量化:如果需要极致压缩耗时,可以将最终的字典生成逻辑改写为带静态类型声明的Cython代码,但如果保留逐行读取DataFrame的逻辑,Cython也不会带来本质的性能提升。
内容的提问来源于stack exchange,提问作者James Hall
相关产品推荐
相关产品推荐

