pct_change()计算环比需先按时间排序并补全缺失月份,否则结果失真;同比需确保数据严格月对齐,建议转datetime索引后对齐;前值为0或NaN会导致结果异常,可用shift()手动控制分母。

直接用 pct_change() 就能算,但结果不准多半是数据没排好序或缺月份
很多人一上来就写 df['环比'] = df['sales'].pct_change(),跑完发现第一行是 NaN、中间突然跳变、同比全空——不是函数错了,是输入不满足前提条件。pct_change() 本质就是对相邻行做 (当前值 - 前值) / 前值,它不认“时间”,只认“行顺序”。如果你的原始数据按 ID 或随机顺序排列,或者 2025 年 2 月数据在 1 月前面,那算出来的“环比”根本不是时间意义上的环比。
- 必须先按时间列(如
month或date)升序排序:df.sort_values('month', inplace=True) - 如果数据是月度汇总,但某个月缺失(比如没有 2024-07 的记录),
pct_change(1)会拿 6 月直接减 8 月,变成“跳月环比”,结果失真 - 真实业务中建议补全时间轴:用
pd.date_range()构造完整月份索引,再reindex()或merge()补NaN,再算变化率
pct_change(periods=1) 是环比,periods=12 是同比?小心频率陷阱
“同比=上一年同月”这个逻辑成立的前提是:你的数据是严格按月对齐的等距序列。如果用字符串 '202401'、'202402' 存月份,pct_change(12) 确实能算出 202501 对比 202401;但如果数据里混了 '202413' 这种非法值,或存在重复月份,periods=12 就只是机械地往前数 12 行,和“年”毫无关系。
- 更安全的做法:把月份转成
datetime类型(pd.to_datetime(df['month'], format='%Y%m')),再设为索引,用resample('M').sum()强制对齐 - 季度同比别硬套
periods=4:如果数据是季末值(如2024-03-31,2024-06-30),pct_change(4)才合理;如果是季初值(2024-01-01),就得用periods=4,但前提是索引已按时间排序且无缺失 -
freq参数仅对 DatetimeIndex 生效,普通列传了也无效;别指望靠它“自动识别年/月”
为什么有时 pct_change() 返回全是 NaN?三个常见断点
不是数据全空,而是计算链在某个环节断了。最典型的是:前值为 0、前值为 NaN、或 fill_method 配置反直觉。
- 前值为 0 → 结果是
inf或-inf,pandas 默认显示为NaN(实际是float('inf')),可用np.isfinite()检查 - 前值是
NaN→ 当前值无论多少,结果都是NaN;默认fill_method='pad'会用上一个有效值填充,但若开头连续多个NaN,填充不上就会一直NaN - 用了
limit但没配fill_method→limit只在fill_method启用时起作用,单独设limit=1不会限制填充行为
替代方案:shift() + 手动除法,可控性更强但要自己处理边界
当你要精确控制分母、过滤零值、或加条件(比如“只对非零销售额计算环比”),pct_change() 就不够用了。这时候用 shift() 显式取前值,再手写公式,反而更透明。
- 环比:
df['环比'] = (df['sales'] - df['sales'].shift(1)) / df['sales'].shift(1) - 可加保护:
prev = df['sales'].shift(1); df['环比'] = np.where(prev != 0, (df['sales'] - prev) / prev, np.nan) - 同比同理:把
shift(1)换成shift(12),但注意——这依然要求数据已按月对齐,否则 12 行 ≠ 12 个月
真正麻烦的从来不是函数怎么调,而是你手里的数据有没有“时间语义”。算出来数字漂亮,但对应不上业务定义,那再快的代码也是白跑。










