《时间序列预测终极指南:从ARIMA到Prophet,7大模型算法解析与实战选型》
目录

一、ARIMA 模型族
前提:序列平稳
通用核心思想:非平稳序列先差分转化为平稳序列;利用序列自身滞后值(自回归 AR)、历史预测误差(移动平均 MA)建立线性模型。
1. ARIMA(p,d,q)
1.1 核心思想
通过d次差分消除趋势实现平稳,结合自回归项与移动平均项建模。 
:d阶差分后平稳序列;p自回归阶数;d差分阶数;q移动平均阶数;
:白噪声
- 公式讲解:使用差分消除趋势;由序列滞后信息与历史误差共同预测未来。
- 优点:统计学理论完善,可进行显著性检验;
- 缺点:原生不支持季节性;要求差分后平稳;长步预测效果差;
- 应用场景:无明显周期性的单变量时序,部分金融指标、平稳负荷序列。
1.2 计算流程
1. 差分算法(确定 d)【计算机的“平稳性体检”】,把序列化成平稳
计算机无法通过肉眼判断,它依赖假设检验的循环(Loop)。
执行逻辑:
单位根检验(ADF检验):计算当前序列的Dickey-Fuller统计量,得到p值。
判断分支:若
p-value > 0.05(无法拒绝非平稳原假设),则d = d + 1,执行y_t = Y_t - Y_{t-1}(一阶差分)。递归检验:对差分后的新序列再次进行ADF检验。重复上述过程,直到
p-value <= 0.05或达到预设最大差分阶数(通常硬性限制d <= 2)。隐藏的“防过差”算法:计算机不仅看平稳性,还会计算差分后的方差变化。如果差分后序列的方差比上一级增大了超过一定阈值(如标准差扩大20%)

2. 定阶算法(确定 p, q)【暴力搜索 + 信息论裁判】
确定差分阶数 d 后,计算机需要找出最合适的滞后期数 p(自回归)和 q(移动平均)。它不会去“看图”,而是采用带惩罚的网格搜索。
执行逻辑:
划定搜索空间:设定 p∈[0,Pmax],q∈[0,Qmax](通常 Pmax 和 Qmax 设为 5~7,因为高阶模型极易过拟合)。
暴力遍历:计算机生成所有 (p,q) 组合的笛卡尔积。例如,若最大值为5,则有 36 种组合。
对每个组合执行“快速拟合并打分”:
针对每一组 (p,q),利用条件最小二乘(CLS) 快速(不迭代)估算出大概的参数向量。
计算该模型的 AIC(赤池信息准则):AIC=2k−2ln(L),其中 k=p+q(参数个数),L 为似然函数值。
排序输出:遍历完毕后,计算机选取 AIC 值最小 的一组作为最优 (p,q)。(Hyndman-Khandakar 算法还会加入一步“逐步搜索”,比如在最优解附近微调,防止漏掉局部最优)。
3. 参数估计算法(算出 AR 和 MA 的系数)
这是ARIMA中最核心、计算量最大的环节。难点在于:MA(移动平均)项涉及不可观测的历史误差 ϵ,无法直接用最小二乘法闭式求解。
计算机采用“状态空间 + 卡尔曼滤波 + 迭代优化”的三层嵌套算法:
状态空间重构(State-Space Representation):
计算机将 ARIMA 方程改写为“状态方程”和“观测方程”,将不可见的 AR/MA 系数转化为状态变量。初始化“误差”(Backcasting/逆推法):
由于 q 阶之前的误差 ϵ0,ϵ−1...无法观测,计算机先用逆向预测(Backcasting),利用全部历史数据的反向递推,估算出初始时刻的残差初始值,作为迭代的“种子”。核心迭代(卡尔曼滤波 + 最大似然估计 MLE):
给定一组初始猜测的系数(例如全部设为 0.1),计算机启动卡尔曼滤波,沿着时间轴从头到尾递推一次。
每递推一步,卡尔曼滤波会计算预测误差,并根据预测误差实时更新内部状态(这就是为什么它能处理不可观测的 MA 误差)。
递推完整个序列后,计算机计算出一个负对数似然值(Negative Log-Likelihood)。
随后,计算机调用BFGS(拟牛顿法) 或 Nelder-Mead(单纯形法) 优化算法:优化算法微调系数(如将 AR(1) 从 0.1 改为 0.11),再次运行卡尔曼滤波,计算新的似然值。
收敛判定:当连续两次迭代的系数变化小于 10−6 或梯度接近零时,停止迭代。这组系数就是最终输出的 AR 和 MA 系数。
4. 预测算法:
当系数 ϕ(AR项)和 θ(MA项)被固定下来后,计算机开始生成预测。这里存在两种完全不同的预测模式:
一步预测(1-step ahead,用于拟合检验):
预测 t+1 时,计算机使用 t,t−1... 时刻的历史真实值,以及 t,t−1... 时刻的历史真实误差(残差)。
直接代入方程:Y^t+1=c+ϕ1Yt+...+θ1ϵt+....。
这种预测极其准确,但仅用于计算拟合优度。
多步预测(Multi-step ahead,即你想要的未来预测):
链式递推:预测 t+2 时,计算机发现 Yt+1 是未知的,于是它把上一步预测出的 Y^t+1 当作真实值代入方程。
致命算法约定:未来误差置零:对于 t+1 时刻之后的误差项 ϵt+1,ϵt+2...,计算机将其数学期望(即 0)直接代入,不再递归预测误差。
结果显现:正因为未来的随机冲击(误差)被强制归零,所以 ARIMA 的多步预测曲线在 3~5 步后迅速收敛到序列的长期均值(平稳序列中心),且置信区间呈扇形急剧扩大。
5. 额外补充:计算机如何处理“常数项” c?
算法还会判断是否包含常数项 c。在差分(d>0)后,如果序列均值显著不为零,算法会估计 c;若均值围绕零波动,则会强制令 c=0,因为包含多余的常数项会显著增大多步预测的累积误差(容易导致预测发散)。
6. 一句话总结这四步算法的计算机本质:
d:由机器循环执行差分 + 单位根检验决定(硬判断);
p, q:由机器执行全网格搜索 + AIC打分决定(贪心选择);
系数:由机器执行卡尔曼滤波递推计算似然值,并由 BFGS优化器 疯狂迭代数千次直至收敛(非线性求解);
预测值:由机器执行线性递推,并将所有未发生的误差强行设定为 0(期望归零)。
1.3 代码示例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.stattools import adfuller
import warnings
warnings.filterwarnings('ignore') # 忽略版本警告,保持输出清爽
配置中文字体,避免标题/标签显示为乱码
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
----- 1. 模拟数据(生成一个非平稳序列:线性趋势 + 白噪声)-----
np.random.seed(42)
n = 150
time = np.arange(n)
原始数据:斜率 0.5 的趋势 + 随机波动
data = 0.5 * time + np.random.normal(0, 3, n)
series = pd.Series(data, index=pd.date_range('2020-01-01', periods=n, freq='D'))
----- 2. 平稳性检验(确认是否需要差分)-----
ADF检验: p-value > 0.05 说明非平稳,需要差分
result = adfuller(series)
print(f"ADF 检验 p-value: {result[1]:.4f}") # 通常 p 值远大于 0.05,确认非平稳
----- 3. 差分处理(确定 d=1)并对差分后序列再次做平稳性检验 -----
一阶差分去除趋势
diff_series = series.diff().dropna()
diff_result = adfuller(diff_series)
p 值极小(如 2.6e-08)时用科学计数法显示,避免被 :.4f 截断成 0.0000 的假象
def fmt_p(p):
return f"{p:.2e}" if p < 0.0001 else f"{p:.4f}"
print(f"一阶差分后 ADF 检验 p-value: {fmt_p(diff_result[1])} (若 <0.05 则已平稳,d=1 足够)")
----- 3.5 差分效果可视化(上下对比:原序列 vs 一阶差分序列)-----
先弹出本图,关闭后才能继续后面的网格搜索与预测
fig, axes = plt.subplots(2, 1, figsize=(12, 7), sharex=True)
上图:原始序列(明显上升趋势 → 非平稳)
axes[0].plot(series.index, series, color='blue', label=f'原始序列 (非平稳, ADF p={result[1]:.4f})')
axes[0].set_title('差分前:带线性趋势,ADF p-value > 0.05 → 非平稳')
axes[0].legend()
axes[0].grid(True)
下图:一阶差分序列(围绕 0 上下波动 → 平稳)
axes[1].plot(diff_series.index, diff_series, color='green', label=f'一阶差分序列 (平稳, ADF p={fmt_p(diff_result[1])})')
axes[1].axhline(0, color='gray', linestyle='--', linewidth=0.8) # 0 参考线
axes[1].set_title('差分后:围绕 0 波动,ADF p-value < 0.05 → 平稳,d=1 足够')
axes[1].legend()
axes[1].grid(True)
plt.suptitle('一阶差分效果对比', fontsize=14)
plt.tight_layout()
plt.show() # 先看差分效果,关掉本窗口后继续执行网格搜索
----- 4. AIC 网格搜索选择最优 (p, q)(d 已由差分验证确定为 1)-----
p_range = range(0, 5) # p: 0~4
q_range = range(0, 5) # q: 0~4
d = 1
grid_results = [] # 保存所有 (p, q, AIC) 结果
best_aic = np.inf
best_order = None
best_model = None
print("\n===== 开始 AIC 网格搜索 (p×q = 5×5 = 25 种组合) =====")
for p in p_range:
for q in q_range:
try:
model = ARIMA(series, order=(p, d, q))
fitted = model.fit()
grid_results.append({'p': p, 'q': q, 'AIC': fitted.aic})
print(f"ARIMA({p},{d},{q}) AIC = {fitted.aic:>8.2f}")
# 记录最小 AIC 对应的最优模型
if fitted.aic < best_aic:
best_aic = fitted.aic
best_order = (p, d, q)
best_model = fitted
except Exception as e:
# 某些阶数组合可能无法收敛/估计失败,跳过并记录 NaN
print(f"ARIMA({p},{d},{q}) 拟合失败: {e}")
grid_results.append({'p': p, 'q': q, 'AIC': np.nan})
将网格搜索结果整理成二维表格展示(行=p,列=q)
grid_df = pd.DataFrame(grid_results)
aic_pivot = grid_df.pivot(index='p', columns='q', values='AIC')
print("\n===== AIC 网格搜索热力表格(行=p,列=q,数值越小模型越好)=====")
print(aic_pivot.round(2))
----- 4.5 导出 AIC 网格结果为 Excel(供 Origin 生成热力图)-----
export_file = 'AIC_网格热力图.xlsx'
注意:用 index_label='p' 让第一列(A列)是 p 值,第一行是 q 值,形成 Origin 可直接画热力图的矩阵格式
aic_pivot.to_excel(export_file, sheet_name='AIC', index_label='p')
print(f"\n>>> AIC 网格结果已导出: {export_file}(行=p,列=q,可直接在 Origin 中画热力图)")
print(f"\n>>> 网格搜索选出的最优模型: ARIMA{best_order} AIC = {best_aic:.2f}")
----- 5. 打印网格搜索最优模型的详细结果(作为"反例"展示)-----
注意:这里打印 ARIMA(3,1,4) 是故意的——用它的 summary 展示过拟合证据
(ma.L1~ma.L4 的 P>|z| 全 > 0.86、标准误高达 11+,明显无效参数)
真正用于预测/出图的最终模型,在下面第 6 节选定为简约推荐 ARIMA(1,1,2)
fitted_model = best_model
print("\n", fitted_model.summary()) # 查看 AR/MA 系数的显著性(P>|z|)
----- 6. 三方模型对比:网格最优 vs 简约推荐 vs 手动预设 -----
(2,1,2) 是原始代码里"凭经验预设"的模型,保留它作为对比基准,
代表"不经过网格搜索、拍脑袋选的模型"
manual_model = ARIMA(series, order=(2, 1, 2)).fit()
(1,1,2) 是简约推荐:AIC 与全局最优几乎并列(差 <2),但参数更少、更稳健
parsimonious_order = (1, 1, 2) # 最终推荐使用的模型阶数
parsimonious_model = ARIMA(series, order=parsimonious_order).fit()
后续预测/出图统一改用简约推荐模型(避免展示 AIC 最小但已过拟合的模型)
fitted_model = parsimonious_model
print("\n===== 模型效果对比(AIC 越小越好)=====")
print(f"网格搜索最优 ARIMA{best_order} : AIC = {best_aic:.2f} (AIC 最小,但注意检查系数是否过拟合)")
print(f"简约推荐 ARIMA{parsimonious_order} : AIC = {parsimonious_model.aic:.2f} (AIC 与最优差 <2,参数却更少)")
print(f"手动预设 ARIMA(2,1,2) : AIC = {manual_model.aic:.2f} (原始代码凭经验选的基准)")
print("(规则:AIC 相差 <2 算没差别,此时应选参数较少的简约模型)")
----- 7. 模型诊断:残差白噪声检验(Q检验)-----(针对最终推荐的简约模型)
判断标准看 summary 里的 Ljung-Box Q 检验:Prob(Q) > 0.05 说明残差无自相关
resid = fitted_model.resid
print(f"\n残差均值: {resid.mean():.6f}(ARIMA 残差均值不强制为 0,应以 Ljung-Box 检验为准)")
----- 8. 进行预测(未来 10 步),并输出 95% 置信区间 -----
forecast_steps = 10
forecast_result = fitted_model.get_forecast(steps=forecast_steps) # get_forecast 可同时拿到置信区间
forecast = forecast_result.predicted_mean
ci = forecast_result.conf_int()
print(f"\n未来 {forecast_steps} 天的预测值: \n{forecast}")
print(f"\n预测 95% 置信区间: \n{ci}")
----- 9. 可视化绘图 -----
plt.figure(figsize=(12, 5))
plt.plot(series.index, series, label='原始数据 (非平稳)', color='blue')
plt.plot(forecast.index, forecast, label=f'预测趋势 ARIMA{parsimonious_order}', color='red', marker='o', linestyle='--')
绘制置信区间(半透明带)
plt.fill_between(forecast.index, ci.iloc[:, 0], ci.iloc[:, 1],
color='red', alpha=0.2, label='95% 置信区间')
plt.axvline(x=series.index[-1], color='gray', linestyle=':', label='预测起始点')
plt.title(f'ARIMA{parsimonious_order} 简约推荐模型预测效果 (AIC={fitted_model.aic:.2f})')
plt.legend()
plt.grid(True)
plt.show()
运行结果
ADF 检验 p-value: 0.9757
SARIMAX Results
==============================================================================
Dep. Variable: y No. Observations: 150
Model: ARIMA(2, 1, 2) Log Likelihood -393.343
Date: Mon, 10 Aug 2026 AIC 796.685
Time: 12:12:36 BIC 811.705
Sample: 01-01-2020 HQIC 802.787
- 05-29-2020
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 -1.0618 0.162 -6.562 0.000 -1.379 -0.745
ar.L2 -0.2378 0.147 -1.615 0.106 -0.526 0.051
ma.L1 0.3906 0.156 2.500 0.012 0.084 0.697
ma.L2 -0.4378 0.177 -2.474 0.013 -0.785 -0.091
sigma2 11.4444 1.378 8.307 0.000 8.744 14.145
Ljung-Box (L1) (Q): 3.12 Jarque-Bera (JB): 0.09
Prob(Q): 0.08 Prob(JB): 0.96
Heteroskedasticity (H): 1.35 Skew: -0.01
Prob(H) (two-sided): 0.29 Kurtosis: 3.12
Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
残差均值 (应接近0): 1.179711
未来 10 天的预测值:
2020-05-30 74.132958
2020-05-31 73.687345
2020-06-01 74.459684
2020-06-02 73.745602
2020-06-03 74.320122
2020-06-04 73.879932
2020-06-05 74.210683
2020-06-06 73.964185
2020-06-07 74.147252
2020-06-08 74.011498
Freq: D, Name: predicted_mean, dtype: float64
其中网格搜索法是我们进行模拟指定的
真正的网格搜索法如下:
print("\n===== 开始 AIC 网格搜索 (p×q = 5×5 = 25 种组合) =====")
for p in p_range:
for q in q_range:
try:
model = ARIMA(series, order=(p, d, q))
fitted = model.fit()
grid_results.append({'p': p, 'q': q, 'AIC': fitted.aic})
print(f"ARIMA({p},{d},{q}) AIC = {fitted.aic:>8.2f}")
# 记录最小 AIC 对应的最优模型
if fitted.aic < best_aic:
best_aic = fitted.aic
best_order = (p, d, q)
best_model = fitted
except Exception as e:
# 某些阶数组合可能无法收敛/估计失败,跳过并记录 NaN
print(f"ARIMA({p},{d},{q}) 拟合失败: {e}")
grid_results.append({'p': p, 'q': q, 'AIC': np.nan})
将网格搜索结果整理成二维表格展示(行=p,列=q)
grid_df = pd.DataFrame(grid_results)
aic_pivot = grid_df.pivot(index='p', columns='q', values='AIC')
print("\n===== AIC 网格搜索热力表格(行=p,列=q,数值越小模型越好)=====")
print(aic_pivot.round(2))
----- 4.5 导出 AIC 网格结果为 Excel(供 Origin 生成热力图)-----
export_file = 'AIC_网格热力图.xlsx'
注意:用 index_label='p' 让第一列(A列)是 p 值,第一行是 q 值,形成 Origin 可直接画热力图的矩阵格式
aic_pivot.to_excel(export_file, sheet_name='AIC', index_label='p')
print(f"\n>>> AIC 网格结果已导出: {export_file}(行=p,列=q,可直接在 Origin 中画热力图)")
print(f"\n>>> 网格搜索选出的最优模型: ARIMA{best_order} AIC = {best_aic:.2f}")
----- 5. 使用网格搜索选出的最优模型,打印详细拟合结果 -----
fitted_model = best_model
print("\n", fitted_model.summary()) # 查看 AR/MA 系数的显著性(P>|z|)
搜索结果:

我们可以看到(2,1)和(4,3)的AIC值差不多,但是参数少,拟合也够好,也与最优几乎持平,性价比最高。故我们选择(p,q)=(1,2)作为网格搜索的最优结果。综上所述,可以得到最后训练的ARIMA模型参数:
model = ARIMA(series, order=(1, 1, 2)) #中间的“1”是差分数d,之前算出为1

2. SARIMA
2.1 核心思想
在 ARIMA 基础上增加季节差分、季节自回归、季节移动平均项,专门处理周期性时序。
2.2 SARIMA 与 ARIMA 的 5 大本质不同
多了 3 个超参数(P,D,Q,m):ARIMA 只有
(p,d,q),SARIMA 多了季节自回归阶数P、季节差分阶数D、季节移动平均阶数Q,以及固定的季节周期m(如 12 表示月度,7 表示周度)。差分逻辑不同(先剥皮再切块):ARIMA 只做
lag-1差分;SARIMA 强制先做季节差分(lag-m),再做普通差分(lag-1)。且算法硬性限制D + d ≤ 2。参数搜索策略不同(贪心避炸):ARIMA 是全网格暴力搜;SARIMA 为了避开数百倍的组合爆炸,采用分步贪心(先固定 p,q 搜 P,Q,再反过来搜 p,q),最后用爬山算法微调。
状态矩阵维度暴涨:拟合系数时,ARIMA 的状态向量维度通常 < 10;SARIMA 因引入
lag-m,维度会变成max(p, P×m, q, Q×m)。若m=12,矩阵维度直接突破几十,计算机必须强制加岭回归正则化防止矩阵奇异报错。预测曲线形态不同:ARIMA 多步预测快速收敛成一条直线(均值回归);SARIMA 因方程里强行绑定“去年同期”数值,预测曲线会持续保持波浪形状,但超过 2 个季节周期后波幅会扭曲。
2.3 代码演示
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.statespace.sarimax import SARIMAX
from sklearn.metrics import mean_absolute_error
plt.rcParams['font.sans-serif'] = ['SimHei'] # 修复matplotlib中文显示
plt.rcParams['axes.unicode_minus'] = False # 修复负号显示
# 1. 生成带明显年度季节性(m=12)的模拟数据
np.random.seed(42)
time = np.arange(120) # 10年数据
# 趋势 + 正弦波(周期12) + 噪声
data = 0.3 * time + 8 * np.sin(2 * np.pi * time / 12) + np.random.normal(0, 2, 120)
# 注意:本机 pandas 2.0.3 用 'M'('ME' 需 pandas>=2.2;pandas 2.2+ 若见 FutureWarning 可改 'ME')
series = pd.Series(data, index=pd.date_range('2010-01-01', periods=120, freq='M'))
划分训练集(前108个月)和测试集(后12个月)
train, test = series[:-12], series[-12:]
2. 拟合纯 ARIMA(1,1,1) —— 它不认识季节
model_arima = ARIMA(train, order=(1, 1, 1))
fit_arima = model_arima.fit()
forecast_arima = fit_arima.forecast(12)
3. 拟合 SARIMA(1,1,1)(1,1,1,12) —— 明确告诉它周期 m=12
model_sarima = SARIMAX(train,
order=(1, 1, 1), # 非季节 (p,d,q)
seasonal_order=(1, 1, 1, 12)) # 季节 (P,D,Q,m)
fit_sarima = model_sarima.fit(disp=False) # disp=False 静默迭代
forecast_sarima = fit_sarima.forecast(12)
4. 绘图对比(一眼看出差异)
plt.figure(figsize=(14, 5))
plt.plot(train.index, train, label='训练集', color='gray', alpha=0.6)
plt.plot(test.index, test, label='真实值(测试集)', color='black', linewidth=2)
ARIMA 预测(蓝色) —— 迅速变直
plt.plot(test.index, forecast_arima, label='ARIMA 预测(直线收敛)',
color='blue', marker='o', linestyle='--')
SARIMA 预测(红色) —— 保留波浪
plt.plot(test.index, forecast_sarima, label='SARIMA 预测(波浪形状)',
color='red', marker='s', linestyle='-', linewidth=2)
plt.title('SARIMA vs ARIMA 预测效果核心差异(红色维持波动,蓝色收敛为直线)')
plt.legend()
plt.grid(True)
plt.show()
打印误差对比
print(f"ARIMA 平均绝对误差: {mean_absolute_error(test, forecast_arima):.2f}")
print(f"SARIMA 平均绝对误差: {mean_absolute_error(test, forecast_sarima):.2f}")
运行结果:

我们可以看到,ARIMA是直线收敛,SARIMA为波形收敛,明显SARIMA的拟合效果更好,为什么?
| ARIMA | SARIMA | |
|---|---|---|
| 记忆长度 | p+q 期(很短) | p+q + 季节项 (P+D+Q)·m(可到一年) |
| 对季节数据的识别 | ❌ 看不见 | ✅ 结构内建 |
| 多步预测形态 | 直线/衰减 | 维持波浪 |
| 适合数据 | 无季节性的平稳/趋势序列 | 有固定周期(如月度/季度数据) |
ARIMA 不是"不能"波形,而是它的记忆长度不够、系数又难以稳定锁定周期;SARIMA 则是在数学结构里直接内置了"去年同期"的对照,所以对周期性数据是必然的选择。这就是为什么时序建模前要先用 ACF 图(我之前有一个作品说过这个事)或季节性分解(如 statsmodels 的 seasonal_decompose)确认周期。
3. ARIMAX
3.1 核心思想
ARIMA 基础上引入外部解释变量,捕捉除序列自身之外的外部影响因素。
:第k个外部变量;
外部变量系数
3.2 ARIMAX 与纯 ARIMA 的 5 大本质不同
输入数据结构质变(二维矩阵 vs 一维向量):ARIMA 只接受单一序列(一维);ARIMAX 的算法底层要求传入
endog(目标变量) 和exog(外部变量矩阵)。这导致状态空间模型的观测方程从 yt=...变成了 yt=β1Xt1+β2Xt2+...+AR/MA项。参数估计的联合迭代(不再是纯时序递推):纯 ARIMA 只优化 AR/MA 系数;ARIMAX 的优化器(BFGS)在每次迭代中,必须同步计算回归系数 β 和 AR/MA 系数。算法采用的是“带约束的极大似然估计(MLE)”,即先通过OLS估算 β 的初值,算出残差,再对残差拟合 ARIMA,反复交替迭代直至两组参数同时收敛。
定阶策略被“干扰”(X 吃掉了一部分自相关):纯 ARIMA 直接对原始序列做 ACF/PACF 定阶;ARIMAX 定阶时,算法会先剔除 X 的线性影响(计算偏残差),再对残差序列进行网格搜索确定 (p,q)。这意味着,加入强有力的 X 变量后,最优的 p,q往往会变小(因为 X 解释掉了原本需要滞后项来解释的波动)。
预测的“死穴”——必须提前知道 X 的未来值(致命差异):纯 ARIMA 预测未来只需要自己的历史值递推;ARIMAX 在调用
forecast()时,“必须同时传入未来所有步长的外部变量 Xt+1,Xt+2...Xt+1,Xt+2... ”。如果未来气温、促销力度未知,算法直接报错无法运行(这也正是它最大的应用限制)。矩阵求逆的“共线性”风险加剧:纯 ARIMA 的状态矩阵仅含滞后项,多重共线性较弱;ARIMAX 的矩阵里混入了外生变量(如气温、节假日哑变量),若 X 与时间趋势高度相关,或 X 之间存在多重共线性,卡尔曼滤波中的矩阵求逆会直接报
LinAlgError,算法必须依赖更强的岭回归正则化(L2惩罚)来强行稳定矩阵。
3.3 代码演示:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_error
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.statespace.sarimax import SARIMAX # 注意:Python中ARIMAX由SARIMAX类实现
plt.rcParams['font.sans-serif'] = ['SimHei'] # 修复matplotlib中文显示
plt.rcParams['axes.unicode_minus'] = False # 修复负号显示
# ----- 1. 模拟数据(基础趋势 + 外部变量X:促销活动)-----
np.random.seed(42)
n = 120
time = np.arange(n)
外部变量 X(促销计划,取 0/1 确定值)设计为"测试期脉冲式促销":
- 训练期(前100天):仅在中间有零星促销,让 ARIMAX 能学到促销系数 β≈10;
训练尾部(第80~99天)完全无促销 → 纯ARIMA从低位外推;
- 测试期(后20天):第3~8天突发促销(脉冲),其余无促销 → 纯ARIMA措手不及,
而 ARIMAX 提前拿到未来促销计划,能准确预测脉冲日的暴涨。
promotion = np.zeros(n)
promotion[35:41] = 1 # 训练期 35~40 天:促销(提供 β 的识别变异)
promotion[60:64] = 1 # 训练期 60~63 天:促销
promotion[102:108] = 1 # 测试期(index 100~119)第3~8天:突发促销脉冲
目标销量 Y = 基础趋势(0.2时间) + 促销带来的销量(系数10X) + 随机噪声
sales = 0.2 * time + 10 * promotion + np.random.normal(0, 2, n)
series = pd.Series(sales, index=pd.date_range('2020-01-01', periods=n, freq='D'))
exog_df = pd.DataFrame({'promotion': promotion}, index=series.index)
划分训练集(前100天)和测试集(后20天)
train_series = series[:-20]
test_series = series[-20:]
train_exog = exog_df[:-20]
test_exog = exog_df[-20:] # 关键:未来20天的促销情况必须已知!
----- 2. 纯 ARIMA(2,1,0) —— 忽视外部变量 -----
model_arima = ARIMA(train_series, order=(2, 1, 0))
fit_arima = model_arima.fit()
forecast_arima = fit_arima.forecast(20) # 不需要传 X,纯靠自己猜
----- 3. ARIMAX 模型 —— 引入促销变量 (外生矩阵) -----
order=(p,d,q) 依旧,但通过 exog 传入外部变量
model_arimax = SARIMAX(train_series,
order=(2, 1, 0), # ARIMA部分
exog=train_exog, # 训练期间的外部变量
enforce_stationarity=False,
enforce_invertibility=False)
fit_arimax = model_arimax.fit(disp=False)
【核心差异点】预测未来20天,必须同时传入未来20天的促销计划!
forecast_arimax = fit_arimax.forecast(steps=20, exog=test_exog)
----- 4. 可视化对比 -----
plt.figure(figsize=(14, 5))
plt.plot(train_series.index, train_series, label='训练集', color='gray', alpha=0.6)
plt.plot(test_series.index, test_series, label='真实销量', color='black', linewidth=2)
纯ARIMA预测(忽视促销导致低估)
plt.plot(test_series.index, forecast_arima, label='纯ARIMA(忽视促销)',
color='blue', linestyle='--', marker='o')
ARIMAX预测(捕捉到促销带来的销量暴涨)
plt.plot(test_series.index, forecast_arimax, label='ARIMAX(引入促销外生变量)',
color='red', linewidth=2, marker='s')
plt.axvline(x=train_series.index[-1], color='gray', linestyle=':', label='预测起点')
plt.title('ARIMAX vs ARIMA 核心差异:外部变量捕捉突变冲击')
plt.legend()
plt.grid(True)
plt.show()
打印误差对比
print(f"纯ARIMA 误差 (忽视外部因素): {mean_absolute_error(test_series, forecast_arima):.2f}")
print(f"ARIMAX 误差 (引入促销变量): {mean_absolute_error(test_series, forecast_arimax):.2f}")

3.4 ARIMAX vs ARIMA 的核心结论
在同一道题里 ARIMAX 更好,根本原因:数据是"促销直接决定销量"的机制,且 ARIMAX 预测时能收到未来的促销计划。
销量 = 0.2×时间 + 10×促销 + 小噪声(σ≈2)
促销系数 10 远大于噪声,属于主导性、可预期的外部冲击。预测测试期第 3~8 天突发促销时:
| 预测逻辑 | 误差 (MAE) | |
|---|---|---|
| 纯 ARIMA | 只看过去销量,不知道促销要来 → 低位直线外推,撞上真实值的暴涨尖峰 | 5.90 |
| ARIMAX | 提前拿到未来 20 天促销计划(exog=test_exog),β≈10 直接把脉冲预测出来 | 3.39 |
ARIMAX 的优势本质是"先知"——它把促销→销量的因果关系做进了模型结构(多一项 β·X_t),而 ARIMA 只能靠历史销量猜。
二、现代时间序列模型
1. Prophet(Facebook)
1.1 适用场景
日/周/月维度的商业运营指标(日活、订单量、网站流量)。数据有明显年/周周期性、存在节假日冲击(双11、春节),且大量缺失值或异常值无需清洗。
1.2 核心操作步骤:
- 准备数据框:必须包含两列,
ds(日期时间)和y(数值)。- 实例化模型:设置趋势变点灵敏度(
changepoint_prior_scale)和季节性。- 添加自定义节假日(如春节、双11)。
- 拟合模型 + 生成未来时间框架 + 预测。
1.3 代码骨架:
from prophet import Prophet
1. 准备数据(列名必须为 ds 和 y)
df = pd.DataFrame({'ds': dates, 'y': sales})
2. 实例化并添加节假日
model = Prophet(yearly_seasonality=True, weekly_seasonality=True)
model.add_country_holidays(country_name='US') # 或手动 model.add_seasonality()
3. 拟合与预测
model.fit(df)
future = model.make_future_dataframe(periods=30) # 未来30天
forecast = model.predict(future)
4. 出图(自带可视化)
fig1 = model.plot(forecast)
fig2 = model.plot_components(forecast)
2. TBATS
2.1 适用场景
数据同时存在多个不同长度的周期(如电力负荷:日周期24小时 + 周周期168小时;或交通流量:周周期 + 年周期),且你懒得手动调参去设周期。
2.2 核心操作步骤
- (Python需安装
tbats包)传入单变量序列(numpy数组)。- 指定可能的周期列表(如
[7, 365.25]),算法会自动判断周期是否显著。- 拟合模型(内部自动进行 Box-Cox 变换和 ARMA 误差修正)。
- 调用
forecast获取多步预测。
2.3 代码骨架
from tbats import TBATS
1. 准备数据(一维数组)
y = series.values
2. 实例化(指定可能周期,算法自动筛选)
estimator = TBATS(seasonal_periods=[7, 365.25], # 常见周期
use_box_cox=True, # 自动决定是否变换
use_trend=True,
use_damped_trend=True)
3. 拟合与预测
fitted_model = estimator.fit(y)
y_forecast = fitted_model.forecast(steps=30) # 未来30步
还可输出 fitted_model.summary() 查看最终选中的周期
3. VAR / VECM
3.1 VAR (p) 向量自回归
3.11 适用场景
多个经济/业务指标相互影响,需要联合预测且不关心谁因谁果(如 GDP、CPI、失业率)。要求所有变量平稳(不平稳需先差分)。
3.12 核心操作步骤
- 将多个时间序列合并为二维矩阵(行=时间,列=变量)。
- 进行 ADF 单位根检验,非平稳则做差分至平稳。
- 确定最优滞后期数(通过 AIC/BIC 遍历
maxlags)。- 用选定的滞后阶数拟合 VAR 模型,并预测未来多步。
3.13 代码骨架
from statsmodels.tsa.vector_ar.var_model import VAR
from statsmodels.tsa.stattools import adfuller
1. 准备多变量数据(DataFrame,每列一个变量)
data = df[['GDP', 'Inflation', 'Unemployment']]
2. 平稳性检验(略),若非平稳做差分
data_diff = data.diff().dropna()
3. 定阶(自动选择最佳滞后)
model = VAR(data_diff)
lag_order = model.select_order(maxlags=15) # 打印 lag_order.aic 最优值
best_lag = lag_order.aic # 或手动指定
4. 拟合与预测
fitted = model.fit(best_lag)
预测未来5步(需要传入最近 best_lag 期的历史值)
forecast_input = data_diff.values[-best_lag:]
forecast = fitted.forecast(y=forecast_input, steps=5)
print(forecast) # 返回二维数组,行=未来时间,列=各变量预测值
3.2 VECM 向量误差修正模型
3.21 适用场景
多个变量本身不平稳(带随机游走),但它们在经济学上存在长期稳定比例关系(如现货价格与期货价格、汇率与利率)。需要同时刻画长期均衡和短期偏离修正。
3.22 核心操作步骤
- 输入原始非平稳的多变量数据(不要提前差分)。
- 进行 Johansen 协整检验,确定协整秩
r(即存在多少个长期均衡关系)。- 指定
coint_rank = r和短期滞后期k_ar_diff,拟合 VECM。- 调用
predict获取预测(VECM 会内生地处理短期波动向长期均衡回归的过程)。
3.23 代码骨架
from statsmodels.tsa.vector_ar.vecm import VECM, select_coint_rank
1. 准备原始非平稳数据(DataFrame)
data = df[['Spot_Price', 'Futures_Price']] # 假设两者有协整关系
2. 确定协整秩(Johansen检验)
rank_result = select_coint_rank(data, det_order=0, k_ar_diff=1) # k_ar_diff为短期滞后
print(rank_result.rank) # 输出最优协整秩,假设为 1
3. 拟合 VECM(指定协整秩=1)
model = VECM(data, coint_rank=1, k_ar_diff=1) # k_ar_diff=1表示短期带1阶滞后
fitted = model.fit()
4. 预测未来5步
forecast = fitted.predict(steps=5)
注意:VECM的预测结果会呈现出“向长期均衡回归”的修正特性
4. 终极算法本质一句话总结(区别于传统 ARIMA)
| 模型 | 算法内核本质 | 到底在算什么? |
|---|---|---|
| Prophet | 分段线性回归 + 傅里叶级数拟合 | 全局最小二乘解,不递推,纯回归。 |
| TBATS | 指数平滑状态空间 + 三角季节分解 | 卡尔曼滤波递推状态,但周期自动挖掘。 |
| VAR | 多变量最小二乘矩阵求解(OLS) | 把所有变量滞后项做线性回归,矩阵求逆一次出结果。 |
| VECM | 矩阵特征值分解(秩分解) + 降秩回归 | 先找协整特征向量,再修正短期系数。 |
三、完整模型对比总结表(7大时序模型)
| 模型 | 核心侧重点(算法灵魂) | 最适合的使用场景 | 适合的题目 | 致命短板 |
|---|---|---|---|---|
| ARIMA | 单变量自回归:只用自己的过去和过去的误差线性外推。 | 无明显周期、平稳化后的单变量金融指标、平滑负荷。 | 数据无季节波动,差分后平稳,只有一个变量。 | 长步预测必收敛为直线;原生完全不认“去年同期”。 |
| SARIMA | 单变量+固定周期:在ARIMA基础上强行绑定“去年同期”的数值。 | 有固定月度(12)、季度(4)或周度(7)周期的单变量数据。 | 数据有明显的单一固定周期(如每月固定高峰),且周期已知。 | 只能处理一个季节周期;多周期(如日+周+年)直接崩溃。 |
| ARIMAX | 单变量+外部先知:把“已知的未来外部冲击(X)”当作额外输入特征。 | 外部因素主导的预测(促销、天气预报、节假日日期表)。 | 未来外部变量取值已知(如未来一周天气已报、促销已排期)。 | 预测时必须提前拿到X的未来值;拿不到就无法运行。 |
| Prophet | 全局分段回归:把趋势切成几段直线,用傅里叶级数拟合周/年,并加入自定义节假日。 | 日/周/月维度的商业KPI(DAU、订单量、网站流量)。 | 有年/周周期性、有明确节假日冲击、数据有缺失或异常值懒得清洗。 | 默认加法结构;数据量太少(<2个周期)时拟合效果极差。 |
| TBATS | 多重周期自动挖掘:用指数平滑+三角分解,自动识别多个不同长度的周期。 | 电力负荷(24小时+168小时)、城市交通流量(周+年)。 | 同时存在多个不同长度的周期(如日周期+周周期),且不想手动定周期。 | 模型极复杂,训练极慢;结果解释性差(老板看不懂)。 |
| VAR | 多变量联动(全量OLS):把几个变量的所有历史滞后值放在一个矩阵里做回归。 | 宏观经济多指标联合预测(GDP、CPI、失业率)。 | 多个变量相互影响,且所有变量均平稳(或差分后平稳)。 | 变量一旦超过8个,参数呈平方级爆炸;仅捕捉线性关系。 |
| VECM | 多变量长期均衡:在VAR基础上加了个“纠偏项”,拉回长期稳定比例关系。 | 现货与期货价格、汇率联动、大宗商品套利分析。 | 多变量本身不平稳,但长期存在稳定比例关系(协整)。 | 建模流程极其繁琐(必须先做Johansen协整检验);超纲题常用来坑人。 |
更多推荐


所有评论(0)