如何在非平衡日期DataFrame中计算T-12至T-2的资产累积收益率
非平衡面板数据中计算指定区间的累积收益率
问题背景
现有如下结构的非平衡面板DataFrame,DATE日期不连续,RETURN列存在缺失值(NA):
| DATE | CODE | RETURN |
|---|---|---|
| 2000-01-01 | ASSET_1 | -0.02 |
| 2000-02-01 | ASSET_1 | 0.02 |
| 2000-03-01 | ASSET_1 | 0.01 |
| 2000-04-01 | ASSET_1 | 0.02 |
| 2000-05-01 | ASSET_1 | 0.03 |
| 2000-06-01 | ASSET_1 | -0.02 |
| 2000-07-01 | ASSET_1 | 0.05 |
| 2000-08-01 | ASSET_1 | -0.04 |
| 2000-09-01 | ASSET_1 | -0.02 |
| 2000-10-01 | ASSET_1 | 0 |
| 2000-11-01 | ASSET_1 | 0.05 |
| 2000-12-01 | ASSET_1 | 0.03 |
| 2000-03-01 | ASSET_2 | NA |
| 2000-04-01 | ASSET_2 | -0.02 |
| 2000-05-01 | ASSET_2 | NA |
| 2000-06-01 | ASSET_2 | -0.03 |
| 2000-07-01 | ASSET_2 | 0.05 |
| 2000-08-01 | ASSET_2 | 0.02 |
| 2000-09-01 | ASSET_2 | -0.03 |
| 2000-10-01 | ASSET_2 | 0.04 |
| 2000-11-01 | ASSET_2 | 0.04 |
| 2000-12-01 | ASSET_2 | 0.04 |
| 2001-01-01 | ASSET_2 | -0.03 |
需求说明
为每个资产计算T-12到T-2区间的累积收益率:
- 例如,针对2001-01-01的观测,计算区间为2000-01-01至2000-11-01的累积收益,公式为
(1+RETURN1)*(1+RETURN2)*...*(1+RETURNn) - 1 - 若区间内存在NA值,或观测期数不足11期,累积收益率设为NA
- 当前仅能通过手动创建多个
lag(RETURN)列实现,寻求更简便的方法
R 解决方案
使用dplyr分组排序后,结合slider包的滑动窗口函数,直接指定目标区间计算:
library(dplyr) library(slider) # 转换日期格式并按资产、日期排序 df <- df %>% mutate(DATE = as.Date(DATE)) %>% arrange(CODE, DATE) %>% group_by(CODE) %>% # 滑动窗口取T-12到T-2的11个观测,计算累积收益率 mutate(CUM_RET = slide_dbl( x = 1 + RETURN, .f = ~ifelse(all(!is.na(.x)) && length(.x) == 11, prod(.x) - 1, NA), .before = 11, # 窗口包含当前行前11个观测 .after = -1 # 排除当前行前1个观测(即T-1) )) %>% ungroup()
关键逻辑
arrange(CODE, DATE)确保每个资产的时间序列有序slide_dbl的.before=11和.after=-1精准锁定T-12到T-2的区间- 内置条件判断自动处理NA值和期数不足的情况
Python 解决方案
利用pandas的分组滚动计算,结合位移操作实现目标区间的累积收益:
import pandas as pd # 转换日期格式并排序 df['DATE'] = pd.to_datetime(df['DATE']) df = df.sort_values(['CODE', 'DATE']) # 计算1+RETURN,用于累积乘积 df['1+RET'] = 1 + df['RETURN'] # 分组后滚动计算11期累积收益,再位移对应到目标时间点 df = df.groupby('CODE').apply(lambda x: x.assign( CUM_RET = x['1+RET'].rolling(window=11, min_periods=11).apply(lambda y: y.prod() - 1, raw=True).shift(1) )).reset_index(drop=True) # 过滤窗口内存在NA的情况 df['CUM_RET'] = df.groupby('CODE').apply( lambda x: x['CUM_RET'].where(x['RETURN'].rolling(window=11, min_periods=11).apply(lambda y: all(pd.notna(y)), raw=True).shift(1), pd.NA) ).reset_index(drop=True)
关键逻辑
rolling(window=11)计算连续11期的累积乘积,shift(1)将结果对应到T期(原窗口为T-11到T-1,位移后对应T-12到T-2)- 第二次滚动判断窗口内是否存在NA,确保结果符合需求
方案优势
相比手动创建多个lag列,上述方法:
- 代码简洁,无需硬编码11个位移列
- 逻辑清晰,直接对应业务需求的区间范围
- 具备扩展性,若调整区间长度只需修改窗口参数即可
内容的提问来源于stack exchange,提问作者datgoaltho
相关产品推荐
相关产品推荐

