pct_change()默认计算环比,需先排序、处理缺失值,同比需指定periods;shift()更灵活可控,但须注意分母为0、时间对齐及多列广播问题。

用 pct_change() 计算百分比变化时,注意默认是环比(上一期)
pct_change() 是最直接的百分比变化计算方法,但它默认按行方向(axis=0)对每一列向上找前一个非空值做差比,也就是「环比」。如果你的数据是按时间升序排列的(比如 2023-01, 2023-02, ...),那结果就是「本月 vs 上月」。
常见误用点:
- 没排序就调用 —— 若索引乱序,
pct_change()仍按物理顺序算,不是按时间逻辑;务必先用df.sort_index()或df.sort_values('date') - 忽略缺失值影响 ——
pct_change(n=1)遇到NaN会跳过,导致错位;建议先用df.fillna(method='ffill')或明确处理空值 - 跨年同比需要手动指定周期 —— 比如月度数据同比要算「当前月 vs 去年同月」,得用
pct_change(periods=12)(非默认)
示例:
df['sales_yoy'] = df['sales'].pct_change(periods=12) # 月度同比
df['revenue_mom'] = df['revenue'].pct_change() # 默认环比(上月)
用 shift() 手动构造同比/环比更可控
当 pct_change() 的 periods 不够灵活(比如周数据同比要 periods=52,但某年有53周),或你想同时保留原始值、变化量、变化率时,shift() 更透明可靠。
立即学习“Python免费学习笔记(深入)”;
核心思路:用 shift() 拉出基准列,再用原列减去它、除以它。
df['profit_qoq'] = (df['profit'] - df['profit'].shift(1)) / df['profit'].shift(1)- 同比可写成
df['profit_yoy'] = (df['profit'] - df['profit'].shift(4)) / df['profit'].shift(4)(季度数据) - 避免分母为 0:加
.replace({0: np.nan})或用np.where(df['base'] != 0, ..., np.nan) - 支持多级索引对齐:若按
['region', 'date']索引,shift()默认在最内层生效,无需额外分组
带时间频率的同比环比必须先用 asfreq() 或 resample() 对齐
真实业务数据常有缺失日期(比如节假日无销售)、或采样不均(有些月有28天,有些31天)。直接用 shift(1) 或 pct_change() 会把「2023-02-28」和「2023-03-01」当成相邻,造成错误环比。
正确做法是先将数据规整为固定频率:
- 月度分析:用
df.asfreq('M')补齐月末,缺失值自动填NaN;再算pct_change() - 周度分析:用
df.resample('W').sum()(或.mean())聚合后对齐,再shift(1) - 注意
asfreq()不插值,只对齐;若需填充,跟.fillna(method='ffill')组合使用
错误示范:
# 错!原始索引是不规则日期串,直接 shift 可能跨周<br>df['value'].shift(1)
多列批量计算同比环比时,小心 axis 和广播行为
如果对整个 DataFrame 调用 pct_change(),它会对每列独立计算,没问题;但用 shift() 批量操作时容易出错。
-
df.shift(1)是对整表向下平移,列间不干扰 —— 安全 - 但写
(df - df.shift(1)) / df.shift(1)时,若某列含NaN,整行结果可能全NaN(Pandas 默认逐元素运算) - 推荐按列循环或用
apply()控制粒度:df_pct = df.select_dtypes(include='number').apply(lambda s: s.pct_change())
- 避免对非数值列(如
category,object)直接算 —— 会报TypeError: unsupported operand type(s)
真正难的不是公式,而是确认你的「期」到底对应哪一天、是否被业务定义为自然月/财年/滚动N个月 —— 这些必须靠上游数据清洗阶段对齐,算法层没法自动猜。


















