简介:基于ARIMAX的多变量预测模型Python源码与配套数据集,面向有一定时间序列分析基础、希望用Python实现多元外生变量预测的读者,常用于经济指标、销量预测、能源负荷等场景,也是科研与竞赛中常用的预测方案。压缩包内共7个文件:2个Python脚本分别承担数据预处理与ARIMAX建模训练,2个CSV文件提供示例多变量数据,2张图片辅助展示数据特征与模型结果,另有1份Markdown说明文档梳理全流程。所有代码均带注释,配合README可从数据读取、平稳性检验、外生变量构造、参数调优到模型评估逐步理解,方便替换自己的数据快速复用,覆盖实用建模闭环。整个包仅146KB,轻量专注,已有408人学习/下载,是多变量时间序列建模入门与工程落地的直接参考,尤其适合课程设计与项目原型验证。
1. ARIMAX 多变量预测模型:这份源码包能解决什么问题
提到时间序列预测,绝大多数人第一个想到的是 ARIMA。但单变量 ARIMA 只吃自己的历史值,当预测目标明显受其他变量影响时——比如气温影响空调销量、广告投放影响网站流量——ARIMA 就给不出令人满意的答案。ARIMAX(带外生变量的自回归移动平均模型)就是在 ARIMA 的框架里多加一组输入 X,把外部影响和自身惯性一起建模。这份源码包恰好覆盖了从数据预处理、平稳性检验、定阶、拟合到预测评估的完整链路,适合拿来做课程设计,也适合作为多变量预测的起步模板。包里两个 Python 脚本、两组 CSV 数据集和 README 都齐,解压后对照注释改参数就能跑通。
2. ARIMAX 原理与建模思路:外生变量是怎么进入时间序列模型的
2.1 ARIMA 到 ARIMAX:多了一组输入,模型发生了什么变化
ARIMA 模型由三部分组成:自回归项 AR(p) 用目标变量自身的历史值解释当前值,移动平均项 MA(q) 用过去的预测误差修正当前值,差分阶数 d 负责把非平稳序列变成平稳序列。它的表达式可以粗略理解为:当前值等于常数项加上前 p 个自身历史值的加权和,再加上前 q 个误差项的加权和,最后加一个白噪声。
ARIMAX 在这个基础上增加了一行:把外生变量 X 的当期值(也可以包含滞后项)以线性回归的形式加入方程。表达式大致变成:当前值等于常数项加上外生变量的线性组合,加上 AR 项、MA 项和误差项。注意这里的 X 和普通线性回归里的特征不太一样——它不负责解释全部方差,而是在 ARIMA 已经处理了目标变量自相关结构之后,补充外部因素带来的增量影响。
这里有个关键区别:普通回归模型假设误差独立,但时间序列的误差往往是自相关的。ARIMAX 的好处在于,AR 和 MA 项已经把自相关结构吸收了,X 的系数估计才不会被残差自相关污染。换句话说,外生变量的系数是「扣除历史惯性之后」的净效应,这比直接拿 X 和目标变量做 OLS 回归要可靠得多。
我在看这份源码的时候,注意到 arimax.py 里对 X 的处理顺序是放在差分之后的,这个顺序很关键。statsmodels 的 SARIMAX 类在建模时会先对目标变量做差分,而外生变量是作为回归项直接进入状态空间方程的,所以外生变量本身是否平稳,模型不会自动帮你处理。要不要对外生变量也做差分或变换,需要自己判断,这也是后面避坑章节要展开的重点。
2.2 适用场景:什么时候该用 ARIMAX,什么时候别用
ARIMAX 最适合的场景是:目标变量有明确的外部驱动力,而且这些外生变量在预测未来时也能拿到。我见过比较典型的用法是门店销售预测——把天气温度、节假日标记、促销活动作为外生变量,预测未来一周的日销售额。这类场景中外生变量要么是已知的计划值,要么是气象预报值,未来值可得,模型才有意义。宏观指标预测也类似,比如用工业增加值、货币供应量等指标预测 GDP 季度的走势。
反过来,有三种情况我建议直接放弃 ARIMAX。第一,外生变量本身的未来值拿不到,比如你想用「消费者信心指数」预测消费,但这个指数发布滞后,预测当期时它的最新值还没公布,输入就是残缺的。第二,目标变量和外生变量存在强双向因果,比如股价和成交量互相影响,你用成交量预测股价,本质上是在用内生变量当外生变量,系数解释会失控。第三,金融高频序列这种信噪比极低的场景,很多做量化交易的人拿 ARIMAX 去预测价格,结果往往不如随机游走假设。不是说模型不行,而是金融数据的驱动机制远超「线性外生变量」能表达的范围。
那这份源码适合拿来做什么?从它配套的数据集看,data.csv 和 datacf.csv 是典型的目标序列加外部因素序列,比较接近经济指标或运营监控数据的组织方式。拿它做课程设计、做毕业论文的实证部分,或者作为你第一个多变量预测模型来练手,都比从零开始写合适。README 里把文件关系和运行顺序写清楚了,对新手友好程度不错。
2.3 选型落点:statsmodels 里没有 ARIMAX 类,用 SARIMAX 传 exog
一个容易让新人卡壳的点是:statsmodels 里找不到一个叫 ARIMAX 的类。ARIMAX 的实现在 statsmodels 里被统一收纳在SARIMAX这个类下面——它本来是为了处理带季节性的 SARIMA 模型设计的,但加上了exog参数之后,它同时也是一个功能完整的 ARIMAX 实现。你只需要传入order=(p, d, q)和exog=X,模型内部就会把外生变量当回归项处理。
看一下它的调用形态:
from statsmodels.tsa.statespace.sarimax import SARIMAX # y 是目标变量序列,X 是外生变量矩阵(可以是多列) model = SARIMAX(y, exog=X, order=(1, 1, 1)) result = model.fit(disp=False)disp=False是为了关掉迭代过程的输出,训练的时候不会刷屏。这里的order=(1, 1, 1)意思是 p=1、d=1、q=1。p 和 q 的取值通常靠 ACF/PACF 图或者 AIC 网格搜索确定,d 的取值靠平稳性检验确定,这些在 arimax.py 里都有对应的实现逻辑。
把这个 API 映射到这份源码包上,arimax.py 里的核心流程应该是:先读取数据处理好的目标序列和外生变量,做平稳性检验确定 d,再用 AIC 遍历确定 p 和 q,最后用 SARIMAX 拟合和预测。我拆这份代码时也是按这条主线去读的,读完基本就能把这套逻辑迁移到自己的数据集上。
3. 环境与文件梳理:把压缩包跑通的最小路径
3.1 Python 环境与依赖安装
这份源码是基于 Python 写的,依赖集中在 pandas、numpy、statsmodels、matplotlib 这四个库上。pandas 负责数据读取和对齐,numpy 做数组运算,statsmodels 提供 SARIMAX 模型,matplotlib 用来画预测对比图。建议直接用 pip 安装:
pip install pandas numpy statsmodels matplotlib如果你用的是 Anaconda,也可以换成conda install pandas numpy statsmodels matplotlib。版本方面没有特别苛刻的要求,Python 3.8 以上都能跑,statsmodels 的 API 在近几个版本里对SARIMAX的调用方式保持一致,所以不太需要担心版本冲突。在 vscode 里配好 Python 解释器之后,直接在终端里运行脚本就行。
我自己拿到压缩包的习惯是先建一个干净的虚拟环境再装依赖,避免和系统 Python 里的包互相污染。创建虚拟环境的命令:
python -m venv arimax_env source arimax_env/bin/activate # Windows 下是 arimax_env\Scripts\activate pip install pandas numpy statsmodels matplotlib装好依赖之后,最好先跑一个最简单的导入测试,确认 statsmodels 能正常加载。很多时候报错不是源码的问题,而是环境里缺包或者包的版本不对。
3.2 压缩包文件结构与各文件用途
解压之后你会看到六个东西:两个 Python 脚本、两个 CSV 数据集、一个 README、两张运行截图。文件分工如下表:
| 文件 | 作用 |
|---|---|
| README.md | 运行说明,包含执行顺序和预期输出 |
| datapre.py | 数据预处理脚本,负责读取原始数据、对齐时间索引、处理缺失值 |
| arimax.py | 主模型脚本,负责定阶、建模、预测、评估 |
| data.csv | 目标变量序列(内生变量) |
| datacf.csv | 外生变量序列(从文件名看 cf 大概率是控制因素/协变量) |
| 微信图片_*.png | 运行结果截图,方便对照输出格式 |
两个 Python 脚本的分工很清晰:datapre.py 是上游,arimax.py 是下游。运行顺序上必须先跑 datapre.py 把数据处理好,再跑 arimax.py 建模。如果你直接运行 arimax.py 报错找不到数据,基本就是因为跳过了预处理步骤。
3.3 最小复现路径:从解压到出预测图
要在你自己的机器上把这份源码跑通,按下面步骤来:
# 1. 进入解压目录 cd 你解压的目录路径 # 2. 先跑数据预处理 python datapre.py # 3. 再跑模型训练与预测 python arimax.py跑完之后屏幕上应该会输出模型拟合的摘要信息、预测评价指标(比如 RMSE 或 MAE),并且弹出或保存一张预测值和真实值的对比图。压缩包里的两张微信图片就是当时跑出来的截图,可以拿来对照自己的输出是否正常。
这里有一个小提醒:Windows 下不要直接双击.py文件运行,闪退之后没有任何报错信息可查。应该打开终端(cmd 或 PowerShell),先cd到目录,再执行python xxx.py。这样即使报错也能看到完整的 traceback。如果你用的数据文件不在脚本同级目录,记得改脚本里的路径变量,这个在 README 里应该也提到了。
4. 核心代码拆解:datapre 到 arimax 的完整预测链路
4.1 datapre.py:数据对齐和缺失值处理是第一步
拿到时间序列数据,第一件事永远是把两张表的索引对齐。data.csv 和 datacf.csv 的采样频率和起止时间未必完全一致,直接扔进模型一定会报错。datapre.py 里核心做的事情就是把两张表读进来、统一时间格式、按时间索引对齐、处理缺失值。
常见的处理逻辑是这样的:
import pandas as pd # 读取目标变量和外生变量 y_raw = pd.read_csv("data.csv", parse_dates=["date"], index_col="date") X_raw = pd.read_csv("datacf.csv", parse_dates=["date"], index_col="date") # 按时间索引对齐,left 保证目标变量的时间点全部保留 df = y_raw.join(X_raw, how="left") # 缺失值处理:先向前填充,再向后填充,兜底用线性插值 df = df.fillna(method="ffill").fillna(method="bfill").interpolate() # 去掉完全没数据的行 df = df.dropna()逻辑说明:parse_dates把日期列解析成时间类型,index_col把日期列设为索引;join里的how="left"表示以目标变量 y 的时间点为准,X 里多出来的时间点会被舍弃,这样可以保证模型训练时 y 和 X 的行数严格一致。缺失值处理顺序是先向前填充再向后填充,最后用插值兜底,这个顺序是为了避免序列开头和结尾的缺失值处理不了。
参数说明:fillna(method="ffill")表示用上一个有效值填充,适合处理偶发的缺失点;interpolate()对连续缺口的处理更平滑。如果你的数据缺口特别大,建议回头检查数据源,而不是依赖填充。
这一步最容易翻车的地方是join之后行数变化。y 有 500 条记录,X 只有 400 条,left join 之后有 100 行 X 是 NaN,如果直接 dropna 会把这 100 行丢掉,模型实际训练样本就缩水了。这块属于数据质量问题,不是代码问题,得回到业务层面判断要不要补数据。
4.2 arimax.py:平稳性检验与定阶
ARIMAX 的建模起点是确定差分阶数 d。常见做法是用 ADF 检验判断序列是否平稳,p 值大于 0.05 就做一阶差分,再检验,直到序列平稳。这份源码里大概率封装了这样一个循环:
from statsmodels.tsa.stattools import adfuller def find_diff_order(series, max_d=2): s = series.copy() for d in range(max_d + 1): p_value = adfuller(s.dropna(), autolag="AIC")[1] if p_value < 0.05: return d # 当前阶数下序列已经平稳 s = s.diff() return max_d逻辑说明:adfuller返回的第二个元素是 p 值,小于 0.05 可以拒绝「存在单位根」的原假设,认为序列平稳。每做一次差分就对差分后的序列重新检验,最多试到 max_d 阶;如果超过两阶还不行,可能是数据有季节性或者结构突变,需要另外处理。
参数说明:autolag="AIC"表示检验时用 AIC 自动选择滞后阶数;max_d=2限定了最大差分阶数,实际建模中超过 2 阶的差分会让模型解释变得困难,我一般不会继续往上加。
确定 d 之后再确定 p 和 q。经验做法是画 ACF 和 PACF 图看截尾情况,但看图定阶这回事多少有点玄学——不同人看同一个图能定出不同的阶数。所以我更推荐用网格遍历加 AIC 选优,把这个过程自动化:
import warnings warnings.filterwarnings("ignore") best_aic, best_order = float("inf"), None for p in range(5): for q in range(5): try: model = SARIMAX(y_train, exog=X_train, order=(p, d, q)) result = model.fit(disp=False) if result.aic < best_aic: best_aic, best_order = result.aic, (p, d, q) except Exception: continue逻辑说明:遍历 p 和 q 从 0 到 4 的全部组合,用 AIC 作为模型质量的衡量指标。AIC 在拟合优度和模型复杂度之间取平衡,值越小越好。try-except是必要的——某些参数组合下模型可能收敛失败,直接跳过就行,不影响最终选优。
参数说明:order=(p, d, q)里的 d 来自上一步的find_diff_order;exog=X_train必须传,否则这就退化成普通 ARIMA 了。这个网格的规模是 5×5=25 个组合,每个组合拟合一次,数据量不大的时候几秒就能跑完。
4.3 模型拟合与预测评估
定好阶之后,整个流程进入最后一段:划分训练测试集、拟合模型、做预测、算指标。这块的代码逻辑是整份源码最核心的部分:
from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np # 按 8:2 划分训练集和测试集 split_idx = int(len(df) * 0.8) y_train, y_test = df["y"].iloc[:split_idx], df["y"].iloc[split_idx:] X_train, X_test = df[["x1", "x2"]].iloc[:split_idx], df[["x1", "x2"]].iloc[split_idx:] # 用最优参数拟合 final_model = SARIMAX(y_train, exog=X_train, order=best_order) final_result = final_model.fit(disp=False) # 预测测试集长度对应的未来值,必须同时传入未来外生变量 forecast = final_result.get_forecast(steps=len(y_test), exog=X_test) pred_mean = forecast.predicted_mean # 计算评估指标 rmse = np.sqrt(mean_squared_error(y_test, pred_mean)) mae = mean_absolute_error(y_test, pred_mean)逻辑说明:get_forecast(steps=n, exog=X_test)返回一个对象,predicted_mean是点预测结果。这里最容易忽略的是exog=X_test——ARIMAX 预测未来时,外生变量的未来值必须由你自己提供,模型不会自动生成。你要预测未来 10 期,就必须传给模型未来 10 期的 X 数据。
参数说明:steps=len(y_test)表示预测的期数等于测试集长度,这样预测值和真实值才能逐位对比;RMSE和MAE都是越小越好,RMSE 对大误差更敏感,如果预测在某些时间点偏差特别大,RMSE 会明显拉开差距。
预测完成之后,再把预测值和真实值画在一张图上,视觉上确认预测是跟着真实走势走的。压缩包里那两张微信图片就是运行到这一步的产出。到这一步,这个包的完整链路就跑通了。
5. ARIMAX 常见问题与避坑:五个容易翻车的多变量预测细节
5.1 exog 行数和 y 对不齐,拟合时报 ValueError
现象:运行 arimax.py 到model.fit()这一步,报错提示exog must have the same number of rows as the endog。
原因:外生变量和目标变量的行数不一致。datapre.py 处理后如果dropna()把部分行删掉了,或者两张表的时间索引没有对齐,就会出现这个错。另一种情况是你自己换了数据集,X 的日期范围比 y 窄。
解决:先用print(len(y), len(X))确认两个序列的长度,再检查df.index有没有重复值。对齐的逻辑应该以目标变量的时间索引为基准做 left join,不要把两张表直接横向拼接。处理完缺失值之后,最后再打印一次长度,确保完全一致再进模型。
5.2 预测时报错或结果全是 NaN:未来外生变量没传
现象:模型训练成功,但get_forecast()报错提示需要提供外生变量,或者预测结果返回的全是 NaN。
原因:ARIMAX 和 ARIMA 在预测阶段的差异就在这里——ARIMAX 必须知道预测期内每个时间点的外生变量值,否则状态空间方程算不出来。很多人习惯了 ARIMA 的get_forecast(steps=n)不带额外参数,迁移到 ARIMAX 时漏传了exog。
解决:在调用get_forecast之前,先确认 X_test 的长度等于 steps。如果要用模型预测未来的具体日期,那段时间的外生变量你得自己准备——常见做法是直接用历史均值、单独跑一个预测模型、或者用它实际对应的业务计划值。没有未来 X 的数据,ARIMAX 就预测不了。
5.3 差分之后预测曲线变成一条水平线,外生变量完全没起作用
现象:预测结果的曲线几乎是一条直线,RMSE 大得离谱,外生变量似乎对预测没有任何贡献。
原因:这个坑通常出在差分阶数 d 和外生变量的关系上。当 d≥1 时,模型建模的对象是差分后的 y,但外生变量 X 还是原始值。如果 X 本身也是非平稳的,差分后的 y 和原始 X 之间几乎没有有效协变关系,X 的系数估计出来接近零,预测自然退化成了纯 ARIMA。
解决:对 X 做和目标变量一样的差分处理,再把差分后的 X 放进模型。具体做法是在预处理阶段手动对每列外生变量也做diff(),然后和差分后的 y 对齐建模。另一种思路是先确认 X 对 y 的驱动关系,把毫无相关性的外生变量直接移除,变量不是越多越好。
5.4 ADF 检验 p 值在 0.05 附近反复横跳,定出的 d 不稳定
现象:同一份数据,某次运行定出的差分阶数是 1,换一段训练集再跑就变成 0 或者 2,模型预测结果也随之大起大落。
原因:ADF 检验的 p 值在样本量不够大、序列存在弱季节性或结构性断点的时候,检验功效会下降,p 值经常在临界值附近摇摆。这不是代码问题,是数据本身给不出明确答案。
解决:搭配 KPSS 检验一起判断,两个检验结论一致时才确定差分阶数。KPSS 的原假设是平稳,和 ADF 正好互补。如果 ADF 说非平稳、KPSS 说平稳,那大概率是数据量不足,需要扩充训练集;如果序列有明显的周期性,先做季节分解或直接上seasonal_order,不要急着加大 d。d 不是越高越好,过差分会让模型丢失长期信息。
5.5 中文路径和中文列名引发编码报错,脚本在 Windows 上闪退
现象:在 Windows 上运行python datapre.py,报UnicodeDecodeError或者直接闪退,错误指向pd.read_csv。
原因:CSV 文件的编码和系统默认编码不一致。Windows 下 pandas 默认用gbk解码,但很多 CSV 是utf-8存的;另外脚本所在路径带中文时,部分环境下 Python 的读取也会出问题。
解决:在pd.read_csv里显式指定编码,写成pd.read_csv("data.csv", encoding="utf-8"),如果还报错就换encoding="gbk"试。项目目录和文件名尽量用英文,不要放在「桌面」这种含中文字符的路径下跑。运行的时候在终端里执行而不是双击脚本,这样所有报错信息都能完整看到。
6. 进阶:让 ARIMAX 预测结果更可信
到这一步,模型能跑通、能出数了,但预测结果到底靠不靠谱,还需要额外验证。我一般会做两件事:滚动预测和残差检验。一次性预测容易把误差放大——模型在测试集第一期的误差会传导到后面所有期,看起来误差很大,但不是模型真实水平。滚动预测每次只预测一期,然后把真实的下一期值追加到训练集里,模拟实际业务中「每天收到新数据、预测明天」的节奏:
history = y_train.copy() history_X = X_train.copy() predictions = [] for i in range(len(y_test)): model = SARIMAX(history, exog=history_X, order=best_order) result = model.fit(disp=False) pred = result.get_forecast(steps=1, exog=X_test.iloc[[i]])["predicted_mean"].iloc[0] predictions.append(pred) # 把真实值加入历史,推进到下一个时间点 history = pd.concat([history, y_test.iloc[[i]]]) history_X = pd.concat([history_X, X_test.iloc[[i]]])这样得到的误差更接近真实上线表现。配合残差检验看模型有没有吃干净信息:
from statsmodels.stats.diagnostic import acorr_ljungbox residuals = y_test - predictions ljung_box = acorr_ljungbox(residuals, lags=10, return_df=True)Ljung-Box 检验的 p 值如果大于 0.05,说明残差接近白噪声,模型把该抓住的时间结构都抓住了;如果 p 值很小,说明残差里还有自相关,模型漏掉了某些规律,该回去调整 p、q 或者补充外生变量。从那以后我每次做多变量预测,第一件事是先确认未来外生变量拿不拿得到,再决定到底要不要用 ARIMAX——这个包让你把整个流程跑通,但最关键的判断永远在数据这一侧。希望帮到你。
本文还有配套的精品资源,点击获取