如何优化Pandas按日期累计计算持股数量的代码实现?
问题描述
我有2个DataFrame:
df_hist:存储股票价值的每日数据df_buy_data:存储股票买入日期的相关数据
需求是基于df_buy_data按日期计算累计持股数量,将对应持股数据匹配到df_hist的每一行中。目前通过遍历DataFrame的方式实现了需求,但认为实现方式不够优雅,原实现代码如下:
import pandas as pd hist_data={'Date':['2022-01-01','2022-01-02','2022-01-03','2022-01-04'],'Value':[23,22,21,24]} df_hist=pd.DataFrame(hist_data) buy_data={'Date':['2022-01-01','2022-01-04'],'Ticker': ['Index1', 'Index1'], 'NumberOfShares':[15,29]} df_buy_data = pd.DataFrame(buy_data) for i, historical_row in df_hist.iterrows(): ticker_count = df_buy_data.loc[(df_buy_data['Date'] <= historical_row['Date'])]\ .groupby('Ticker').sum()['NumberOfShares'] if(len(ticker_count)>0): df_hist.at[i,'Index1_NumberOfShares'] = ticker_count.item() else: df_hist.at[i, 'Index1_NumberOfShares'] = 0 df_hist
优化方案
原写法用iterrows()做Python层逐行循环,没有用到pandas底层的C级优化,数据量稍大时性能会非常差。这类时间序列累计匹配的场景,用pandas内置的向量化API就能高效实现,代码更简洁,运行速度比循环高2~3个数量级。
单标的实现(适配当前示例)
如果只需要处理单个股票标的,直接用pd.merge_asof即可,这个API就是专门为“匹配小于等于当前时间点的最近记录”这类场景设计的:
# 先将日期列转为datetime类型,避免字符串比较出现顺序错误 df_hist['Date'] = pd.to_datetime(df_hist['Date']) df_buy_data['Date'] = pd.to_datetime(df_buy_data['Date']) # 按日期聚合买入量,计算累计持股 buy_cum = (df_buy_data.groupby('Date', as_index=False)['NumberOfShares'].sum() .sort_values('Date') .assign(cum_shares = lambda x: x['NumberOfShares'].cumsum()) ) # 按日期做向后匹配合并,自动对齐每个历史日期之前的累计持股 df_hist = pd.merge_asof( df_hist.sort_values('Date'), buy_cum[['Date', 'cum_shares']], on='Date', direction='backward' ).rename(columns={'cum_shares':'Index1_NumberOfShares'}) # 无买入记录的日期持股数填充为0 df_hist['Index1_NumberOfShares'] = df_hist['Index1_NumberOfShares'].fillna(0).astype(int)
运行结果和原循环逻辑完全一致:
| Date | Value | Index1_NumberOfShares |
|---|---|---|
| 2022-01-01 | 23 | 15 |
| 2022-01-02 | 22 | 15 |
| 2022-01-03 | 21 | 15 |
| 2022-01-04 | 24 | 44 |
多标的通用实现
如果后续df_buy_data会包含多个不同Ticker的买入记录,可以用透视+重采样的方式一次性计算所有标的的累计持股,不需要修改循环逻辑:
# 日期格式转换 df_hist['Date'] = pd.to_datetime(df_hist['Date']) df_buy_data['Date'] = pd.to_datetime(df_buy_data['Date']) # 按日期、标的聚合买入量,转为日期为索引、标的为列的宽表 buy_pivot = (df_buy_data.pivot_table( index='Date', columns='Ticker', values='NumberOfShares', aggfunc='sum' ).sort_index()) # 对齐到历史表的所有日期,计算累计值后前向填充空缺 buy_cum_all = buy_pivot.reindex(df_hist['Date']).cumsum().ffill().fillna(0).astype(int) # 统一列名后缀,合并回历史数据表 buy_cum_all.columns = [f'{col}_NumberOfShares' for col in buy_cum_all.columns] df_hist = pd.concat([df_hist.set_index('Date'), buy_cum_all], axis=1).reset_index()
提示:所有涉及日期比较、计算的逻辑,都要先把日期列转为
datetime64类型,不要直接用字符串比较,否则会出现类似'2022-1-10' < '2022-01-02'的判断错误。
内容的提问来源于stack exchange,提问作者Max Petzelbauer
相关产品推荐
相关产品推荐

