第 4 篇:平稳性 - 时间序列分析的基石
第 4 篇:平稳性 - 时间序列分析的基石
在上一篇中,我们学习了如何将时间序列分解为趋势、季节性和残差。我们看到,很多真实世界的时间序列(比如 CO2 浓度)都包含明显的趋势(长期向上或向下)和/或季节性(固定周期的波动)。
这些成分虽然揭示了数据的内在模式,但也带来了一个“问题”:它们使得序列的统计特性随时间而变化。例如,带有上升趋势的序列,其均值(平均水平)会随着时间推移而增加。这种“不稳定”的特性,在时间序列分析中被称为非平稳性 (Non-stationarity)。
为什么我们要关心这个?因为许多经典且强大的时间序列预测模型(如 ARIMA 模型家族)都建立在一个关键假设之上:数据是平稳的 (Stationary)。
本篇我们就来深入探讨这个时间序列分析的基石——平稳性。我们将了解:
- 什么是平稳性?
- 为什么平稳性如此重要?
- 如何判断一个序列是否平稳?
- 如果序列不平稳,最常用的处理方法是什么?
什么是平稳性 (Stationarity)?
想象一条平静的湖面(Stationary)和一条奔腾的河流(Non-stationary)。
- 平静的湖面: 在任何位置取一瓢水,它的平均深度、水面的波动程度(方差)看起来都差不多。
- 奔腾的河流: 在上游和下游取水,平均深度可能截然不同;在急流和缓滩处,水流的湍急程度(方差)也相差甚远。
平稳性 就是时间序列数据拥有类似“平静湖面”的特性。更严谨地说,一个时间序列是**(弱)平稳**的,如果它的:
- 均值 (Mean) 不随时间
t
变化。 - 方差 (Variance) 不随时间
t
变化。 - 自协方差 (Autocovariance) 只依赖于时间的间隔
k
,而不依赖于具体的时间点t
。(这保证了序列内部的相关性结构是稳定的,我们暂时不用深究数学细节,理解前两点更重要)。
看图说话:
- 平稳序列 (Stationary): 数据点围绕一个固定的水平线上下波动,且波动的幅度大致稳定。典型的例子是白噪声 (White Noise),即纯粹的随机波动。
- 非平稳序列 (Non-stationary):
- 含趋势: 数据明显呈现长期上升或下降。 (例如我们上一篇分解出的 CO2 趋势部分)
- 含季节性: 数据存在固定的周期性波动。 (例如 CO2 的季节性部分)
- 方差变化: 数据的波动幅度随时间变化(例如,早期波动小,后期波动剧烈)。
- 随机游走 (Random Walk): 一个典型的非平稳过程,下一步的位置是当前位置加上一个随机步长。股价有时被建模为类似随机游走。
为什么平稳性如此重要?
- 模型假设: 很多经典时间序列模型(如 ARMA, ARIMA)明确要求输入数据是平稳的。如果用非平稳数据直接建模,可能会导致模型参数估计不准、预测结果不可靠。
- 可预测性基础: 平稳序列意味着其统计特性在未来可能保持不变。这为我们基于历史模式进行预测提供了更坚实的基础。如果一个序列的均值和方差都在不断变化,预测其未来将非常困难。
- 简化分析: 处理平稳序列通常比处理非平稳序列更简单。我们可以专注于分析其内部的相关性结构(自相关性),而不用同时处理变化的趋势和季节性。
如何判断平稳性?
主要有两种方法:
-
视觉检查 (Visual Inspection):
- 直接绘制时间序列图: 观察是否存在明显的趋势或季节性模式?数据的均值线是否大致水平?数据的波动幅度是否大致恒定?
- 查看分解图 (来自上一篇):
seasonal_decompose
的结果可以帮助我们。如果趋势 (Trend) 成分不是水平的,或者季节性 (Seasonal) 成分很明显,那么原始序列很可能不是平稳的。
-
统计检验 (Statistical Tests):
-
肉眼观察有时会骗人,我们需要更客观的统计方法。最常用的检验之一是 ADF 检验 (Augmented Dickey-Fuller Test)。
-
ADF 检验的目的: 它的“原假设 (Null Hypothesis)”是序列存在单位根 (Unit Root),即序列是非平稳的。它的“备择假设 (Alternative Hypothesis)”是序列没有单位根,即序列是平稳的。
-
如何解读结果? 我们主要关心检验输出的 p-value:
- 如果 p-value > 0.05 (常用的显著性水平): 我们没有足够证据拒绝原假设。也就是说,我们倾向于认为序列是非平稳的。
- 如果 p-value <= 0.05: 我们拒绝原假设。也就是说,我们倾向于认为序列是平稳的。
-
Python 实现 (使用
statsmodels
):
from statsmodels.tsa.stattools import adfuller import statsmodels.api as sm import pandas as pd# 仍然使用上一篇的月度 CO2 数据 data = sm.datasets.co2.load_pandas().data data['co2'].interpolate(inplace=True) monthly_data = data.resample('M').mean()print("对原始月度 CO2 数据进行 ADF 检验:") result = adfuller(monthly_data['co2'])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))# 解释 p-value if result[1] > 0.05:print("\n结论:p-value > 0.05,未能拒绝原假设,数据很可能非平稳。") else:print("\n结论:p-value <= 0.05,拒绝原假设,数据很可能平稳。")
运行上述代码,你会发现 CO2 数据的 p-value 远大于 0.05,印证了我们视觉观察到的非平稳性。
-
如何让序列平稳?—— 差分 (Differencing)
如果我们的序列被判断为非平稳,怎么办?最常用、最简单的处理方法就是差分 (Differencing)。
差分的思想很简单:计算相邻时间点数据之间的差值。
-
一阶差分 (First Difference):
Y'(t) = Y(t) - Y(t-1)
- 目的: 主要用于消除序列中的线性趋势。
- 效果: 如果原序列有稳定增长或下降的趋势,差分后的序列通常会围绕 0 值上下波动。
-
季节性差分 (Seasonal Difference):
Y'(t) = Y(t) - Y(t-s)
,其中s
是季节性周期长度(例如,对于月度数据年度季节性,s=12)。- 目的: 主要用于消除序列中的季节性。
-
高阶差分: 有时需要进行多次差分(例如,对一阶差分后的序列再做一次差分,称为二阶差分),或者结合使用普通差分和季节性差分,来达到平稳。但通常一阶或二阶差分就足够了。过度差分可能会引入不必要的模式。
Python 实现一阶差分 (.diff()
):
import matplotlib.pyplot as plt
import seaborn as sns# 对 CO2 数据进行一阶差分
monthly_data['co2_diff'] = monthly_data['co2'].diff()# 差分后第一个值是 NaN,需要去掉或填充
monthly_data_diff = monthly_data.dropna() # 去掉 NaN 值print("\n差分后的数据 (前几行):")
print(monthly_data_diff.head())# 绘制差分后的序列图
plt.figure(figsize=(12, 6))
sns.lineplot(data=monthly_data_diff, x=monthly_data_diff.index, y='co2_diff')
plt.title('First Difference of Monthly CO2 Data')
plt.xlabel('Date')
plt.ylabel('First Difference')
plt.axhline(0, color='red', linestyle='--') # 添加 0 值参考线
plt.show()# 对差分后的序列再次进行 ADF 检验
print("\n对一阶差分后的 CO2 数据进行 ADF 检验:")
result_diff = adfuller(monthly_data_diff['co2_diff'])
print('ADF Statistic: %f' % result_diff[0])
print('p-value: %f' % result_diff[1])
print('Critical Values:')
for key, value in result_diff[4].items():print('\t%s: %.3f' % (key, value))if result_diff[1] > 0.05:print("\n结论:p-value > 0.05,差分后数据仍可能非平稳 (可能需要进一步处理,如季节性差分)。")
else:print("\n结论:p-value <= 0.05,拒绝原假设,差分后数据很可能平稳。")# 注意:CO2 数据同时有强趋势和强季节性,仅一阶差分可能不足以完全平稳,
# ADF检验的 p-value 可能会显著降低,但仍可能大于 0.05 或略小于 0.05。
# 完全平稳化可能需要再做季节性差分: .diff(12)
# monthly_data['co2_diff_seasonal'] = monthly_data['co2'].diff().diff(12).dropna()
# 但我们这里主要演示一阶差分的效果。
解读结果:
- 差分后的图形: 你会看到,原始 CO2 数据那个明显的上升趋势消失了!差分后的序列现在看起来更像是在 0 附近波动,尽管可能还保留着一些季节性模式。
- 差分后的 ADF 检验: 运行 ADF 检验,你会发现差分后序列的 p-value 显著降低了(可能已经小于 0.05,或者非常接近)。这表明一阶差分有效地移除了大部分非平稳性(主要是趋势)。如果 p-value 仍然偏高,可能意味着还需要处理季节性(进行季节性差分)。
重要提醒: 我们的目标是使用尽可能少的差分次数来使序列平稳。过度差分会丢失信息。
小结
今天我们学习了时间序列分析中一个至关重要的概念——平稳性:
- 定义: 平稳序列的均值、方差和自协方差结构不随时间改变。
- 重要性: 是许多经典时间序列模型的基础假设。
- 判断方法:
- 视觉检查: 观察时间序列图和分解图中的趋势、季节性及波动幅度。
- 统计检验: 使用 ADF 检验,通过 P-value 判断是否存在单位根(非平稳)。
- 处理方法: 最常用的使序列平稳的方法是差分(一阶差分消除趋势,季节性差分消除季节性)。
理解并能够处理数据的平稳性,是进行有效时间序列建模的关键一步。
下一篇预告
到目前为止,我们已经掌握了如何加载、可视化、分解时间序列,以及如何处理非平稳性。现在,我们终于可以开始尝试做一些简单的预测 (Forecasting) 了!
下一篇,我们将学习几种最基础、最直观的预测方法,比如朴素预测、平均法、移动平均法等。它们虽然简单,但有时效果惊人,并且是理解更复杂模型的重要起点。
准备好迎接你的第一个时间序列预测模型了吗?敬请期待!
(对你的数据进行 ADF 检验和差分,结果如何?差分后的序列看起来平稳了吗?欢迎在评论区分享你的实践!)