你的时间序列真的平稳吗?手把手教你用ADF检验(Dickey-Fuller)和滚动统计为预测模型打好基础

📅 2026/7/9 18:27:10 👁️ 阅读次数 📝 编程学习
你的时间序列真的平稳吗?手把手教你用ADF检验(Dickey-Fuller)和滚动统计为预测模型打好基础

时间序列平稳性诊断实战:从理论到Python实现

时间序列分析中,平稳性检验是建模前的关键步骤。许多经典预测模型(如ARIMA)都建立在数据平稳的假设之上。但现实中的时间序列往往带有趋势或季节性,直接建模会导致预测失效。本文将系统讲解平稳性的核心概念,并手把手演示如何通过ADF检验和滚动统计方法诊断时间序列的平稳性。

1. 平稳性的本质与分类

1.1 严平稳与宽平稳

严平稳要求序列的所有统计特性(包括高阶矩)都不随时间变化。数学上,对任意时间位移τ,联合概率分布满足:

F_{X}(x_{t_1},...,x_{t_k}) = F_{X}(x_{t_1+τ},...,x_{t_k+τ})

宽平稳(弱平稳)则只需满足三个条件:

  1. 均值恒定:E[X_t] = μ(与t无关)
  2. 方差恒定:Var(X_t) = σ²(有限且不变)
  3. 自协方差仅与时滞相关:Cov(X_t, X_s) = γ(|t-s|)

实际分析中通常使用宽平稳标准,因其更易检验且大多数模型只需满足弱平稳条件

1.2 非平稳序列的典型特征

特征类型表现形式对建模的影响
趋势性长期上升/下降导致伪回归问题
季节性周期性波动预测出现系统性偏差
结构突变统计特性突然变化模型参数失效

2. 视觉诊断:滚动统计法

2.1 移动窗口技术实现

import pandas as pd import matplotlib.pyplot as plt def plot_rolling_stats(series, window=12): """绘制滚动统计量""" # 计算滚动统计量 rolling_mean = series.rolling(window=window).mean() rolling_std = series.rolling(window=window).std() # 创建画布 plt.figure(figsize=(12, 6)) # 绘制原始序列 plt.plot(series, color='blue', label='Original') plt.plot(rolling_mean, color='red', label='Rolling Mean') plt.plot(rolling_std, color='black', label='Rolling Std') plt.legend(loc='best') plt.title('Rolling Mean & Standard Deviation') plt.show() # 示例使用 from statsmodels.datasets import co2 data = co2.load().data plot_rolling_stats(data['co2'], window=24)

2.2 解读要点

  • 平稳序列:滚动均值和标准差应在小范围内波动
  • 存在趋势:滚动均值呈现明显上升/下降
  • 方差非恒定:滚动标准差呈现规律性变化

窗口大小的选择:对于月度数据,通常选择12或24;季度数据选择4。窗口过大会平滑过多细节,过小则波动剧烈难以判断

3. 统计检验:ADF测试详解

3.1 ADF检验的数学模型

ADF检验通过以下回归模型进行:

Δy_t = α + βt + γy_{t-1} + δ_1Δy_{t-1} + ... + δ_pΔy_{t-p} + ε_t

其中关键假设检验为:

  • H₀: γ = 0 (存在单位根,序列非平稳)
  • H₁: γ < 0 (序列平稳)

3.2 Python实现与结果解读

from statsmodels.tsa.stattools import adfuller def adf_test(series, regression='c'): """执行ADF检验并格式化输出""" result = adfuller(series, regression=regression) print('ADF Statistic: %f' % result[0]) print('p-value: %f' % result[1]) print('Critical Values:') for key, value in result[4].items(): print('\t%s: %.3f' % (key, value)) if result[1] > 0.05: print("未能拒绝原假设 - 序列可能非平稳") else: print("拒绝原假设 - 序列可能是平稳的") # 检验CO2数据 adf_test(data['co2'].dropna())

典型输出结果解析:

ADF Statistic: -2.312 p-value: 0.167 Critical Values: 1%: -3.440 5%: -2.866 10%: -2.569

判断规则:

  1. ADF统计量<临界值(绝对值比较)→ 拒绝H₀
  2. p-value< 0.05 → 拒绝H₀

3.3 检验类型选择

回归类型模型包含项适用场景
'c'常数项无趋势序列
'ct'常数+趋势项有趋势序列
'nc'无附加项罕见,理论分析

4. 非平稳序列的转换方法

4.1 差分处理

一阶差分公式:

diff_1 = series.diff().dropna()

季节性差分(以12个月为例):

diff_seasonal = series.diff(12).dropna()

4.2 对数转换

适用于指数增长趋势:

log_series = np.log(series)

4.3 组合策略

# 先对数再差分 transformed = np.log(series).diff().dropna()

转换效果评估矩阵:

方法适用场景优点缺点
一阶差分线性趋势简单直接可能过度差分
季节性差分周期性波动消除季节影响需要知道周期长度
对数变换指数趋势稳定方差对零值敏感

5. 进阶诊断:KPSS与PP检验

5.1 KPSS检验

与ADF检验互为补充:

from statsmodels.tsa.stattools import kpss def kpss_test(series, regression='c'): result = kpss(series, regression=regression) print('KPSS Statistic: %f' % result[0]) print('p-value: %f' % result[1]) print('Critical Values:') for key, value in result[3].items(): print('\t%s: %.3f' % (key, value))

5.2 组合判断策略

ADF结果KPSS结果结论
拒绝H₀不拒绝H₀确定平稳
不拒绝H₀拒绝H₀确定非平稳
都拒绝H₀都拒绝H₀需进一步分析

6. 实战案例:股票价格序列分析

以苹果公司股价为例:

import yfinance as yf # 获取数据 aapl = yf.download('AAPL', start='2020-01-01', end='2023-01-01')['Adj Close'] # 可视化分析 plot_rolling_stats(aapl, window=30) # ADF检验 adf_test(aapl, regression='ct') # 转换后检验 log_returns = np.log(aapl).diff().dropna() adf_test(log_returns)

关键发现:

  • 原始股价序列ADF检验p值为0.98,强烈非平稳
  • 对数收益率序列p值<0.01,可视为平稳

7. 建模前的完整性检查清单

  1. 视觉检查:绘制原始序列和滚动统计量
  2. ADF检验:选择适当的回归类型
  3. 转换验证:每次转换后重新检验
  4. 残差诊断:建模后检查残差是否白噪声
def full_diagnostic(series): """完整诊断流程""" # 1. 绘制原始序列 plt.figure(figsize=(12,4)) plt.subplot(121) series.plot(title='Original Series') # 2. 绘制滚动统计 plt.subplot(122) plot_rolling_stats(series) # 3. 执行ADF检验 print("\nADF Test Results:") adf_test(series) # 4. 执行KPSS检验 print("\nKPSS Test Results:") kpss_test(series)

在真实项目中,我发现同时使用多种检验方法可以避免单一方法的局限性。特别是对于金融时间序列,结合ADF和KPSS检验能给出更可靠的结论。当检验结果出现矛盾时,建议优先考虑ADF结果,并辅以视觉分析。