
本文介绍如何使用 Pandas 的 shift() 配合重索引与前向填充,高效计算每个时间点相对于“至少 N 个工作日之前”值的百分比变化,避免显式循环,兼顾稀疏日期与非等距时间序列。
本文介绍如何使用 pandas 的 `shift()` 配合重索引与前向填充,高效计算每个时间点相对于“至少 n 个工作日之前”值的百分比变化,避免显式循环,兼顾稀疏日期与非等距时间序列。
在时间序列分析中,常需计算当前值相对于“至少 N 天前”最近有效观测值的变化率(即 (current - ref) / ref),尤其当原始数据日期不连续(如仅含交易日、周末缺失)时,直接使用 shift(2) 会跨行而非跨日,导致逻辑错误。例如,2024-04-05 的“2天前”应是 2024-04-03,但若该日无数据,则需回溯至最近存在的更早日期(如 2024-04-01)。
核心思路是:将原始时间序列对齐到完整日历(每日一行),用前向填充(ffill)补全缺失值,再在完整日历上执行固定步长 shift(n),最后映射回原始索引。该方法天然支持任意 n(如 n=2 表示“至少2天前”),且自动处理日期空缺。
以下为完整实现(以 n=2 为例):
import pandas as pd
# 示例数据(注意:日期不连续)
df = pd.DataFrame({
'x': [1, 2, 3, 4, 5, 6]
}, index=pd.to_datetime(['2024-04-01', '2024-04-05', '2024-04-06', '2024-04-07', '2024-04-08', '2024-04-09']))
n = 2
df = df.assign(
change=df['x']
.reindex(pd.date_range(df.index.min(), df.index.max(), freq='D')) # 对齐完整日历(每日)
.ffill() # 前向填充缺失值
.pipe(lambda s: (s - s.shift(n)) / s.shift(n)) # 计算变化率:(current - n_days_ago) / n_days_ago
.loc[df.index] # 只取原始存在的日期行
)
输出结果:
x change 2024-04-01 1 NaN 2024-04-05 2 1.000000 2024-04-06 3 2.000000 2024-04-07 4 1.000000 2024-04-08 5 0.666667 2024-04-09 6 0.500000
✅ 关键说明:
- reindex(..., freq='D') 构建每日索引,确保 shift(2) 真正对应“2个日历日之前”;
- ffill() 使缺失日继承最近的有效值(如 2024-04-02 ~ 2024-04-04 均填 1),从而 2024-04-05 的 shift(2) 指向 2024-04-03 → 值为 1;
- pipe() 链式调用提升可读性,(s - s.shift(n)) / s.shift(n) 即标准相对变化公式;
- 最终 .loc[df.index] 过滤掉补全的中间行,只保留原始数据点。
⚠️ 注意事项:
- 若首 n 个日期在完整日历中无足够前置日(如 2024-04-01 的 shift(2) 会越界),结果为 NaN,符合预期;
- 对于高频数据(如分钟级),请将 freq 改为 'T' 或 'H' 并确保时间范围合理;
- 若业务要求“至少 N 个交易日”(跳过周末/节假日),需预先构建自定义日期范围(如 pd.bdate_range)替代 date_range。
此方案兼具简洁性、可扩展性与鲁棒性,是处理非等距时间序列相对变化的推荐实践。











