3. 时间序列基础:平稳性检验、自相关与偏自相关、差分操作

各位同学,咱们今天聊点硬核的。时间序列分析,说白了就是跟历史数据对话,让它告诉我们未来可能发生什么。但这里有个大坑——如果数据本身不老实,你建的模型就是空中楼阁。

我个人习惯,拿到任何时间序列数据,第一件事不是跑模型,而是先做三件事:平稳性检验、看自相关结构、必要时做差分。这三板斧砍下去,数据的基本脾气你就摸透了。

时间序列基础三件套 平稳性检验 ACF / PACF 分析 差分操作 ADF 检验 KPSS 检验 ACF 图 PACF 图 一阶差分 季节性差分 目标:将非平稳序列转化为平稳序列,识别AR/MA阶数

3.1 平稳性:时间序列的「定海神针」

什么叫平稳?简单说,就是数据的统计性质不随时间变化。均值是常数,方差是常数,自相关结构也是稳定的。

你想想看,如果数据本身均值一直在漂移,你拿什么去预测未来?就像在移动的火车上测距离,永远测不准。

平稳性的两种类型:
严平稳: 联合分布不随时间平移而变化。这个条件太强,现实中几乎不存在。
弱平稳(宽平稳): 均值恒定、方差恒定、自协方差只与时间间隔有关。我们平时说的「平稳」默认指这个。

我在项目中遇到过最典型的非平稳序列——GDP数据。你看它长期趋势是向上的,均值明显在变化。这种数据直接建模,结果就是伪回归,看着R²很高,其实全是假象。

3.2 平稳性检验:别靠肉眼,上统计量

很多人喜欢画个图,凭感觉说「嗯,这个看起来平稳」。千万别!我吃过这个亏。有一次我对着一个看起来挺平的序列建了ARIMA模型,回测效果奇好,结果实盘一跑就崩。后来一查,那序列是随机游走,压根不平稳。

所以,老老实实用检验方法:

3.2.1 ADF 检验(增广迪基-富勒检验)

这是最常用的单位根检验。原假设是「序列存在单位根(非平稳)」。p值小于0.05,就拒绝原假设,认为序列平稳。

from statsmodels.tsa.stattools import adfuller
import pandas as pd

# 假设 data 是 Series
result = adfuller(data, autolag='AIC')
print(f'ADF统计量: {result[0]:.4f}')
print(f'p值: {result[1]:.4f}')
print(f'临界值: {result[4]}')

if result[1] < 0.05:
    print('✅ 拒绝原假设,序列平稳')
else:
    print('❌ 无法拒绝原假设,序列非平稳')
我的小技巧: ADF检验的滞后阶数选择很关键。我习惯用 autolag='AIC' 让程序自动选,但有时候手动指定阶数会更稳健。特别是高频数据,我一般会设 maxlag=20 再跑一遍对比。

3.2.2 KPSS 检验

这个跟ADF正好反过来——原假设是「序列平稳」。所以两个检验配合使用,就像双保险。

from statsmodels.tsa.stattools import kpss

result_kpss = kpss(data, regression='c')
print(f'KPSS统计量: {result_kpss[0]:.4f}')
print(f'p值: {result_kpss[1]:.4f}')

if result_kpss[1] < 0.05:
    print('❌ 拒绝原假设,序列非平稳')
else:
    print('✅ 无法拒绝原假设,序列平稳')
避坑指南: 我曾经同时跑ADF和KPSS,结果ADF说平稳,KPSS也说平稳——这种情况最理想。但如果ADF说非平稳,KPSS也说非平稳,那基本确定数据有问题。最头疼的是两者结论不一致,这时候我一般会多看几阶差分后的结果,或者用其他检验(如PP检验)做交叉验证。

3.3 自相关与偏自相关:读懂数据的「记忆」

时间序列有个特点——过去会影响现在。今天的股价跟昨天有关,今天的温度跟昨天有关。这种「记忆」就是自相关。

3.3.1 自相关函数(ACF)

ACF 衡量的是当前值与其滞后值之间的相关性。比如 lag=1 的ACF,就是今天和昨天的相关系数。

嗯,这里要注意:ACF 会受中间滞后项的影响。比如今天和前天相关,可能是因为今天和昨天相关、昨天和前天相关导致的「传递效应」。所以我们需要 PACF。

3.3.2 偏自相关函数(PACF)

PACF 剔除了中间变量的影响,直接看「纯」的相关性。比如 lag=2 的PACF,就是剔除了 lag=1 的影响后,今天和前天的直接关系。

from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))

plot_acf(data, lags=20, ax=ax1)
ax1.set_title('自相关函数 (ACF)')

plot_pacf(data, lags=20, ax=ax2, method='ywm')
ax2.set_title('偏自相关函数 (PACF)')

plt.tight_layout()
plt.show()
实战解读口诀:
- ACF 拖尾(缓慢衰减)+ PACF 截尾(p阶后突降)→ AR(p) 模型
- ACF 截尾(q阶后突降)+ PACF 拖尾 → MA(q) 模型
- 两者都拖尾 → ARMA 模型
- 两者都截尾 → 可能数据是白噪声,没啥可建模的

我记得有一次做CPI预测,ACF在lag=12处有个明显的尖峰,我当时就意识到——这是季节性。后来加了12阶差分,模型效果直接提升了一个档次。所以看ACF图的时候,别光看前几阶,远一点的滞后也可能藏着重要信息。

3.4 差分操作:让不平稳变平稳的「手术刀」

如果数据不平稳,最直接的解决办法就是差分。说白了,就是把「数值」变成「变化量」。

3.4.1 一阶差分

今天的值减去昨天的值。如果原始数据是随机游走,一阶差分后就是白噪声。

# 一阶差分
diff_1 = data.diff().dropna()

# 再检验一下平稳性
result_diff = adfuller(diff_1)
print(f'差分后p值: {result_diff[1]:.4f}')

3.4.2 季节性差分

对于月度数据,用今年1月减去年1月。对于季度数据,用今年Q1减去年Q1。

# 季节性差分(以12期为例)
seasonal_diff = data.diff(12).dropna()
我的经验: 差分不是越多越好。每差一次,数据就少一个样本,而且会丢失长期信息。我一般最多差两阶,如果还不平稳,我会考虑先做对数变换或Box-Cox变换,而不是继续差分。另外,差分后的数据记得做逆变换,否则预测结果没法解释。

3.4.3 如何确定差分阶数?

我个人习惯用两步法:

  1. 先看ACF图: 如果ACF衰减很慢(比如10阶后还有显著相关),大概率需要差分
  2. 再用单位根检验: ADF和KPSS交叉验证,直到p值通过

其实还有一个更自动化的方法——用 pmdarima 库的 ndiffs 函数,它会帮你算最优差分阶数。

from pmdarima.arima import ndiffs

# 自动确定差分阶数
d = ndiffs(data, test='adf')
print(f'建议差分阶数: {d}')
曾经踩过的坑: 有一次我处理一个高频交易数据,ndiffs 建议差两阶。我照做了,结果模型预测的波动率完全失真。后来发现,高频数据本身就有「过度差分」的问题——差一阶已经把信息差没了,再差一阶就是在差噪声。所以,差分阶数宁少勿多,特别是高频数据。

3.5 实战流程总结

好了,把今天的内容串起来。我拿到一个时间序列,一般走这个流程:

步骤 操作 目的
1 画时序图 + ADF/KPSS检验 判断是否平稳
2 如果不平稳,做差分(先1阶,再季节性) 转化为平稳序列
3 画ACF和PACF图 识别AR/MA阶数
4 根据ACF/PACF特征选择模型 确定p、q参数

你想想看,这套流程走下来,数据的基本特征就全在你手里了。后面建什么模型,心里都有底。

嗯,今天就到这儿。记住一句话:平稳性是时间序列建模的基石,差分是处理非平稳的利器,ACF/PACF是识别模型结构的眼睛。这三样东西玩熟了,时间序列分析你就入门了。


蓝海数据掘金营,专注资料整理