本节摘要:自相关函数(ACF)和周期图是识别季节性周期的两把标尺。ACF 在时间域告诉你"相隔 k 天的两个值有多相关";周期图在频率域告诉你"哪个频率的能量最强"。本节深入两者的数学原理,并用合成数据演示"周期峰有多显著才算强"。
阅读完本节,你应当能够:
本节是双路对照的第二路:ACF + 周期图(看周期长度)。读完后你能从 lag=7/12/24 处的局部峰确认季节性的存在,并用显著性判据避免把噪声当信号。
自相关函数(Autocorrelation Function, ACF)定义为:
ρ(k) = Cov[y(t), y(t+k)] / Var[y(t)]
直观理解:把序列平移 k 个时间点再与原序列做相关。ρ(0) = 1(自己与自己完全相关),ρ(k) 随 k 增大应当衰减。
如果序列有季节性(周期 m),那么**ρ(m) 通常显著大于 0**——因为相隔一个周期的两个值在季节性模式下有强相关。ρ(2m)、ρ(3m) 也会出现峰,因为相隔两个周期、三个周期的值也强相关。
import numpy as np import pandas as pd from statsmodels.tsa.stattools import acf from statsmodels.graphics.tsaplots import plot_acf # 构造 200 天数据:周期 7 + 趋势 + 噪声 rng = np.random.default_rng(11) n = 200 t = np.arange(n) trend = 0.1 * t weekly = 5 * np.sin(2 * np.pi * t / 7) y = trend + weekly + rng.normal(0, 1, n) s = pd.Series(y, index=pd.date_range("2024-01-01", periods=n)) # 算 ACF(nlags=30 看 lag 1 到 30) acf_vals = acf(s, nlags=30, fft=True) # 显著性的 95% 置信区间近似为 ±1.96/√n threshold = 1.96 / np.sqrt(n) print(f"显著性阈值 ±{threshold:.3f}") # 关键:lag=7、14、21 处的 ACF 值 for lag in [6, 7, 8, 13, 14, 15, 20, 21, 22]: flag = "✓" if abs(acf_vals[lag]) > threshold else "·" print(f"lag={lag:3d} ACF={acf_vals[lag]:+.3f} {flag}") # 预期:lag=7、14、21 处的 ACF 显著大于阈值
经验法则:|ρ(k)| > 2/√n 就算显著(约 95% 置信区间)。如果 |ρ(m)| > 2/√n 且 m 是业务周期长度,则季节性存在。
ACF 的形状透露两类信息:
两种信号可以同时存在。例如,一个有线性趋势 + 周季节性的序列,ACF 会先随 lag 增大快速衰减(趋势效应),然后在 lag=7、14、21 等位置出现周期峰。判读时关注周期峰是否"突出"于整体衰减趋势之上。
周期图(periodogram)把序列拆成不同频率的复指数波,统计每个频率的能量:
P(f) = (1/n) |Σ y(t) e^(-i 2π f t)|²
直观理解:在频率 f 处,序列的"信号强度"。最强的峰对应"主导周期"。
from scipy.signal import periodogram freqs, power = periodogram(s.values, fs=1.0) # fs=1 每天 1 个采样点 # 周期 = 1 / 频率 periods = 1 / freqs[1:] # 排除 f=0 power_no_dc = power[1:] # 找最强峰 top_idx = np.argsort(power_no_dc)[::-1][:5] for i in top_idx: print(f"周期 {periods[i]:.2f} 天 功率 {power_no_dc[i]:.2f}") # 预期:周期 ≈ 7 天的功率最高
| 工具 | 优势 | 局限 |
|---|---|---|
| ACF | 直观:周期峰在 lag 轴上 | 周期峰如果太靠后会看不到(要 nlags 够大) |
| 周期图 | 频率分辨率高,能识别"非整数周期" | 频率轴不直观,需要反推周期长度 |
工程上两个一起画最稳:ACF 确认"lag=7、14 处显著",周期图确认"频率 1/7 处功率最高"。两者一致时,季节性才算"确认"。
statsmodels 的 plot_acf 会自动画 95% 置信带(蓝色阴影区域)。如果某个 lag 的 ACF 超出置信带 → 显著。
import matplotlib.pyplot as plt from statsmodels.graphics.tsaplots import plot_acf fig, ax = plt.subplots(figsize=(10, 4)) plot_acf(s, lags=30, ax=ax, alpha=0.05) # alpha=0.05 对应 95% 置信带 plt.tight_layout()
如果只有 lag=0 之外、零星几个 lag 超出置信带,且不在业务周期位置 → 多半是噪声。如果 lag=7、14、21 都超出 → 强季节性。
某电商 90 天日数据,ACF 显示:
判读:lag=7、14、21 三个峰依次递减,强周季节性。后续建模应当用 SARIMA(?, ?, ?)(?, ?, ?, 7) 或 Prophet。
|ρ(k)| > 2/√n(约 95% 置信区间)。