本节摘要:季节性调整是把季节性从数据中"剥掉",让调整后序列能反映真实的同比变化。X-13ARIMA-SEATS 是美国人口普查局 / 欧统局联合开发的事实标准,被各国统计局广泛使用。本节给出它的核心思想、Python 接口(statsmodels)以及一份对比"调整前/调整后"销量序列的完整案例。
阅读完本节,你应当能够:
本节把季节性"剥掉"用于下游分析。读完后你能解释各国统计局公布的"CPI 同比"是怎么来的,以及为什么业务上的同比分析要先做季节性调整。
假设某电商今年 8 月销量 1.2 亿元,去年 8 月 1 亿元。表面看增长 20%。但 8 月本来就有"暑期消费高峰"——8 月销量比 6 月高 25% 是季节性常态。如果不剥掉季节性,你说"今年 8 月比去年 8 月增长 20%",但其中多少是"今年真的增长",多少是"季节性模式没变"?
季节性调整后,调整后序列剔除了季节性"指纹",剩下的只是"今年 8 月相对去年 8 月的真实变化"——这才是同比分析真正想要的信号。各国统计局每月公布的"CPI 同比"、失业率、零售销售数据都做了季节性调整。
X-13ARIMA-SEATS 是一套由美国人口普查局和欧统局联合开发、用于季节性调整的工业级方法。它的工作流大致是:
X-13 的优势:
import pandas as pd import numpy as np from statsmodels.tsa.x13 import x13_arima_analysis # 构造月度销量数据:趋势 + 12 月季节性 + 噪声 rng = np.random.default_rng(5) n_months = 60 monthly_seasonal = 20 * np.sin(2 * np.pi * np.arange(n_months) / 12) trend = np.linspace(100, 300, n_months) y = trend + monthly_seasonal + rng.normal(0, 5, n_months) y = pd.Series(y, index=pd.date_range("2019-01-01", periods=n_months, freq="MS")) # 跑 X-13 调整(需要系统安装 X-13ARIMA-SEATS 程序) result = x13_arima_analysis(y, x12path="x13as") # x12path 指向 X-13 可执行文件 adjusted = result.seasadj # 季节性调整后序列 trend_component = result.trend irregular = result.irregular
result.seasadj 就是把季节性剥掉后的序列。把原序列和调整后序列画在一张图上:
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(y.index, y.values, label="原序列") ax.plot(adjusted.index, adjusted.values, label="季节性调整后") ax.legend() plt.tight_layout()
调整后序列:
工程上也有人用简单移动平均(如 12 月中心化 MA)做季节性调整。X-13 比简单 MA 强在三个地方:
| 维度 | 简单 MA | X-13 |
|---|---|---|
| 交易日 / 移动节假日 | 忽略 | 显式建模 |
| 季节性变化 | 假设固定 | 允许缓慢变化 |
| 残差诊断 | 无 | 自带 M 检验、谱图、稳定性测试 |
| 工程门槛 | 极低 | 需要安装 X-13ARIMA-SEATS 程序 |
如果你的数据有强日历效应(财务月末、交易日差异、移动节假日),X-13 是更稳的选择。否则简单 MA 也够用。
⚠️ 常见坑:别在原序列上同时做趋势建模和季节性调整。如果你要做未来 6 个月的预测,应当对原序列拟合(让模型自己处理季节性);季节性调整主要是"分析过去",不是"预测未来"。
X-13ARIMA-SEATS 需要单独安装程序(在 Linux 上是 x13as,Windows 上需要额外配置)。如果系统不便安装,可以用 seasonal_decompose 或 statsmodels 的 STL 做"够用"的季节性调整:
from statsmodels.tsa.seasonal import STL stl = STL(y, period=12, robust=True).fit() adjusted = y - stl.seasonal # 去掉季节性
STL 的优势是不依赖外部程序、纯 Python;劣势是处理移动节假日和交易日差异不如 X-13 精细。
我们用 6.5 年(78 个月)的合成电商销量数据演示一次完整流程:
n = 78 monthly = 30 * np.sin(2 * np.pi * np.arange(n) / 12) trend = np.linspace(200, 600, n) y = trend + monthly + rng.normal(0, 8, n) y = pd.Series(y, index=pd.date_range("2018-01-01", periods=n, freq="MS")) # 简单 STL 季节性调整 stl = STL(y, period=12, robust=True).fit() adjusted = y - stl.seasonal # 对比:原序列"今年 8 月" vs "调整后今年 8 月" print("原序列 8 月销量均值:", y[y.index.month == 8].mean()) print("调整后 8 月销量均值:", adjusted[adjusted.index.month == 8].mean()) # 调整后 8 月销量应该接近趋势线(季节性被剥掉)
调整后,8 月的均值应当比原序列低 30 左右(剥掉夏季高峰),但月度的同比变化("今年 8 月 vs 去年 8 月")的差异变小、信号更清晰。
x13_arima_analysis,需要系统安装 X-13 程序。