使用df.iterrows()创建类实例运行极慢的优化方案咨询
性能瓶颈核心原因
df.iterrows()是pandas所有遍历方式中效率最低的实现,逐行生成Series对象的开销占总耗时的30%以上- 重复计算问题严重:同一标的同一交易日的分钟线筛选、时间格式转换、盘前/盘后时段切分、指标计算,会在每遍历到当日日K行时重复执行,甚至同一个盘前时段数据会被多次调用
GET_TIME_PERIOD_DATA重复计算 - 逐行生成单条记录的小DataFrame再循环append、最后concat的写法,会产生大量无意义的内存拷贝操作,额外拖慢速度
- 所有指标计算均为单值串行计算,完全没有用到pandas/numpy的向量化批量计算能力
具体优化方案
1. 提前完成分钟线全量预处理,彻底消除重复计算
把原来放在SESSION_DATA类里的分钟线筛选、时段切分、指标计算逻辑全部挪到外层,读入单只标的分钟线后一次性处理完所有交易日的指标,存为键为date的查询字典,后续直接取值即可,不需要重复计算。示例代码:
from datetime import time import numpy as np import pandas as pd def preprocess_minute(df_minute): # 一次性完成全量时间格式转换 df_minute['time'] = pd.to_datetime(df_minute['time']).dt.time # 提前固定时段判断条件,避免循环内重复判断 pre_market_cond = (df_minute['time'] >= time(4, 0)) & (df_minute['time'] < time(9, 30)) after_hours_cond = (df_minute['time'] >= time(16, 0)) & (df_minute['time'] < time(20, 0)) day_metrics = {} # 按日期分组批量计算所有当日分钟指标 for trade_date, day_df in df_minute.groupby('date'): # 切分盘前、盘后数据 pm_df = day_df[pre_market_cond] ah_df = day_df[after_hours_cond] # 批量计算盘前指标 if len(pm_df) == 0: pm_high = pd.NA pm_high_time = pd.NA pm_200ema_hit = False else: pm_high = pm_df['high'].max() pm_high_time = pm_df.loc[pm_df['high'].idxmax(), 'time'] # 向量化计算200EMA,判断是否触及 pm_df['ema200'] = pm_df['close'].ewm(span=200, adjust=False).mean() pm_200ema_hit = (pm_df['low'] <= pm_df['ema200']).any() # 盘后指标 ah_high = ah_df['high'].max() if len(ah_df) else pd.NA day_metrics[trade_date] = { 'PM hi': pm_high, 'PM hi time': pm_high_time, 'PM 200 ema hit': pm_200ema_hit, 'ah_high': ah_high } return day_metrics
仅这一步优化,通常就能把整体运行速度提升10倍以上。
2. 放弃逐行类实例化+iterrows逻辑,改用表关联+向量化计算
最终输出的字段一半来自日K原始数据,一半来自提前计算好的分钟指标,完全不需要逐行循环生成结果,直接做表关联后批量计算衍生指标即可:
for fname in glob.glob(folder)[:13]: ticker = Path(fname).stem df_daily = pd.read_csv(fname) df_daily['ticker'] = ticker # 读入分钟线并一次性预处理 df_min = pd.read_csv(f'intraday_bars_gapped_new/{ticker}.csv') min_metrics = preprocess_minute(df_min) # 把分钟指标转成DataFrame和日K表关联 df_min_metrics = pd.DataFrame.from_dict(min_metrics, orient='index').reset_index().rename(columns={'index':'date'}) df_res = df_daily.merge(df_min_metrics, on='date', how='left') # 向量化计算所有剩余衍生指标,无循环 df_res['Spike'] = (df_res['high'] - df_res['open']).abs() df_res['after_hours_runner'] = np.where(df_res['ah_high'] > df_res['previous close'], True, False) # 对应原有new_gapper、pop_over_10等逻辑,全部用np.where/pandas内置向量化方法实现 df_res['Pop over 10%'] = np.where(df_res['Spike']/df_res['open'] >= 0.1, True, False) df_intraday_analysis.append(df_res) # 最后一次性合并所有结果 final_df = pd.concat(df_intraday_analysis, ignore_index=True) final_df.to_csv('mikeys-spreadsheet2222.csv', index=False)
这一步替换后,基本可以消除90%以上的循环开销,原来数小时的计算量通常可以压缩到几十秒级别。
3. 附加提速技巧
- 如果暂时不想完全重构逻辑,至少把
iterrows()替换成itertuples(),后者遍历速度是前者的50~100倍,且取值开销更低 - 读CSV时手动指定字段
dtype,比如把价格字段设为float32、成交量设为int32,可以减少一半以上内存占用,同时提升IO和计算速度 - 如果数据量特别大,可以替换为Polars库做数据处理,其IO和向量化计算速度是pandas的5~20倍,API逻辑和pandas高度相似,迁移成本很低
- 避免在循环中反复执行
pd.concat,如果必须用循环收集结果,优先存为字典列表,最后一次性转为DataFrame,减少内存拷贝次数
内容的提问来源于stack exchange,提问作者a7dc
相关产品推荐
相关产品推荐

