ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ARIMAX多变量预测模型实战:原理、源码与参数调优

ARIMAX多变量预测模型实战:原理、源码与参数调优 简介基于ARIMAX的多变量预测模型源码与配套数据面向具备一定Python与统计基础的数据分析学习者用于在销量预测、经济指标分析等场景中掌握带外生变量的时间序列建模方法。ARIMAX模型在经典时间序列模型基础上引入外生变量能够同时利用目标序列自身历史和相关影响因素的信息。包内共7个文件包含2个Python脚本、2个CSV数据集、2张可视化图片和1份Markdown说明文档一个脚本负责数据预处理与特征构造另一个完成模型训练、预测与结果可视化CSV文件提供可直接复现的实验数据图片展示实际值与预测值的对比说明文档则整理了从数据清洗、平稳性检验、参数确定到预测评估的完整流程。整套资源仅146KB代码注释详细便于快速理解与二次修改。已有408人学习下载非常适合课程设计、论文实验以及实际业务中多变量预测任务的参考读者可直接运行并迁移到自己的数据上。1. 基于ARIMAX的多变量预测模型 python 源码数据集 zip先看懂它解决的问题再动手拿到一个基于ARIMAX的多变量预测模型 python 源码数据集 zip很多人的第一反应是解压、装依赖、跑通脚本然后看一眼预测曲线就收工。但做预测最怕的就是这种“跑通了但没想明白”的状态。ARIMAX 是带外生变量的自回归积分滑动平均模型本质是在经典 ARIMA 的基础上加入一条外部输入通道让销量、流量、电力负荷这类时间序列能同时利用促销活动、天气、价格等外部因子做多变量预测。这个 zip 的价值在于它给了你一个可以直接改的骨架但外生变量未来值从哪来、数据怎么对齐、p/d/q 怎么定才是决定项目成败的地方。这篇笔记按源码落地路径拆解覆盖模型原理、数据格式、最小可运行脚本、参数整定和踩坑记录新手能跟着复现熟手能直接对照边界条件。2. ARIMAX 模型结构外生变量怎么进模型统计口径要对齐2.1 单变量 ARIMA 的局限为什么需要外部变量通道ARIMA(p,d,q) 只吃目标变量自身的时间序列历史。比如你预测一款商品的日销量ARIMA 能捕捉到的只有销量序列内部的趋势、季节性和自相关性。但实际业务里销量的波动经常是被外部事件驱动的大促当天销量翻倍、连续雨天外卖单量上升、调价后转化率变化。这些信息不在目标序列的历史里纯 ARIMA 只能把它们当作不可解释的残差。ARIMAX 解决的就是这个问题在 ARIMA 的框架里加一条外生变量通道把促销标记、天气、价格这些外部因子作为解释变量一起建模。这里的“多变量预测”不是说把多个目标变量放进一个模型同时预测而是指“一个目标变量 多个外生变量”的联合建模方式这个区别很重要很多初学者在第一次看到“多变量”这个词时会理解偏。对比项ARIMA(p,d,q)ARIMAX(p,d,q)exog输入数据仅单变量历史序列单变量目标 外生变量矩阵可解释性只能解释自身历史模式可以量化外部因素影响预测场景无外部干预的纯序列外推有促销、天气、价格等外部干预未来输入要求只需要目标历史还需要外生变量的未来值做一个业务预测项目时我会先问一个问题未来这段时间有什么已知但尚未发生的外部事件会影响目标如果有就用 ARIMAX如果没有ARIMA 就够了。硬给 ARIMA 加外生变量只会增加数据准备成本和共线性风险不会带来精度提升。2.2 exog 进模型的数学位置不是把 X 简单丢进回归statsmodels 是 Python 里最常用的时间序列建模库源码包里的建模部分几乎都是基于它写的。在 statsmodels 中带外生变量的 ARIMA 模型写成ARIMA(endog, exogexog, order(p,d,q))它实际拟合的是这样一结构y_t X_t β η_t其中 η_t 是 ARIMA(p,d,q) 过程。也就是说模型先把外生变量的线性回归项拿掉再对剩下的残差部分用 ARIMA 做时序建模。严格来说这是“带 ARIMA 误差的回归模型”和教科书里那种把外生变量直接塞进 ARIMA 多项式的写法略有差别但在工程实践中大家都叫它 ARIMAX源码包一般也采用 statsmodels 这套实现。这种实现方式有个直接好处外生变量的系数 β 可以直接解读。β 的含义是“外生变量每变化一个单位目标变量平均变化多少”显著性看 summary 输出里的 P|z| 列。这个解释口径对业务方很有价值比如促销标记系数是 200意味着有促销的日子销量平均高出 200 件。需要注意一个细节当你设置 d1 做一阶差分时statsmodels 会对目标变量和外生变量同时做差分处理然后在这组差分序列上估计 β。这时候 β 的解释就不再是“X 升高一单位 Y 升高多少”而是“X 的变化量升高一单位Y 的变化量升高多少”。我在给业务方汇报系数时吃过这个亏后来养成习惯先看模型是否做了差分再决定怎么解释系数。2.3 zip 源码包里的常见结构数据、模型、说明三层以我接触过的大多数时间序列源码包来看结构基本是三段式数据层、模型层、说明层。数据层是一个或多个 CSV 文件至少包含时间列、目标列、外生变量列模型层是 Python 脚本负责读数据、切训练测试集、拟合、预测、画图说明层是数据字典或 README描述每个字段的含义、单位、时间跨度、缺失值情况。这个 zip 的“数据集释”指的就是说明层实际项目里这层往往比代码本身更重要。我见过很多项目换个人接手后代码能跑但没人说得清某列是毫米还是英寸、是累计值还是瞬时值。所以拿到源码包第一件事不是跑代码而是把数据字典读一遍确认目标列和外生变量的业务含义。依赖方面核心库一般就四个pandas 做数据操作statsmodels 做 ARIMAX 拟合numpy 做数值计算matplotlib 画图。如果你的环境是纯基础 Python没有 pandas 和 statsmodels需要先补齐这些依赖再跑源码。下一章我会把完整的环境准备和数据预处理步骤拆开讲。3. 照源码包跑通最小 ARIMAX 示例环境、数据格式与预测脚本3.1 Python 环境准备与依赖安装源码包的运行环境要求不苛刻Python 3.8 到 3.10 之间基本都能跑。我建议你不要直接把依赖装进系统 Python而是建一个独立的虚拟环境避免把其他项目搞坏。在命令行里执行python -m venv venv在 Windows 上激活环境是venv\Scripts\activate在 Linux 或 macOS 上是source venv/bin/activate。激活后命令行前面会出现(venv)前缀。如果本机还没有安装 Python先去 python 官方网站下载对应操作系统的安装包安装时勾选“Add Python to PATH”不然后面python命令会提示找不到。接下来安装依赖pip install pandas numpy statsmodels matplotlib装完后可以验证一下核心包版本python -c import statsmodels, pandas, numpy; print(statsmodels.__version__, pandas.__version__, numpy.__version__)statsmodels 从 0.13 开始把ARIMA类放在了statsmodels.tsa.arima.model模块里早期在statsmodels.tsa.arima_model两个接口在参数名上有差异。源码包如果是新写的一般用的是新接口也就是我下面演示的这种。如果你在 vscode 里跑代码之前先确认右下角选择的解释器是你刚建好的 venv 环境不然会一直提示找不到 statsmodels。3.2 数据集格式与预处理ARIMAX 要求数据是严格的按时间排序的表格不能有随机顺序。典型的数据集长这样datesalespromotemp2024-01-0132002.12024-01-024101-1.32024-01-032800-2.5date 是时间索引sales 是目标变量promo 是促销标记0/1temp 是天气外生变量。实际项目里外生变量可能更多但格式规律是统一的每一列必须是一个数值序列长度和时间索引严格对齐不允许有空行错位。预处理步骤我一般用这段代码import pandas as pd # 读取数据集encoding 用 utf-8-sig 兼容 Windows 下 Excel 导出的 BOM 头 df pd.read_csv(arimax_dataset.csv, encodingutf-8-sig) # 时间列转成 pandas 的 datetime 类型并设为索引 df[date] pd.to_datetime(df[date]) df df.sort_values(date).set_index(date) # 先排序再填充缺失值防止 ffill 用错顺序 df df.fillna(methodffill) # 确认没有类型异常列 print(df.dtypes)这段代码做了三件事第一把 date 列从字符串转成 datetime并设为行索引这是 statsmodels 识别时间序列结构的前提第二按时间升序排序这是时间序列分析区别于普通机器学习的最基本要求一旦乱序所有滞后期计算都是错的第三用前向填充处理缺失值时序数据通常不适合用均值填充因为均值会抹掉相邻时点的连续趋势。需要注意fillna(methodffill)在 pandas 2.x 里会提示改用df.ffill()功能一样遇到 warning 直接换写即可。如果目标变量本身有大量缺失前向填充会让模型误以为缺失期间数值不变这种情形建议直接删掉那段区间。3.3 最小可运行脚本从数据加载到预测出图这是整个源码包最核心的一段我拆成完整脚本。假设你已经把数据准备好列名是sales、promo、tempimport pandas as pd from statsmodels.tsa.arima.model import ARIMA from sklearn.metrics import mean_absolute_error import matplotlib.pyplot as plt # 1. 读取数据并排序 df pd.read_csv(arimax_dataset.csv, parse_dates[date]) df df.sort_values(date).set_index(date) # 2. 指定目标列和外生变量列 y df[sales] X df[[promo, temp]] # 3. 按时间顺序切分训练集和测试集不做随机打散 train_size int(len(df) * 0.8) y_train, y_test y.iloc[:train_size], y.iloc[train_size:] X_train, X_test X.iloc[:train_size], X.iloc[train_size:] # 4. 拟合 ARIMAX 模型order 里的三个数分别是 p, d, q model ARIMA(y_train, exogX_train, order(2, 1, 2)) result model.fit() print(result.summary()) # 5. 多步预测预测 20 步必须有对应的未来外生变量 steps len(y_test) pred result.forecast(stepssteps, exogX_test) # 6. 评估预测误差 mae mean_absolute_error(y_test, pred) print(fTest MAE: {mae:.3f}) # 7. 画图对比 plt.figure(figsize(10, 4)) plt.plot(y_test.index, y_test.values, labelactual) plt.plot(y_test.index, pred.values, labelforecast) plt.legend() plt.title(ARIMAX Forecast Result) plt.show()代码结构按六层拆开。第 3 步切分时用:train_size这种纯位置切片而不是随机抽样是因为时间序列一旦 shuffle训练集和测试集之间的时序依赖就被破坏了模型等于偷看了未来的部分信息。第 4 步order(2,1,2)是超参数元组第一个数 2 是自回归阶数 p第二个数 1 是差分阶数 d第三个数 2 是移动平均阶数 q这三个值的含义在下一章展开现在先用经验值跑通。第 5 步是 ARIMAX 和 ARIMA 使用差异最大的地方forecast()必须指定exog且X_test的行数必须等于steps行索引不要求连续但列名必须和训练时完全一致。result.forecast()返回的是一个 Series索引是预测时点值和y_test对齐后直接喂给mean_absolute_error算误差。如果这里报错说长度不匹配大概率是steps和len(y_test)不一致或者X_test里有 NaN。3.4 第一次跑通之后先看这四行输出跑通后不要急着调参先看print(result.summary())的输出。重点看四个地方第一P|z|列外生变量 promo 和 temp 对应的 p 值如果大于 0.05说明这个外生变量在统计上不显著可以考虑去掉第二AIC 和 BIC 两个信息准则值后面网格搜索会用它比较模型第三Ljung-Box 检验的 p 值如果小于 0.05 说明残差还有自相关模型没把信息抽干净第四看coef列里外生变量的符号是否符合业务直觉促销系数是负数就得警惕数据出问题了。如果拟合过程中报了Non-stationary starting autoregressive parameters之类的警告不用慌先尝试把 p 和 q 调小或者检查你需要不需要差分。有时候强行用高阶 AR 项拟合一个近乎随机的序列会触发数值不稳定警告。4. ARIMAX 参数整定用 AIC 网格搜索确定 p、d、q 与外生变量组合4.1 平稳性检验决定 d 值先用 ADF 判断要不要差分ARIMA 框架对数据有个基本要求建模的序列需要是平稳的也就是均值和方差不随时间变化。实际业务数据大多不平稳销量有逐年上涨趋势温度有年周期所以需要差分。d 就是差分阶数d1 表示对原序列做一次一阶差分d2 表示再差分一次。判断差分阶数不能靠肉眼观察一般用 Augmented Dickey-Fuller 检验也就是 ADF 检验。代码如下from statsmodels.tsa.stattools import adfuller result adfuller(df[sales].dropna()) print(ADF p-value:, result[1])ADF 检验的原假设是序列不平稳。p 值小于 0.05 时拒绝原假设认为序列平稳p 值大于 0.05 时不平稳需要差分。如果第一次检验不平稳就对df[sales].diff().dropna()再做一次直到 p 值达标。对目标变量差分后外生变量对应的回归行为会变化这一点在 2.2 里提过解释系数时留个心眼。4.2 ACF 和 PACF 给 p、q 一个初始范围确定 d 之后p自回归阶数和 q移动平均阶数的初步范围可以靠 ACF 和 PACF 图判断。ACF 是自相关函数图PACF 是偏自相关函数图两者分别对应 MA 项的阶数和 AR 项的阶数。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import matplotlib.pyplot as plt # 对差分后序列画 ACF 和 PACF diff_series df[sales].diff().dropna() fig, axes plt.subplots(1, 2, figsize(12, 4)) plot_acf(diff_series, axaxes[0], lags20) plot_pacf(diff_series, axaxes[1], lags20) plt.show()看图规律有个粗略口诀PACF 在滞后 k 阶后突然截尾选 pkACF 在滞后 k 阶后突然截尾选 qk两边都是缓慢衰减则 p、q 都取一个适中小值。但实际序列很少给出教科书式的完美图形所以网格搜索才是真正定参的手段ACF/PACF 只是用来缩小搜索范围避免网格搜索遍历一个明显不合理的大空间。4.3 AIC 网格搜索把候选空间交给循环网格搜索的意思很简单准备一组候选取值遍历所有组合分别拟合模型记录每个组合的 AIC 或 BIC选最小者。AIC 是赤池信息准则BIC 是贝叶斯信息准则两者都在衡量“拟合优度和参数个数”的平衡数值越小越好。BIC 对参数惩罚更重倾向于选择更简单的模型。import warnings import itertools from statsmodels.tsa.arima.model import ARIMA warnings.filterwarnings(ignore) y df[sales] X df[[promo, temp]] best_aic float(inf) best_order None best_model None # 候选范围p 和 q 从 0 到 3d 从 0 到 1 for p, d, q in itertools.product(range(4), range(2), range(4)): try: model ARIMA(y, exogX, order(p, d, q)) fit model.fit() if fit.aic best_aic: best_aic fit.aic best_order (p, d, q) best_model fit except Exception: # 某些参数组合可能导致优化失败直接跳过 continue print(Best order:, best_order, AIC:, best_aic) print(best_model.summary())这段代码有几个细节值得说。第一候选范围里 d 只试 0 和 1因为业务数据一般一阶差分就够过差分会导致信息丢失这在实际项目里很常见。第二异常处理不能省某些高 p 高 q 的组合会让数值优化器发散直接报错比如 p3,q3 配合小样本经常失败。第三搜索结果要结合显著性再判断不要机械选 AIC 最小的模型如果最小 AIC 模型的某个外生变量系数不显著换一个 AIC 略高但系数全部显著的模型更稳妥。网格搜索跑完后把best_order带回第 3 章的最小脚本替换order(2,1,2)就是一套完成参数整定的 ARIMAX 训练流程。4.4 外生变量的选择与滞后处理外生变量不是越多越好。模型的\beta系数是联合估计的两个高度相关的外生变量会互相争夺解释力导致系数不稳定。一个简单的排查方法是用 VIF方差膨胀因子检查多重共线性VIF 大于 10 的变量建议去掉。一个小项目里我一般只保留 2 到 5 个业务上明确有关联的外生变量不搞“先全塞进去再靠模型筛选”。外生变量本身也可以做滞后。比如促销活动的影响可能在活动结束后还延续一两天此时可以构造滞后列df[promo_lag1] df[promo].shift(1)添加滞后变量前必须确认序列已经按时间排序否则 shift 拿到的不是“前一天”。另外一旦用了滞后外生变量预测时也要提供对应的未来滞后值比如预测明天的销量需要“今天”的促销值这对业务数据可得性提出了要求方案设计时要提前想清楚。5. ARIMAX 预测避坑指南五个高频翻车的现象、原因与修复5.1 现象预测阶段报 ValueError提示 exog 长度不一致第一次跑result.forecast(steps10)时如果没传exog参数statsmodels 会直接报错因为模型结构里有外生变量预测必须要未来的外生变量值。更隐蔽的错误是传了X_test但X_test的行数和steps不相等或者X_test里的列名和训练时不一致。原因ARIMAX 的预测公式里必须代入外生变量的未来值缺一步都算不出来。列名不一致是因为 statsmodels 在训练时记住了 exog 的列顺序预测时按位置匹配。解决预测前先assert len(X_test) steps并检查列名列表是否一致。最稳妥的写法是result.forecast(stepslen(y_test), exogX_test)让 steps 和 exog 的行数从同一个变量派生从源头消除不一致。5.2 现象训练集拟合得很漂亮预测结果却是一条直线我见过很多新手拿着 R²0.95 的训练结果向业务方汇报结果测试集预测是一条水平线和均值差不多。这种情况多半是 d 取大了。差分阶数 d2 会把序列里的大部分信息抹掉只剩一个白噪声残差模型找不到可预测的模式预测值自然向均值回归。另一个常见原因是外生变量的预测值填错了比如未来促销活动没被正确编码模型以为所有未来时点外生变量都是零。原因过差分导致信号丢失或者未来外生变量取值失真。解决把 d 降回去重跑 ADF 检验确认平稳性不要盲目多差。验证外生变量未来值时打印前几行人工检查数值是否符合业务预期。这条经验我反复强调预测曲线一旦看起来“太干净”先怀疑输入数据而不是模型。5.3 现象类别型外生变量直接传入报错或跑出一个不可解释的系数业务里经常有“天气类型”“是否节假日”这种文本列。直接传给ARIMA的 exogstatsmodels 会尝试把字符串当作数值要么报类型错误要么硬编码成缺失值。原因statsmodels 的回归部分只接受数值矩阵不做自动独热编码。解决先把类别处理成数值。用 pandas 的get_dummies生成哑变量注意去掉第一列避免和常数项共线df pd.get_dummies(df, columns[holiday], drop_firstTrue)生成的holiday_1列含义是“是否为节假日”0/1 两个取值可以直接传给 exog。如果有多个类别列每个类别列都建议只生成 N-1 个哑变量否则模型会因完全共线性而无法正常估计。5.4 现象用 train_test_split 随机切分模型预测结果奇准无比这是最常见也最危险的一个坑。train_test_split默认是随机切分对于时间序列就是灾难训练集里混杂着测试集之后的样本模型相当于提前看到了未来数据测试误差虚低。原因时间序列的样本顺序就是信息顺序随机切分会破坏时序依赖关系造成数据泄露。解决坚持用位置切片或专门的TimeSeriesSplit交叉验证。正确写法是train_end int(len(df) * 0.8) y_train, y_test y.iloc[:train_end], y.iloc[train_end:]另外外生变量的滞后列也要在切分之前构造好不能在切分后再用整段数据 shift否则滞后列会带入未来信息。5.5 现象换一台机器结果不一样或者解压后脚本报编码错误statsmodels 的数值优化在多线程环境下可能有细微差异但差异通常很小。结果差异大到无法接受时先确认两台机器上 statsmodels 和 numpy 的版本是否一致版本差异会导致优化路径变化。另外一个常见状况是 Windows 解压 zip 后代码里的中文注释或 CSV 里的中文列名出现UnicodeDecodeError。原因源码包里的 CSV 可能是 UTF-8 编码Windows 默认用 GBK 打开中文路径也会让某些老版本 pandas 读取失败。解决读取 CSV 时显式指定编码encodingutf-8-sig解压后把整个项目目录放到纯英文路径下所有列名和文件名避开中文。代码文件本身如果有中文注释运行时一般没问题但编辑器要设置为 UTF-8 保存。6. 进阶验证方法滚动预测与朴素基线对比6.1 滚动预测每步都重新拟合一次模型单次划分训练测试集只能验证一个固定时间点的预测能力业务环境里更常见的是滚动预测每预测完一天就把真实观测值并入历史再预测下一天。这种策略更接近线上实盘逻辑也比较残酷因为它不允许模型偷看未来。history_y list(y_train) history_X X_train.copy() preds [] for i in range(len(y_test)): model ARIMA(history_y, exoghistory_X, orderbest_order).fit() next_X X_test.iloc[[i]] pred model.forecast(steps1, exognext_X).iloc[0] preds.append(pred) # 把真实值追加到历史完成滚动 history_y.append(y_test.iloc[i]) history_X pd.concat([history_X, X_test.iloc[[i]]]) print(Rolling MAE:, mean_absolute_error(y_test, preds))这段代码里最关键的循环逻辑是先用当前历史拟合模型只预测一步然后立刻把这一步的真实值放回历史再进入下一轮。历史只增不减模拟的是系统每天更新一次模型的真实状态。滚动预测的误差通常比一次性多步预测低因为每次只预测一步误差不会累积这是它更适合验证模型能力的原因但代价是计算量大每次都要重新跑一遍拟合。6.2 朴素基线ARIMAX 不打赢它就没必要上线ARIMAX 模型不是永远优于简单方法。我养成了一个雷打不动的习惯任何预测模型上线前先和一个朴素基线对比。时序预测里最经典的朴素基线是“用昨天的值预测今天”也就是持久性预测naive_preds list(y_train) # 用训练集最后一个值起步 for i in range(len(y_test)): # 预测值等于上一个真实值 naive_preds.append(y_test.iloc[i - 1] if i 0 else y_train.iloc[-1]) # 对齐到测试集长度 naive_preds pd.Series(naive_preds[-len(y_test):], indexy_test.index) naive_mae mean_absolute_error(y_test, naive_preds)如果 ARIMAX 的滚动 MAE 没有明显低于朴素基线说明外生变量没有带来增量信息或者当前的数据生成过程本身就是随机游走不值得上线。反过来说如果 ARIMAX 赢了还要再看外生变量的系数是否显著、方向是否合理这样业务方才愿意用模型的解释结论。我一般会做三组对比ARIMAX 固定分点多步预测、ARIMAX 滚动预测、朴素基线。三组 MAE 摆在一起模型值不值得投入人力做数据清洗和外生变量维护一目了然。做 ARIMAX 项目这两年我最大的教训是模型结构只是整个预测系统的一小部分外生变量的数据质量和未来值的可得性才真正决定预测能不能落地。任何预测都不是把数据喂给机器就能自动变准确的把这套流程走通了你会少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取
返回列表