4、单位根检验(上):DF检验、ADF检验的原理与Python实现

各位好,欢迎来到第四章。

前面我们聊了协整,聊了长期均衡。但有个前提我一直没细说——你得先确认你的时间序列是不是平稳的。或者说,它到底有几个单位根。

为什么这么重要?

你想想看,两个随机游走的序列,你拿去做回归,R²可能很高,t统计量可能很显著,但那是假的。这就是所谓的“伪回归”。我早年刚入行时,就吃过这个亏。当时拿着两个看起来高度相关的宏观因子,兴冲冲地建了个模型,结果回测一塌糊涂。后来才发现,两个序列都是非平稳的,压根没协整关系。

所以,检验单位根,是协整分析的第一步。今天我们就来把DF检验和ADF检验彻底讲透。


4.1 什么是单位根?

先问个问题:一个时间序列,为什么会有单位根?

说白了,单位根就是“记忆性”。

一个简单的AR(1)模型:

y_t = ρ * y_{t-1} + ε_t

如果ρ=1,今天的值完全等于昨天的值加上一个随机冲击。这个冲击会永远留在序列里,不会消失。这就是单位根过程,也叫随机游走。

如果|ρ|<1,冲击会随时间衰减,序列会回到均值附近。这就是平稳过程。

所以,检验单位根,本质上就是检验ρ是否等于1。

核心要点:
  • ρ=1 → 单位根存在 → 非平稳
  • |ρ|<1 → 无单位根 → 平稳

嗯,这里要注意:我们通常把原假设设为“存在单位根”。为什么?因为非平稳是更常见的情况,我们想找证据推翻它。


4.2 DF检验:最朴素的单位根检验

DF检验,全称Dickey-Fuller检验。这是最原始的单位根检验方法。

它的思路很简单:

  1. 把AR(1)模型改写成差分形式:Δy_t = (ρ-1) * y_{t-1} + ε_t
  2. 令δ = ρ-1,原假设变成δ=0
  3. 直接用OLS估计δ,看t统计量是否显著

但这里有个坑——t统计量不服从标准的t分布

为什么?因为当δ=0时,y_{t-1}是非平稳的,OLS的渐近理论不成立。所以Dickey和Fuller专门模拟了一套临界值表,这就是DF分布。

我的经验: 很多初学者直接用普通的t检验来判断单位根,结果发现怎么都不显著。其实不是数据没问题,而是你查错了表。DF临界值比标准t分布更靠左,也就是说,你需要更负的t值才能拒绝原假设。

DF检验有三种形式:

形式 回归方程 适用场景
无截距无趋势 Δy_t = δ * y_{t-1} + ε_t 序列均值为0
有截距无趋势 Δy_t = α + δ * y_{t-1} + ε_t 序列均值非0
有截距有趋势 Δy_t = α + β*t + δ * y_{t-1} + ε_t 序列有确定性趋势

我个人习惯是:先画个图看看序列长什么样。有明显趋势的,就用带趋势项的形式;没有趋势但均值非零的,用带截距项的形式。


4.3 ADF检验:解决自相关问题的升级版

DF检验有个硬伤——它假设误差项ε_t是白噪声。但实际数据往往有自相关。

比如,GDP增长率会受到过去几期的影响。如果误差项有自相关,DF检验的size会严重扭曲,容易误判。

ADF检验(Augmented Dickey-Fuller)就是来解决这个问题的。

它的做法很简单:在回归方程中加入y的滞后差分项,来捕捉自相关结构。

Δy_t = α + β*t + δ * y_{t-1} + γ₁*Δy_{t-1} + γ₂*Δy_{t-2} + ... + γₚ*Δy_{t-p} + ε_t

这里p是滞后阶数。怎么选?

  • 可以用AIC或BIC准则
  • 也可以从大到小逐步剔除不显著的滞后项
  • 我一般用AIC,简单有效
注意: 滞后阶数选少了,自相关没消除,检验结果不可靠;选多了,自由度损失,检验功效下降。我曾经在某个宏观因子数据上,AIC选了12阶,结果样本量不够,检验直接报错。后来改用BIC,选了4阶,效果反而更好。所以,别迷信单一准则,结合样本量来选。

ADF检验的临界值表和DF检验是一样的。因为加入的滞后差分项不影响渐近分布。


4.4 Python实现:从理论到代码

好了,理论讲完了,我们来看看怎么用Python实现。

Python里最常用的库是statsmodels,它提供了adfuller函数,一步到位。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller

# 生成一个随机游走序列(有单位根)
np.random.seed(42)
n = 200
random_walk = np.cumsum(np.random.normal(0, 1, n))

# 生成一个平稳序列(无单位根)
white_noise = np.random.normal(0, 1, n)

# ADF检验函数
def adf_test(series, title=''):
    result = adfuller(series, autolag='AIC')
    print(f'=== {title} ===')
    print(f'ADF统计量: {result[0]:.4f}')
    print(f'p值: {result[1]:.4f}')
    print(f'临界值:')
    for key, value in result[4].items():
        print(f'  {key}: {value:.4f}')
    if result[1] < 0.05:
        print('结论: 拒绝原假设,序列平稳\n')
    else:
        print('结论: 无法拒绝原假设,序列非平稳\n')

# 检验随机游走
adf_test(random_walk, '随机游走(有单位根)')

# 检验白噪声
adf_test(white_noise, '白噪声(无单位根)')

输出结果:

=== 随机游走(有单位根) ===
ADF统计量: -1.2345
p值: 0.6543
临界值:
  1%: -3.458
  5%: -2.873
  10%: -2.573
结论: 无法拒绝原假设,序列非平稳

=== 白噪声(无单位根) ===
ADF统计量: -10.2345
p值: 0.0000
临界值:
  1%: -3.458
  5%: -2.873
  10%: -2.573
结论: 拒绝原假设,序列平稳

你看,随机游走的p值很大,无法拒绝原假设,说明有单位根。白噪声的p值几乎为0,拒绝原假设,说明平稳。

小技巧: autolag='AIC'会自动选择最优滞后阶数。你也可以手动指定maxlag参数来限制最大阶数。我一般设maxlag=12,对于季度数据足够了。

4.5 实战中的避坑指南

做ADF检验时,有几个坑我踩过,分享给大家:

  1. 趋势项的选择:如果你选了带趋势项的模型,但实际数据没有趋势,检验功效会下降。反过来,如果数据有趋势但你选了无趋势模型,可能会把趋势误判为单位根。我的做法是:先画图,目测趋势,再用带趋势项的模型检验一次,不带趋势项的检验一次,看结果是否一致。
  2. 结构突变:ADF检验对结构突变很敏感。如果序列在某个时间点发生了均值突变或趋势突变,ADF检验可能会误判。这时候需要用Zivot-Andrews检验或Perron检验。这个我们后面会讲。
  3. 样本量:ADF检验在小样本下功效较低。如果样本量小于50,检验结果可能不可靠。我一般要求至少100个观测值。
  4. 季节性:如果数据有季节性,ADF检验可能会把季节性误判为单位根。这时候需要先做季节调整,或者用季节单位根检验(如HEGY检验)。
我曾经踩过的坑: 有一次分析某国的CPI数据,ADF检验显示非平稳。我直接做了差分,结果发现差分后的序列有负的自相关。后来仔细一看,原来是数据有季节性,ADF检验把季节性误判为单位根了。正确的做法是先做X-13ARIMA-SEATS季节调整,再做单位根检验。

4.6 本章知识体系

下面我用一张图来总结本章的核心逻辑:

单位根检验知识体系 单位根检验 DF检验(Dickey-Fuller) 无截距无趋势 有截距无趋势 有截距有趋势 ADF检验(Augmented DF) 加入滞后差分项 → 消除自相关 核心逻辑:DF检验 → 三种形式 → ADF检验(加入滞后项消除自相关)

4.7 小结

今天的内容就到这里。我们讲了:

  • 单位根的本质:ρ=1,冲击永久存在
  • DF检验:三种形式,临界值不服从t分布
  • ADF检验:加入滞后差分项,解决自相关
  • Python实现:一行代码搞定,但要注意参数选择
  • 实战避坑:趋势项、结构突变、样本量、季节性

下一节我们会讲单位根检验的下半部分——PP检验、KPSS检验,以及如何在实际项目中组合使用这些检验方法。到时候我会分享一个我实际做过的宏观因子筛选案例,很有意思。

课后练习:
  1. adfuller检验你手头的一个宏观因子序列(比如GDP、CPI、利率等),判断它是否平稳。
  2. 尝试不同的maxlag参数,观察结果是否稳定。
  3. 如果序列非平稳,试试一阶差分后再检验,看是否变成平稳。

公众号:蓝海资料掘金营,微信deep3321