应优先使用 pct_change() 计算环比,它自动处理空值、对齐索引、保留 dtype 且性能更优;手动 shift() 易引发除零、错位及边界错误,仅在需非标准偏移(如二阶环比)时适用。

直接用 pct_change() 算环比,别自己手写 shift() 除法
绝大多数场景下,pct_change() 就是专为环比/同比设计的,它自动处理空值、对齐索引、保留原始 dtype,而手动用 shift() 做除法容易漏掉边界情况。比如你有一列月度销售额:df['sales'],直接写 df['sales'].pct_change() 就是标准环比(上月 vs 本月),第一行自动为 NaN —— 这是合理行为,不是 bug。
常见错误现象:
• 手动写 (df['sales'] - df['sales'].shift(1)) / df['sales'].shift(1),结果第一行变成 NaN,第二行却因分母为 0 或缺失值报 ZeroDivisionError 或 inf
• 忘记 shift() 后索引没变,但做除法时 pandas 会按索引对齐 —— 看似没问题,实则在时间不连续(如跳过周末)时悄悄错位
- 默认计算「上一期」变化率,即环比;加参数
periods=12可算同比(需确保索引是规则时间序列,否则可能跨年错配) - 内部已优化:比手写
shift()+ 四则运算快 10%~20%,尤其在大 DataFrame 上 - 注意:若索引含重复值,
pct_change()仍能运行,但shift()在 groupby 后可能失效 —— 这点很多人忽略
用 shift() 的唯一正当理由:要非标准偏移或复合逻辑
当你需要「上上月 vs 上月」这种二阶环比,或者「本月 vs 去年同月平均值」这类混合逻辑时,shift() 才不可替代。此时它不是替代 pct_change(),而是补足其能力边界。
使用场景举例:
• 计算滚动 3 个月同比均值变化:(df['sales'] - df['sales'].shift(12).rolling(3).mean()) / df['sales'].shift(12).rolling(3).mean()
• 处理非等距时间序列(如只记录了每周三数据),想严格按「往前推 4 行」而非「往前推 28 天」
立即学习“Python免费学习笔记(深入)”;
-
shift()返回的是原数据的位移副本,不做任何类型检查或空值抑制 —— 你要自己处理NaN和除零 - 参数
fill_value很少有用:设成 0 会导致后续除法爆炸;设成 1 又违背业务含义;不如留空让 pandas 自动标NaN - 性能提示:链式调用
.shift().rolling().mean()比先存中间变量慢,尤其在百万行以上数据中差异明显
pct_change() 的 period 参数不是万能的,时间索引必须对齐
写 df.set_index('date')['sales'].pct_change(periods=12) 并不等于「去年同月」—— 它只是机械地取当前行向上数第 12 行的值。如果某个月缺数据(比如 2023-02-29 不存在),那 2024-02 的同比就会和 2023-01 对齐,彻底错位。
错误现象:
• 用 periods=12 算月度同比,结果 2024-03 对应的是 2023-02(正确),但 2024-02 却对应 2023-01(错误)
• df.index.freq 是 None,说明 pandas 不认为这是规则序列,pct_change(periods=12) 就纯按行号算
- 真正安全的同比:先用
asfreq('MS')或resample('MS').first()补齐月度频率,再用pct_change(periods=12) - 若原始数据是日频,想算「去年同期日」,得用
df.shift(freq='12M')(注意是shift方法,不是shift()函数),再手动相减除 - 兼容性注意:
freq参数在 pandas 2.0+ 中对shift()支持更好,旧版本建议升级
空值和零值会让两种方法同时翻车,但处理方式不同
pct_change() 遇到前值为 0 时返回 inf 或 -inf,遇到前值为 NaN 时当前结果为 NaN;而手写 shift() 除法会直接抛 RuntimeWarning 或产出 inf,且无法统一控制。这才是线上出问题最多的地方。
- 统一兜底方案:用
np.where()包一层,例如np.where(df['sales'].shift(1) == 0, np.nan, df['sales'].pct_change()) - 更稳妥的做法是提前过滤或标记异常基期:比如定义「基期销售额
- 不要依赖
fillna(0):把NaN填成 0 再参与除法,会把「不可比」变成「零增长」,业务上完全失真
真正麻烦的从来不是函数怎么调,而是你有没有确认过「这个『上期』在业务上是否真的可比」—— 数据断点、统计口径变更、节假日错位,这些都比代码多一行少一行要命得多。


















