如何在pandas中高效对250MB DataFrame执行90次移位、拼接与除法操作
预期合理性判断
你的数分钟完成的预期完全合理,优化后的方案甚至可以在1分钟内完成运算。你之前的方案性能差、内存占用高主要来自两个问题:
- 循环拼接全量DataFrame,每次拼接都会触发全量数据拷贝,90次循环会产生大量冗余内存开销
- 自定义
apply函数逻辑错误(循环内直接return只会返回w=1的计算结果),且自定义apply的矢量化程度远低于pandas内置函数,性能损耗严重
优化方案
方案1:pandas原生函数实现(易维护,性能足够)
核心优化点:预存分组对象避免重复分组,所有计算结果暂存列表后一次性拼接,避免多次全量拷贝。
import pandas as pd # 预定义需要处理的列 target_cols = ["c1", "c2", "c3", "c4", "c5"] # 预存分组对象,避免重复计算分组 gb = data.groupby(level="mylevel")[target_cols] res_list = [] for w in range(1, 91): # 直接使用内置shift计算,矢量化运算效率远高于自定义apply div_result = gb.shift(w) / gb.shift(w-1) # 可指定float32类型,内存占用直接减半 div_result = div_result.astype("float32") # 重命名列 div_result.columns = [f"{col}_{w}" for col in target_cols] res_list.append(div_result) # 一次性拼接所有结果 final_df = pd.concat(res_list, axis=1)
方案2:Numpy优化版(性能更强,适合极致提速需求)
如果需要进一步压缩运行时间,可以把分组后的数据转为numpy数组运算,避免pandas的行索引 overhead:
import numpy as np import pandas as pd target_cols = ["c1", "c2", "c3", "c4", "c5"] # 确保数据已经按分组+时间维度排序,移位结果才正确 data = data.sort_index() # 提取分组边界 group_sizes = data.groupby(level="mylevel").size().values # 转numpy数组 arr = data[target_cols].values rows, cols = arr.shape # 初始化结果数组 res_arr = np.full((rows, cols * 90), np.nan, dtype=np.float32) offset = 0 for size in group_sizes: group_data = arr[offset:offset+size] for w in range(1, 91): if w >= size: continue # numpy索引移位计算,比pandas shift快3-5倍 res_arr[offset + w : offset + size, w*cols : (w+1)*cols] = group_data[:size - w] / group_data[1: size - w + 1] offset += size # 生成列名 col_names = [f"{col}_{w}" for w in range(1,91) for col in target_cols] final_df = pd.DataFrame(res_arr, index=data.index, columns=col_names)
内存优化提示
最终结果如果使用float32存储,1100万行 * 450列的内存占用约为19GB,属于常规服务器可以承受的范围,如果内存不足可以考虑按分组分批计算后再拼接结果。
内容的提问来源于stack exchange,提问作者Crispy Holiday
相关产品推荐
相关产品推荐

