本节摘要:周期长度不固定时,简单的 periodogram 难以稳定识别。Welch 估计通过分段平均提高稳健性,经验模态分解(EMD)则把信号自适应地拆为多个本征模态函数(IMF),每个 IMF 都有自己的瞬时频率。本节用一段合成 GDP 周期数据演示两种方法。
阅读完本节,你应当能够:
本节是"识别工具"专项:周期图太脆、Welch 用分辨率换稳健性、EMD 自适应分解为多个 IMF。读完后你能从一段不规则长波数据中挖出每个独立的振荡成分。
4.2 节介绍过周期图(periodogram)——它把序列拆成不同频率的复指数波,统计每个频率的能量。问题在哪?
Welch 估计把序列切成多段重叠的子序列,分别算 periodogram 再平均:
import numpy as np from scipy.signal import welch, periodogram import matplotlib.pyplot as plt # 合成 30 年月度 GDP:长趋势 + 5 年周期 + 噪声 rng = np.random.default_rng(0) n = 360 # 30 年 t = np.arange(n) trend = 0.05 * t cycle = 8 * np.sin(2 * np.pi * t / 60 + 0.3) y = trend + cycle + rng.normal(0, 1, n) s = pd.Series(y) # 简单 periodogram freqs_p, power_p = periodogram(s.values, fs=1.0) # Welch:分段长度 120 月(10 年),50% 重叠 freqs_w, power_w = welch(s.values, fs=1.0, nperseg=120, noverlap=60)
Welch 的代价是频率分辨率降低(分段后频率点更稀疏),但每个点的方差更小、估计更稳。工程上 Welch 几乎总是优于简单 periodogram。
💡 关键直觉:Welch 是"用分辨率换稳健性"。Nperseg 选 1/4 到 1/2 序列长度是常见经验。
经验模态分解(Empirical Mode Decomposition, EMD)是一种数据自适应的分解方法。它把任意信号拆成多个本征模态函数(Intrinsic Mode Function, IMF),每个 IMF 都满足两个条件:
直觉上,IMF 是"信号里最简单的振荡"——有自己的频率、振幅,但不要求全局平稳。
from PyEMD import EMD import numpy as np # 信号:长趋势 + 5 年周期 + 1 年周期 + 噪声 t = np.arange(360) cycle5 = 8 * np.sin(2 * np.pi * t / 60) cycle1 = 3 * np.sin(2 * np.pi * t / 12) y = 0.05 * t + cycle5 + cycle1 + rng.normal(0, 1, 360) emd = EMD() imfs = emd(y, t) # 返回 (n_imfs, n_samples) 数组 print(f"分解出 {imfs.shape[0]} 个 IMF + 1 个残差")
输出通常是 4–6 个 IMF + 1 个残差:
每个 IMF 都可以单独做频谱分析或建模。
⚠️ 常见坑:不要把 EMD 当成"无监督的 SARIMA"。EMD 输出的 IMF 数量由数据决定,每次跑结果可能略有不同;要保证结果可复现,可以固定随机种子或用 EEMD。
下面用一段合成 GDP 月度数据演示 EMD + Welch 的完整工作流:
import pandas as pd import numpy as np from PyEMD import EMD from scipy.signal import welch import matplotlib.pyplot as plt # 1) 合成 30 年月度 GDP 增速:长趋势 + 5 年周期 + 季度噪声 n = 360 t = np.arange(n) trend = 5 + 0.02 * t cycle = 1.5 * np.sin(2 * np.pi * t / 60 + 0.5) quarterly = 0.3 * np.sin(2 * np.pi * t / 3) y = trend + cycle + quarterly + np.random.default_rng(0).normal(0, 0.2, n) # 2) EMD 分解 emd = EMD() imfs = emd(y, t) # 3) 对每个 IMF 做 Welch 谱估计 fig, axes = plt.subplots(len(imfs) + 1, 1, figsize=(10, 8)) axes[0].plot(t, y, lw=0.6); axes[0].set_title("原始") for i, imf in enumerate(imfs): axes[i+1].plot(t, imf, lw=0.6) freqs, power = welch(imf, fs=1.0, nperseg=120) peak = freqs[np.argmax(power[1:]) + 1] period = 1 / peak if peak > 0 else np.inf axes[i+1].set_title(f"IMF{i+1}(主周期 ≈ {period:.1f} 月)") plt.tight_layout()
输出会展示:
每个 IMF 都可以进一步分析:长波 IMF 4 对应"经济周期",年度 IMF 3 对应"季节性"(如果数据本身没这么强季节性,可能要靠业务判断)。
周期性识别就像从一段混合录音里分辨出"小提琴""大提琴""定音鼓"各自的声音:
实战中常常三种方法一起用:先用 Welch 看大致频段,再对每个频段用 EMD 拆细节,最后用业务知识确认"这个 IMF 是不是真的经济周期"。
