📑 查看全课大纲(第 4 / 20 节)

时间序列预处理代码实战

约 31 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · 时间序列分析

时间序列分析的核心在于从看似杂乱的数据中提取规律性信息。本节我们将通过Python实战,系统掌握时间序列预处理的两大基石:平稳性检验与纯随机性检验。学完本节,你将能够使用Python对任意时间序列数据进行初步诊断,判断其是否适合建模分析,为后续的ARIMA、GARCH等高级模型奠定坚实基础。

💡 核心导读

  • 平稳性检验:通过时序图直观判断,结合ADF单位根检验进行统计验证,识别序列是否具有常数均值、方差和自协方差结构
  • 纯随机性检验:使用Ljung-Box检验判断序列是否为白噪声,避免对无信息序列进行无效建模
  • 自相关分析:通过ACF/PACF图揭示序列内部的相关结构,为模型定阶提供依据
  • Python现代化实现:使用statsmodelspandasmatplotlib等主流库替代原课程的R语言实现
  • 实战全流程:从数据读取、可视化到统计检验的完整代码演示

平稳性检验:理论与Python实现

平稳性的统计定义

在时间序列分析中,我们通常使用宽平稳(弱平稳)定义,要求序列满足以下三个条件:

  1. 常数均值E(Xt)=μ\mathbb{E}(X_t) = \mu,对所有tt成立
  2. 常数方差Var(Xt)=σ2\text{Var}(X_t) = \sigma^2,对所有tt成立
  3. 自协方差仅依赖于时间间隔Cov(Xt,Xt+k)=γ(k)\text{Cov}(X_t, X_{t+k}) = \gamma(k),只与滞后阶数kk有关,与时间起点tt无关

对应的自相关系数定义为: ρ(k)=γ(k)γ(0)=Cov(Xt,Xt+k)Var(Xt)\rho(k) = \frac{\gamma(k)}{\gamma(0)} = \frac{\text{Cov}(X_t, X_{t+k})}{\text{Var}(X_t)}

平稳性的图检验方法

时序图检验

平稳序列的时序图应显示序列在常数均值附近随机波动,波动范围有界,且无明显趋势或周期性特征。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller, acf, pacf
from statsmodels.stats.diagnostic import acorr_ljungbox
import warnings
warnings.filterwarnings('ignore')

# 设置随机种子确保结果可复现
np.random.seed(42)

# 生成示例数据:模拟一个带漂移项的自回归过程
n = 150
dates = pd.date_range('2021-01-01', periods=n, freq='D')

# 生成数据:y_t = 0.5 + 0.6 * y_{t-1} + ε_t, ε_t ~ N(0, 1.2^2)
ε = np.random.normal(0, 1.2, n)  # 白噪声扰动
y = np.zeros(n)
for t in range(1, n):
    y[t] = 0.5 + 0.6 * y[t - 1] + ε[t]

# 创建时间序列对象
series = pd.Series(y, index=dates, name='模拟时序数据')

# 绘制时序图
plt.figure(figsize=(12, 6))
plt.plot(series, linewidth=1.5)
plt.axhline(y=series.mean(), color='r', linestyle='--', alpha=0.7, label=f'均值线 ({series.mean():.2f})')
plt.fill_between(series.index, 
                 series.mean() - series.std(), 
                 series.mean() + series.std(), 
                 alpha=0.2, color='gray', label='±1标准差区间')
plt.title('时序图检验:模拟时间序列', fontsize=14, fontweight='bold')
plt.xlabel('日期')
plt.ylabel('观测值')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# 基础描述统计
desc_stats = series.describe()
print("=== 描述性统计 ===")
print(f"数据长度: {len(series)}")
print(f"均值: {desc_stats['mean']:.4f}")
print(f"标准差: {desc_stats['std']:.4f}")
print(f"最小值: {desc_stats['min']:.4f}")
print(f"25%分位数: {desc_stats['25%']:.4f}")
print(f"中位数: {desc_stats['50%']:.4f}")
print(f"75%分位数: {desc_stats['75%']:.4f}")
print(f"最大值: {desc_stats['max']:.4f}")

自相关图(ACF)检验

平稳序列通常具有短期相关性,随着滞后阶数kk增加,自相关系数ρ(k)\rho(k)会快速衰减到零附近。

from statsmodels.graphics.tsaplots import plot_acf, plot_pacf

# 绘制ACF和PACF图
fig, axes = plt.subplots(2, 1, figsize=(12, 8))

# ACF图(自相关函数)
plot_acf(series, lags=20, ax=axes[0], alpha=0.05, 
         title='自相关图(ACF)检验', color='blue')
axes[0].set_xlabel('滞后阶数')
axes[0].set_ylabel('自相关系数')
axes[0].grid(True, alpha=0.3)

# PACF图(偏自相关函数)
plot_pacf(series, lags=20, ax=axes[1], alpha=0.05, 
          title='偏自相关图(PACF)检验', color='green')
axes[1].set_xlabel('滞后阶数')
axes[1].set_ylabel('偏自相关系数')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 计算具体的ACF和PACF值
acf_vals = acf(series, nlags=10, fft=False)
pacf_vals = pacf(series, nlags=10)

print("\n=== 自相关分析 ===")
print("滞后阶数 | 自相关系数(ACF) | 偏自相关系数(PACF)")
print("-" * 50)
for k in range(0, 6):
    print(f"   {k:2d}    |     {acf_vals[k]:7.4f}     |      {pacf_vals[k]:7.4f}")

ADF单位根检验

图检验方法主观性强,我们需要更客观的统计检验。Augmented Dickey-Fuller (ADF) 检验是最常用的单位根检验方法。

检验原理

  • 原假设H0H_0:序列存在单位根(非平稳)
  • 备择假设H1H_1:序列不存在单位根(平稳)

检验模型Δyt=α+βt+γyt1+i=1pδiΔyti+εt\Delta y_t = \alpha + \beta t + \gamma y_{t-1} + \sum_{i=1}^{p} \delta_i \Delta y_{t-i} + \varepsilon_t

其中γ=ρ1\gamma = \rho - 1,检验γ=0\gamma = 0(存在单位根) vs γ<0\gamma < 0(平稳)。

# ADF单位根检验
print("\n=== ADF单位根检验 ===")
adf_result = adfuller(series, autolag='AIC')  # 使用AIC准则自动选择滞后阶数

adf_statistic = adf_result[0]
adf_pvalue = adf_result[1]
adf_critical_values = adf_result[4]

print(f"ADF统计量: {adf_statistic:.6f}")
print(f"p值: {adf_pvalue:.6e}")
print("\n临界值(显著性水平):")
for key, value in adf_critical_values.items():
    print(f"  {key}: {value:.6f}")

# 判断结论
if adf_pvalue < 0.05:
    print(f"\n结论: p值({adf_pvalue:.6e}) < 0.05,拒绝原假设")
    print("      → 序列是平稳的")
else:
    print(f"\n结论: p值({adf_pvalue:.6e}) >= 0.05,不能拒绝原假设")
    print("      → 序列可能存在单位根(非平稳)")

纯随机性(白噪声)检验

白噪声的统计定义

白噪声序列{εt}\{\varepsilon_t\}满足:

  1. 零均值E(εt)=0\mathbb{E}(\varepsilon_t) = 0,对所有tt
  2. 常数方差Var(εt)=σ2\text{Var}(\varepsilon_t) = \sigma^2,对所有tt
  3. 无自相关Cov(εt,εs)=0\text{Cov}(\varepsilon_t, \varepsilon_s) = 0,对所有tst \neq s

即:εtWN(0,σ2)\varepsilon_t \sim WN(0, \sigma^2)

Ljung-Box检验原理

检验假设

  • H0H_0ρ1=ρ2==ρm=0\rho_1 = \rho_2 = \cdots = \rho_m = 0(序列为白噪声)
  • H1H_1:至少存在某个ρk0\rho_k \neq 01km1 \leq k \leq m(序列非白噪声)

检验统计量LB=n(n+2)k=1mρ^k2nkχ2(m)LB = n(n+2) \sum_{k=1}^{m} \frac{\hat{\rho}_k^2}{n-k} \sim \chi^2(m) 其中nn为样本量,mm为检验的最大滞后阶数,ρ^k\hat{\rho}_k为样本自相关系数。

Python实现白噪声检验

# 纯随机性(白噪声)检验
print("\n=== Ljung-Box白噪声检验 ===")

# 对原始序列进行白噪声检验
lb_result = acorr_ljungbox(series, lags=[6, 12, 18], return_df=True)

print("Ljung-Box检验结果:")
print("-" * 60)
print(f"{'滞后阶数':<10} {'LB统计量':<15} {'p值':<15} {'结论':<20}")
print("-" * 60)

for lag, row in lb_result.iterrows():
    lb_stat = row['lb_stat']
    p_value = row['lb_pvalue']
    
    if p_value < 0.05:
        conclusion = "拒绝H_0(非白噪声)"
    else:
        conclusion = "不能拒绝H_0(可能是白噪声)"
    
    print(f"{lag:<10} {lb_stat:<15.4f} {p_value:<15.6f} {conclusion:<20}")

# 生成并检验真正的白噪声序列
print("\n=== 对比:标准正态白噪声序列检验 ===")

# 生成标准正态白噪声
white_noise = np.random.normal(0, 1, 1000)
white_series = pd.Series(white_noise, 
                         index=pd.date_range('2021-01-01', periods=1000, freq='D'),
                         name='白噪声序列')

# 白噪声的LB检验
lb_white = acorr_ljungbox(white_series, lags=[6, 12], return_df=True)

print("白噪声序列检验结果:")
print("-" * 60)
print(f"{'滞后阶数':<10} {'LB统计量':<15} {'p值':<15} {'结论':<20}")
print("-" * 60)

for lag, row in lb_white.iterrows():
    lb_stat = row['lb_stat']
    p_value = row['lb_pvalue']
    
    if p_value < 0.05:
        conclusion = "拒绝H_0(非白噪声)"
    else:
        conclusion = "不能拒绝H_0(可能是白噪声)"
    
    print(f"{lag:<10} {lb_stat:<15.4f} {p_value:<15.6f} {conclusion:<20}")

# 绘制白噪声的时序图和ACF图
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 原始序列时序图
axes[0, 0].plot(series, linewidth=1, color='blue')
axes[0, 0].set_title('原始序列时序图', fontsize=12)
axes[0, 0].set_xlabel('日期')
axes[0, 0].set_ylabel('观测值')
axes[0, 0].grid(True, alpha=0.3)

# 原始序列ACF图
plot_acf(series, lags=20, ax=axes[0, 1], alpha=0.05, color='blue')
axes[0, 1].set_title('原始序列ACF图', fontsize=12)
axes[0, 1].set_xlabel('滞后阶数')
axes[0, 1].set_ylabel('自相关系数')
axes[0, 1].grid(True, alpha=0.3)

# 白噪声时序图
axes[1, 0].plot(white_series[:150], linewidth=1, color='green')  # 只显示前150个点
axes[1, 0].set_title('白噪声序列时序图(前150期)', fontsize=12)
axes[1, 0].set_xlabel('日期')
axes[1, 0].set_ylabel('观测值')
axes[1, 0].grid(True, alpha=0.3)

# 白噪声ACF图
plot_acf(white_series, lags=20, ax=axes[1, 1], alpha=0.05, color='green')
axes[1, 1].set_title('白噪声序列ACF图', fontsize=12)
axes[1, 1].set_xlabel('滞后阶数')
axes[1, 1].set_ylabel('自相关系数')
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

综合实战:完整预处理流程

下面我们将所有检验整合到一个完整的预处理流程中:

def time_series_preprocessing(data_series, series_name="时间序列"):
    """
    时间序列预处理完整流程
    
    参数:
    -----------
    data_series : pandas.Series
        待处理的时间序列数据
    series_name : str
        序列名称,用于输出标识
    
    返回:
    -----------
    dict : 包含所有检验结果的字典
    """
    
    print(f"=== {series_name} 预处理分析报告 ===\n")
    
    results = {}
    
    # 1. 基础描述统计
    print("1. 描述性统计")
    print("-" * 40)
    desc = data_series.describe()
    for stat, value in desc.items():
        print(f"  {stat}: {value:.6f}")
    results['description'] = desc
    
    # 2. 平稳性检验(ADF)
    print(f"\n2. 平稳性检验(ADF单位根检验)")
    print("-" * 40)
    adf_result = adfuller(data_series, autolag='AIC')
    
    adf_stat = adf_result[0]
    adf_p = adf_result[1]
    results['adf_statistic'] = adf_stat
    results['adf_pvalue'] = adf_p
    results['adf_critical_values'] = adf_result[4]
    
    print(f"  ADF统计量: {adf_stat:.6f}")
    print(f"  p值: {adf_p:.6e}")
    
    if adf_p < 0.05:
        print(f"  → 结论: 序列平稳 (p < 0.05)")
        results['is_stationary'] = True
    else:
        print(f"  → 结论: 序列非平稳 (p >= 0.05)")
        results['is_stationary'] = False
    
    # 3. 纯随机性检验(Ljung-Box)
    print(f"\n3. 纯随机性检验(Ljung-Box)")
    print("-" * 40)
    
    # 检验多个滞后阶数
    lags_to_test = [6, 12, 18]
    lb_result = acorr_ljungbox(data_series, lags=lags_to_test, return_df=True)
    results['lb_test'] = lb_result
    
    print(f"  检验滞后阶数: {lags_to_test}")
    print(f"  {'滞后阶数':<8} {'LB统计量':<12} {'p值':<15} {'结论':<20}")
    print(f"  {'-'*8} {'-'*12} {'-'*15} {'-'*20}")
    
    all_white_noise = True
    for lag, row in lb_result.iterrows():
        lb_stat = row['lb_stat']
        p_value = row['lb_pvalue']
        
        if p_value < 0.05:
            conclusion = "非白噪声"
            all_white_noise = False
        else:
            conclusion = "可能是白噪声"
        
        print(f"  {lag:<8} {lb_stat:<12.4f} {p_value:<15.6e} {conclusion:<20}")
    
    if all_white_noise:
        print(f"  → 综合结论: 序列可能是白噪声")
        results['is_white_noise'] = True
    else:
        print(f"  → 综合结论: 序列包含可提取信息(非白噪声)")
        results['is_white_noise'] = False
    
    # 4. 自相关分析
    print(f"\n4. 自相关分析")
    print("-" * 40)
    
    # 计算ACF和PACF
    acf_vals = acf(data_series, nlags=10, fft=False)
    pacf_vals = pacf(data_series, nlags=10)
    
    results['acf'] = acf_vals
    results['pacf'] = pacf_vals
    
    print(f"  前5阶自相关系数(ACF): {np.round(acf_vals[1:6], 4).tolist()}")
    print(f"  前5阶偏自相关系数(PACF): {np.round(pacf_vals[1:6], 4).tolist()}")
    
    # 5. 综合诊断建议
    print(f"\n5. 综合诊断与建模建议")
    print("-" * 40)
    
    if not results['is_stationary']:
        print("  → 序列非平稳,建议进行差分处理后再分析")
        print("  → 可考虑使用ARIMA模型(包含差分项)")
    elif results['is_white_noise']:
        print("  → 序列为白噪声,无进一步建模价值")
        print("  → 建议检查数据或更换分析对象")
    else:
        print("  → 序列平稳且包含可提取信息,适合进一步建模")
        print("  → 根据ACF/PACF截尾拖尾特征选择AR/MA/ARMA模型")
    
    return results

# 运行完整预处理流程
print("=" * 60)
preprocessing_results = time_series_preprocessing(series, "模拟自回归序列")
print("=" * 60)

📝 动手练一练

  1. 平稳性判断实战 使用以下代码生成三个不同的时间序列,分别判断它们的平稳性:

    import numpy as np
    import pandas as pd
    from statsmodels.tsa.stattools import adfuller
    
    np.random.seed(123)
    n = 200
    
    # 序列1:随机游走(非平稳)
    random_walk = np.cumsum(np.random.normal(0, 1, n))
    
    # 序列2:带趋势的序列(非平稳)
    trend = 0.05 * np.arange(n) + np.random.normal(0, 1, n)
    
    # 序列3:平稳AR(1)过程
    ar1 = np.zeros(n)
    for t in range(1, n):
        ar1[t] = 0.7 * ar1[t-1] + np.random.normal(0, 1)
    
    # 请分别对这三个序列进行ADF检验,判断它们的平稳性

    参考答案:

    # 对随机游走序列进行ADF检验
    adf_rw = adfuller(random_walk)
    print(f"随机游走: ADF统计量={adf_rw[0]:.4f}, p值={adf_rw[1]:.4f}")
    # 预期结果:p值 > 0.05,非平稳
    
    # 对带趋势序列进行ADF检验
    adf_trend = adfuller(trend)
    print(f"带趋势序列: ADF统计量={adf_trend[0]:.4f}, p值={adf_trend[1]:.4f}")
    # 预期结果:p值 > 0.05,非平稳
    
    # 对AR(1)过程进行ADF检验
    adf_ar1 = adfuller(ar1)
    print(f"AR(1)过程: ADF统计量={adf_ar1[0]:.4f}, p值={adf_ar1[1]:.4f}")
    # 预期结果:p值 < 0.05,平稳
  2. 白噪声检验对比 生成以下两个序列并进行Ljung-Box检验:

    from statsmodels.stats.diagnostic import acorr_ljungbox
    
    # 序列A:真正的白噪声
    white_noise_A = np.random.normal(0, 1, 500)
    
    # 序列B:具有自相关的序列
    correlated = np.zeros(500)
    for t in range(1, 500):
        correlated[t] = 0.6 * correlated[t-1] + np.random.normal(0, 1)
    
    # 请分别对这两个序列进行滞后6阶和12阶的LB检验

    参考答案:

    # 对白噪声序列进行LB检验
    lb_white = acorr_ljungbox(white_noise_A, lags=[6, 12], return_df=True)
    print("白噪声序列LB检验:")
    print(lb_white)
    # 预期结果:p值均大于0.05,不能拒绝白噪声原假设
    
    # 对自相关序列进行LB检验
    lb_corr = acorr_ljungbox(correlated, lags=[6, 12], return_df=True)
    print("\n自相关序列LB检验:")
    print(lb_corr)
    # 预期结果:p值均小于0.05,拒绝白噪声原假设

本章小结

本节我们系统学习了时间序列预处理的两大核心检验方法,并通过Python实战掌握了完整的分析流程:

关键要点回顾

  1. 平稳性检验是建模前提

    • 通过时序图直观判断趋势和周期性
    • 使用ADF单位根检验进行统计验证
    • 平稳序列具有常数均值、方差和仅依赖于时间间隔的自协方差
  2. 纯随机性检验避免无效建模

    • 白噪声序列无分析价值,相邻观测相互独立
    • Ljung-Box检验判断序列是否包含可提取信息
    • 非白噪声序列才值得进一步建模分析
  3. 自相关分析揭示序列结构

    • ACF图显示序列的自相关衰减模式
    • PACF图帮助识别AR模型的阶数
    • 结合ACF/PACF特征为模型选择提供依据
  4. Python现代化实现

    • 使用statsmodels库进行统计检验
    • pandas处理时间序列数据
    • matplotlib实现数据可视化

行动清单

学完本节后,你可以立即开始:

  1. 数据诊断:对你感兴趣的任何时间序列数据(股票价格、气温、销售额等)运行完整的预处理流程,判断其平稳性和随机性

  2. 代码实践:将本节提供的预处理函数封装成自己的工具库,方便后续分析直接调用

  3. 对比分析:寻找平稳与非平稳序列、白噪声与非白噪声序列的实际案例,通过对比加深理解

时间序列预处理是建模分析的第一步,也是至关重要的一步。正确的预处理能够避免”垃圾进,垃圾出”的问题,确保后续建模的有效性。在接下来的课程中,我们将基于平稳非白噪声序列,学习AR、MA、ARMA等经典时间序列模型。

— 小象教研组

配套学习资源与课件
  • 第2章课件:时间序列的预处理
    下载
  • 第2章配套代码
    下载
  • 时间序列分析推荐书籍(打包)(经典时序分析参考书合集)
    下载
  • 课程配套数据集(全课程实战数据)
    下载
🎁 免费学习资源

领取《小象 11GB VIP 课件资料包与大厂真题手册》

包含全套实战 Jupyter 源码、清洗后数据集、大厂高频面试真题与专属学员答疑交流群。

  • 完整 Python / 数据分析 Jupyter 实战源码
  • 大厂真实业务数据集与练习题
  • 微信扫码添加顾问免费领取;想学什么,直接告诉顾问
微信二维码:扫码添加课程顾问微信扫码添加顾问