pandas groupby标记30天内待拆股 赋值失效问题排查
问题说明
- 数据集为多只股票近5年收盘价数据,通过
SecuritiesCode字段区分不同股票,AdjustmentFactor != 1代表对应日期发生股票拆分/反向拆分,数据集样例结构如下:
- 目标:新增
pending_stocksplit列,标记单只股票当前日期之后30天内是否存在即将发生的拆分事件。 - 初始实现代码运行后DataFrame未发生任何修改,初始代码如下:
def pending_stocksplit(df): stock_split_dates = df[df['AdjustmentFactor'] != 1]['Date'].tolist() for i in stock_split_dates: df[(df['Date'] >= i - timedelta(days=30)) & (df['Date'] <= i)]['pending_stocksplit'] = 1 return df df = df.groupby('SecuritiesCode').apply(pending_stocksplit)
核心问题点
代码完全不生效的核心原因是链式索引赋值失效,这是Pandas最常见的新手坑:
- 写法
df[筛选条件]['pending_stocksplit'] = 1属于连续两次索引操作(先选行、再选列),第一次筛选行返回的是Pandas生成的临时副本,赋值操作实际作用在这个临时副本上,根本没有落地到原始传入的DataFrame,赋值结束后临时副本被回收,原数据自然没有任何变化。Pandas通常会抛出SettingWithCopyWarning提示这个问题,但多数场景下警告容易被忽略。
除此之外代码还有两个潜在问题:
- 没有提前初始化
pending_stocksplit列,哪怕修正了赋值方式,给不存在的列做切片赋值也容易触发异常。 - 用自然日
timedelta(days=30)计算时间窗口,会把周末、节假日等非交易日算入间隔,实际覆盖的交易日数量不足30个,和股票场景的业务逻辑有偏差;另外如果需求是「之后30天内」,拆分事件当天不应该算「即将发生」,条件里的Date <= i需要调整为Date < i。
修正代码
首先确保日期列是标准时间格式,再用.loc做原生赋值避免链式索引,自然日版本的实现如下:
from datetime import timedelta import pandas as pd # 先统一日期格式 df['Date'] = pd.to_datetime(df['Date']) def pending_stocksplit(group): # 复制分组避免修改原数据切片,初始化标记列默认值为0 group = group.copy() group['pending_stocksplit'] = 0 split_dates = group.loc[group['AdjustmentFactor'] != 1, 'Date'].tolist() for split_day in split_dates: # 构造筛选掩码,用.loc直接在原数据上赋值 mask = (group['Date'] >= split_day - timedelta(days=30)) & (group['Date'] < split_day) group.loc[mask, 'pending_stocksplit'] = 1 return group # group_keys=False避免分组键变成额外索引层级 df = df.groupby('SecuritiesCode', group_keys=False).apply(pending_stocksplit)
如果要按30个交易日(而非30个自然日)做判断,推荐用逆序滚动窗口实现,不需要写循环,运行效率高很多,适合Kaggle竞赛的大体量数据集:
def pending_stocksplit_tradeday(group): group = group.sort_values('Date').copy() # 标记拆分日 group['_is_split'] = (group['AdjustmentFactor'] != 1).astype(int) # 逆序滚动30个交易日窗口,排除当天的拆分事件,只要窗口内有拆分就标记为1 group['pending_stocksplit'] = ( group['_is_split'] .shift(-1) # 错开当天,只看之后的日期 .rolling(30, min_periods=1) .max() .fillna(0) .astype(int) ) return group.drop(columns=['_is_split']) df = df.groupby('SecuritiesCode', group_keys=False).apply(pending_stocksplit_tradeday)
内容的提问来源于stack exchange,提问作者wanderingcatto
相关产品推荐
相关产品推荐

