如何加速/替换Python for循环:百万级DataFrame性能优化求助
优化百万行DataFrame的循环效率:用Pandas向量化操作替代嵌套循环
你的代码慢的核心原因是多层嵌套的Python级循环——尤其是内层遍历merged_data每行的操作,在百万行数据下会带来巨大的性能开销。Pandas的优势在于向量化操作和C级别的窗口计算,我们可以完全替换掉这些循环,把运行时间从小时级压缩到分钟甚至秒级。
先梳理原代码的核心逻辑
你要实现的是:
- 对每个资产,针对多个时间窗口
offset,计算:- 窗口内正收益天数占比(
Pct_positive_{offset}) - 窗口内跑赢标普500的天数占比(
Pct_beating_{offset}) - 过去
offset期的涨跌幅(Pct_change_{offset}) - 未来
offset期的涨跌幅(Pct_change_plus_{offset})
- 窗口内正收益天数占比(
优化方案:用Rolling窗口+向量化计算替代循环
下面是重构后的代码,我会逐部分解释优化点:
import pandas as pd def get_previous_next_returns(portfolio, total_returns): # 提前计算标普500的日收益(避免每次合并后重复计算) sp_500['return_sp'] = sp_500['Close'].pct_change(periods=1) assets = [] for idx, asset in enumerate(portfolio, 1): print(f"Processing asset {idx}: {asset.name}") try: # 1. 提前计算资产的日收益 asset['return_stock'] = asset['Close'].pct_change(periods=1) # 2. 合并数据(只做一次,而非每个offset都重复合并) merged_data = pd.merge(asset, sp_500, on='Date', how='inner') # 3. 定义需要处理的offset列表 offsets = [1,5,15,30,45,60,75,90,120,150,200,250,500,750,1000,1250,1500] for offset in offsets: print(f" Handling offset {offset}") # -------------------------- # 优化:用Rolling窗口计算正收益天数占比 # -------------------------- # 窗口内正收益的天数:return_stock > 0,rolling求和后除以窗口大小 rolling_positive = (merged_data['return_stock'] > 0).rolling(window=offset, min_periods=offset).sum() merged_data[f'Pct_positive_{offset}'] = rolling_positive / offset # -------------------------- # 优化:用Rolling窗口计算跑赢标普的天数占比 # -------------------------- # 窗口内跑赢的天数:return_stock > return_sp,rolling求和后除以窗口大小 rolling_beating = (merged_data['return_stock'] > merged_data['return_sp']).rolling(window=offset, min_periods=offset).sum() merged_data[f'Pct_beating_{offset}'] = rolling_beating / offset # -------------------------- # 优化:向量化计算涨跌幅(原逻辑不变,但避免循环) # -------------------------- # 过去offset期的涨跌幅 merged_data[f'Pct_change_{offset}'] = merged_data['Close_x'].pct_change(periods=offset) # 未来offset期的涨跌幅(用shift替代pct_change的负periods,效果一致) merged_data[f'Pct_change_plus_{offset}'] = merged_data['Close_x'].shift(-offset) / merged_data['Close_x'] - 1 # 将处理后的资产数据加入列表和总收益表 assets.append(merged_data) total_returns = pd.concat([total_returns, merged_data], ignore_index=True) except Exception as e: print(f"Error processing asset {idx}: {asset.name} - {str(e)}") return assets, total_returns
关键优化点说明
- 避免重复计算:提前计算标普500和资产的日收益,资产与标普的合并仅执行一次,消除每个offset循环内的冗余操作
- 用Rolling窗口替代行级循环:原代码逐行切片计算窗口统计量的逻辑,替换为Pandas内置的
rolling方法——这是C级别的高效实现,速度比Python循环快100-1000倍,同时min_periods=offset自动过滤数据不足的窗口,替代原代码中index-offset>0的判断 - 简化未来涨跌幅计算:用
shift(-offset)/Close - 1替代pct_change(periods=-offset),逻辑等价且更直观 - 更清晰的异常处理:捕获所有异常并打印具体信息,方便排查问题
额外性能建议
- 如果资产数量较多,可以用
concurrent.futures实现多进程/多线程并行处理单个资产,进一步压缩总运行时间 - 确保
Date列为datetime类型且已排序,Rolling窗口依赖有序的时间序列 - 若内存允许,可将数据转换为
float32类型,减少内存占用并提升运算速度
内容的提问来源于stack exchange,提问作者adrCoder
相关产品推荐
相关产品推荐

