ARIMA时间序列预测:从平稳性检验到Python落地实战

发布时间:2026/10/20 7:19:35
ARIMA时间序列预测:从平稳性检验到Python落地实战
做运营数据分析也好做系统监控预警也罢只要你手里有一套带时间戳的连续数据想做“未来会变成什么样”的判断就走进了时间序列预测的领域。这个领域里算法很多深度学习模型也不少但 ARIMA 算法永远是绕不开的起点它解释性强、逻辑透明、不需要 GPU而且能帮你把时间序列建模的核心方法论彻底过一遍。这篇文章我会按“原理 → 判断 → 代码 → 调优 → 排坑”的顺序把 ARIMA 从入门到落地讲清楚基于 Python 的 statsmodels 库给出完整实现并分享一些常规文档里不常写的实操经验。文章适合刚接触时间序列的算法初学者、做数据分析和业务策略的朋友以及所有想用 Python 自己跑通一个预测项目的人。你不需要很深的数学背景我会尽量把每个概念翻译成大白话。读完之后你至少能独立完成一份从数据清洗到未来预测的完整项目并且知道模型什么时候该信、什么时候该扔。1. ARIMA 到底是个什么算法1.1 一句话说清 ARIMA 的核心思想ARIMAAutoregressive Integrated Moving Average拆开看就是三个部分AR 自回归、I 差分、MA 移动平均。它的核心公式可以写成以下形式也就是当前值等于过去 p 个自身值的线性组合加上过去 q 个误差项的线性组合最后再加上一个随机噪声y_t c φ1*y_{t-1} ... φp*y_{t-p} θ1*ε_{t-1} ... θq*ε_{t-q} ε_t看着公式复杂但拆成大白话就三句今天的数据跟过去几天的数据有关所谓“自回归”就是用自己的历史解释现在。很多真实数据并不平稳比如销量一路往上走直接建模是建不出来的要先通过差分去掉趋势。模型每一次预测都会产生误差而这些误差自身往往也有规律用过去的误差来修正当前的预测就是“移动平均”部分。把这三个思路叠在一起就是 ARIMA(p, d, q)括号里的三个数字分别对应自回归阶数、差分次数、移动平均阶数。我见过很多初学者一上来就调包跑模型完全不关心 p、d、q 是怎么来的跑通之后发现预测结果无法解释。这一步我必须先讲清楚ARIMA 不是一个黑箱它的每一个参数都有明确的业务和统计含义。1.2 AR、MA、I 三个部分各管什么事为了后面看代码时不迷糊我把三个部分再单独拎出来细讲一遍。AR 部分解决的是“现在和过去的关系”。比如说今天的气温受昨天和前天影响今天的销量受前三天促销活动影响这些都可以用 y_t c φ1*y_{t-1} 这种形式去刻画。p 就是你要回溯多少个历史时刻的值去解释当前值。p 越大用到的历史信息越多但也越容易把过去噪声当成规律学进去。实际使用中 p 一般落在 0 到 5很少超过这个范围。MA 部分解决的是“预测误差会携带信息”。这个名字很容易让人误解成“滑动平均”它其实不是对数据做平滑而是对误差项做回归。你可以简单理解为模型是有记忆的之前几次预测偏高了还是偏低了这个信息对下一次预测有参考价值。MA(q) 里的 q 就代表要用过去多少个误差项来修正当前预测。I 部分也就是差分管的是“把不平稳的数据变平稳”。最常见的做法就是相邻两个时间点相减得到一阶差分序列。举例来说某条曲线的数值从 100 涨到 102一阶差分后就是 2它描述的是变化量而不是绝对值。很多时候人们会发现绝对值序列不平稳但变化量序列却相当稳定这就是差分存在的意义。1.3 ARIMA 适合解决什么问题不适合什么场景ARIMA 的黄金场景是数据有明确的短期自相关结构、没有强烈的季节性波动、样本量足以支撑训练。适合它的任务包括访问量或请求量的短期预测提前几小时或者未来几天的判断电商业务里某个品类销量的短期预测指导补货和排期监控指标的异常趋势预判比如服务响应时间是否正在爬升。注意我反复强调“短期”两个字。ARIMA 本质上是线性模型假设历史规律会延续预测步数越多不确定性累积越严重十几期之后预测往往就变成一条水平线或者一条单调曲线参考价值不大了。它不适合的场景也很明确周期波动强烈的数据比如双十一大促、节假日效应、季节性明显的业务直接用 ARIMA 效果会很差应该考虑 SARIMA 或分解类模型非线性关系明显的序列比如某个阈值触发的雪崩式增长ARIMA 根本表达不了这种结构样本量太小的序列我个人经验是少于 30 个观测值就非常勉强连参数估计的统计显著性都保证不了需要长期预测的任务更适合做趋势外推加置信区间的区间预估而不是依赖 ARIMA 给出一个确定性的长期数值。把适用边界划清楚下面进入正式实操。2. 建模前必须搞懂的三个前置判断2.1 平稳性ARIMA 建模的地基为什么 ARIMA 对平稳性这么执着因为你是在用历史数据估计“规律”如果序列的均值本身一直在漂移那么不同时间段的规律是不同的估计出来的参数就没有意义。平稳的直观意思是序列的均值、方差在时间轴上基本保持不变围绕着一个固定水平上下波动。你可以拿一个实际场景来理解。某个接口的每日调用次数从年初的 1 万涨到年末的 10 万均值一直在变这就是典型的非平稳序列。如果直接拿它建 ARIMA 模型模型会以为过去的所有规律都能线性延伸结果要么严重高估要么严重低估。而如果先做一阶差分得到每天的增量这个增量序列往往就比较平稳了。判断平稳性有两个工具一是画图肉眼看走势二是用 ADF 检验Augmented Dickey-Fuller Test做统计验证。画图是第一步任何建模操作之前都应该先看曲线不能上来就跑检验。2.2 ADF 检验怎么读结果ADF 检验的原假设是序列存在单位根也就是“序列非平稳”。如果检验得到的 p 值小于显著性水平通常取 0.05就拒绝原假设认为序列是平稳的。反之如果 p 值大于 0.05就没有理由拒绝“非平稳”的假设需要做差分处理。实际看结果时我会按这样的顺序先看 p 值再看统计量是否小于临界值最后配合原始序列的折线图做交叉判断。举个例子某序列的 ADF 统计量是 -1.23p 值是 0.45临界值 5% 那行是 -2.88这说明统计量不够小无法拒绝原假设序列是非平稳的。而差分之后统计量变成 -6.51p 值小于 0.001平稳性就成立了。有一个经验值得强调ADF 检验对数据变换非常敏感。很多含量数据直接检验不平稳但取对数之后再做检验就平稳了。所以我通常会先画图看方差是否稳定如果方差随趋势增大先做对数变换再考虑差分。这样往往可以少差一次保留更多原始信息。2.3 差分到底差几次确定 d 参数确定 d 的流程就是一个循环对原始序列做 ADF 检验如果 p 0.05d 0如果不平稳做一阶差分重新检验如果平稳d 1如果一阶差分后仍不平稳做二阶差分再检验一般到 d 2 就很少再往下走了。为什么 d 一般不超过 2因为大多数业务时间序列经过一阶或二阶差分后都能达到平稳超过二阶通常会过度差分把序列里原本有价值的长期关系也抹掉了。我调参时曾试过 d 3预测结果不仅没有变好反而出现明显的震荡回头检查发现二阶差分后的序列已经接近白噪声继续差分完全是在制造新的噪声。另外差分次数和模型是否带常数项有关。差分后的序列如果均值在 0 附近摆动建模时可以不设常数项如果均值明显偏离 0就需要考虑。statsmodels 里通过 trend 参数控制用 c 表示带常数项n 表示不带。3. 完整代码实现从数据清洗到预测落地3.1 环境准备与数据加载先列需要的库pip install pandas numpy statsmodels matplotlib pmdarimapandas 负责数据处理numpy 做数值计算statsmodels 是 ARIMA 建模的主力matplotlib 用来画图pmdarima 用于自动定阶。我用一份模拟数据来演示整个流程构造 150 天的日访问量数据带上升趋势和随机噪声import numpy as np import pandas as pd import matplotlib.pyplot as plt from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf from statsmodels.tsa.arima.model import ARIMA from statsmodels.stats.diagnostic import acorr_ljungbox np.random.seed(42) t np.arange(150) trend 0.3 * t noise np.random.normal(0, 2, 150) y 50 trend noise df pd.DataFrame({ date: pd.date_range(2023-01-01, periods150), value: y }) df.set_index(date, inplaceTrue) plt.figure(figsize(12, 4)) plt.plot(df.index, df[value]) plt.title(Daily Traffic Volume) plt.xlabel(date) plt.ylabel(value) plt.grid(True) plt.show()这里我特意没加入季节性成分让例子聚焦在 ARIMA 主流程上。真实项目如果数据有明显周期后面单独处理。第一步永远是可视化不要直接跑模型。通过折线图你能快速判断均值是否稳定、方差是否稳定、有没有趋势和周期。3.2 平稳性检验与差分实现定义一个小函数封装 ADF 检验方便后续反复调用def adf_test(series): result adfuller(series, autolagAIC) print(fADF Statistic: {result[0]:.4f}) print(fp-value: {result[1]:.4f}) for k, v in result[4].items(): print(fCritical Value ({k}): {v:.4f}) if result[1] 0.05: print( Series is stationary) else: print( Series is non-stationary) return result对模拟数据跑一次大概率得到 p 值大于 0.05 的结果说明序列非平稳需要差分adf_test(df[value]) df[value_diff] df[value].diff() df[value_diff].dropna(inplaceTrue) adf_test(df[value_diff])diff()之后第一行会出现 NaN很多新手直接把这个序列丢给模型导致报错或者输出结果被坑。务必记得用dropna()把缺失值去掉。一阶差分后的序列均值在 0 左右摆动ADF 检验 p 值会小于 0.05说明已经平稳因此 d 1。3.3 ACF 和 PACF 定阶确定 p 和 qd 确定之后接下来要确定 p 和 q。这一步靠两个图ACF自相关函数和 PACF偏自相关函数。fig, axes plt.subplots(1, 2, figsize(12, 4)) plot_acf(df[value_diff].dropna(), lags20, axaxes[0]) plot_pacf(df[value_diff].dropna(), lags20, axaxes[1]) plt.show()看图有一套常用的经验法则PACF 图在滞后 p 阶后截尾突然掉进置信区间而 ACF 图拖尾缓慢衰减适合 AR 模型p 取截尾滞后阶数ACF 图在滞后 q 阶后截尾而 PACF 图拖尾适合 MA 模型q 取截尾滞后阶数两个图都拖尾时p 和 q 都需要设两个图都截尾时先尝试低阶组合。这里要注意plot_pacf的判定方法默认是 ywadjusted一般不用改。模拟数据生成时没有刻意注入强自相关结构所以图上看起来可能比较乱这是正常现象。实际项目中也不会有完美教科书式的图差不多的截尾位置就可以作为参数候选范围。根据图先圈定 p 在 0 到 3、q 在 0 到 3 的范围后面通过网格搜索进一步确认。3.4 用 statsmodels 构建 ARIMA 模型确定了 order(p, d, q) 后就可以建模型了model ARIMA(df[value], order(2, 1, 2)) result model.fit() print(result.summary())order的顺序和很多入门教程里写的一样就是 (p, d, q)别传反了。fit()返回一个ARIMAResults对象summary 输出会包含各项系数、标准误、P 值和 AIC/BIC 等。看 summary 时重点看两个东西一是每个系数对应的 P|z|如果某个项 P 值远大于 0.05说明这一项不显著可以考虑降低对应阶数重新建模二是 AIC/BIC 的大小在后续参数寻优时作为比较基准。我实际项目中经常遇到一种情况系数都显著但模型预测出来还是不对。这时候就要检查是不是差分次数出了问题或者序列里还有未被捕获的周期成分。模型拟合不是终点验证才是。3.5 模型评估与残差分析模型建完最重要的验证手段是看残差是否满足白噪声假设。如果残差中还有明显的自相关说明模型没有把信息提取干净还有改进空间。resid result.resid fig, axes plt.subplots(1, 2, figsize(12, 4)) plot_acf(resid, lags20, axaxes[0]) plot_pacf(resid, lags20, axaxes[1]) plt.show() lb_test acorr_ljungbox(resid, lags[10, 20], return_dfTrue) print(lb_test)Ljung-Box 检验的原假设是残差为白噪声我们希望看到的结果是 p 值全部大于 0.05也就是残差没有显著自相关。如果 p 值很小说明残差里还有可利用的信息需要回到定阶环节重新调整 p、q。除了残差分析还可以把样本内拟合值和真实值对比算一下误差指标。最常用的回归指标是 RMSE均方根误差和 MAE平均绝对误差前者对大误差更敏感更适合需要惩罚大偏差的场景后者更直观适合给业务方解释。3.6 预测未来 N 期并输出置信区间样本外预测是最终目的。用get_forecast可以同时拿到预测均值和置信区间forecast_result result.get_forecast(steps10) mean_forecast forecast_result.predicted_mean conf_int forecast_result.conf_int()为什么我推荐get_forecast而不是forecast因为它能输出置信区间。实际给业务方看预测如果只说点估计很容易被挑战。置信区间能让对方理解未来是一个可能范围而不是一个确定值。这一点在业务沟通中非常重要。画图fig, ax plt.subplots(figsize(12, 4)) ax.plot(df.index, df[value], labelobserved) ax.plot(mean_forecast.index, mean_forecast, labelforecast, colorred) ax.fill_between( conf_int.index, conf_int.iloc[:, 0], conf_int.iloc[:, 1], colorred, alpha0.2, label95% confidence interval ) ax.legend() plt.show()你会看到置信区间随着预测步数增加逐渐扩大这是正常的也是 ARIMA 模型的本质特性——越往后不确定性越大。如果一个预测模型的置信区间长期收得很窄反而需要警惕是不是过拟合了。4. 参数优化的进阶玩法4.1 网格搜索自动寻优手动看 ACF/PACF 图定阶很多情况下不够精确尤其是数据本身自相关结构复杂的时候。实操中我推荐的方式是先看图圈定范围再用网格搜索按信息准则选最优。import itertools import warnings warnings.filterwarnings(ignore) p_range range(0, 5) d_range range(0, 2) q_range range(0, 5) best_aic float(inf) best_order None for p, d, q in itertools.product(p_range, d_range, q_range): try: model ARIMA(df[value], order(p, d, q)) result model.fit() if result.aic best_aic: best_aic result.aic best_order (p, d, q) except Exception: continue print(fbest_order: {best_order}, AIC: {best_aic:.2f})这个循环有两个细节要注意很多参数组合在数值上可能不收敛必须用 try/except 包裹跳过报错的组合p、q 的范围不要一开始就设得很大阶数过高几乎必然过拟合。pmdarima 库里提供了一个更省事的函数pmdarima.auto_arima可以直接在指定范围内搜索 p、d、q 甚至季节项参数适合快速跑基线模型。它的原理就是 AIC/BIC 网格搜索加一些剪枝策略效果不错。4.2 AIC 和 BIC 准则怎么选AIC赤池信息准则和 BIC贝叶斯信息准则都是模型选择的常用指标它们同时惩罚拟合误差和参数数量。AIC 对参数的惩罚相对较轻BIC 对参数的惩罚更重所以 BIC 倾向于选择参数更少的简单模型。实操中我会同时输出多个组合的 AIC 和 BIC如果两者一致选出同一个模型当然最好如果不一致样本量大的时候更倾向 BIC样本量小的时候更倾向 AIC。为什么BIC 的惩罚项里包含样本量 nn 越大参数数量惩罚越重这能有效防止在大数据集上选出一个过度复杂的模型。还有一点AIC 或 BIC 的绝对值本身没有意义只看相对大小。如果最优模型和次优模型的 AIC 只差不到 2那基本等于没有显著差异这时候应该选择更简单的模型省得在预测阶段引入额外的不稳定性。4.3 滚动预测与模型重训ARIMA 模型训练完成后参数是固定的但业务数据每天都在更新。想让模型用上最新数据最简单的方式是定期重训。滚动预测的通用思路是用截止到当前时刻的数据训练模型预测下一步然后和真实值比较再把真实值追加进历史数据继续滚动。train_size 100 train df[value][:train_size] test df[value][train_size:] history list(train) predictions [] for t in range(len(test)): model ARIMA(history, order(2, 1, 2)) result model.fit() pred result.forecast(steps1)[0] predictions.append(pred) history.append(test.iloc[t]) mse np.mean((np.array(predictions) - np.array(test)) ** 2) rmse np.sqrt(mse) print(fRMSE: {rmse:.4f})这个循环在数据量小的时候没问题数据一旦上千条每步都重新拟合就非常耗时。实际工程里可以换两个思路一是每 N 步重训一次而不是每一步二是用result.append(new_data)做增量更新避免从头开始拟合。增量更新的稳定性不如全量重训所以也要看场景取舍。5. 常见问题与排坑实录5.1 差分后仍然不平稳怎么办有一种情况很常见ADF 检验显示需要差分但差分了多次还是不平稳或者模型预测结果依旧一塌糊涂。这时候大概率不是差分次数不够而是数据本身有其他结构比如明显的季节性。周期性波峰波谷是 ARIMA 的差分处理不掉的你需要的是 SARIMA也就是带季节性参数的 ARIMA。SARIMA 在 ARIMA 基础上增加了一组季节参数 (P, D, Q, m)其中 m 是季节周期长度。日粒度数据如果每周有明显的周几效应m 7月度数据如果每年有淡旺季m 12。statsmodels 直接支持只需要把 order 和 seasonal_order 都传给 ARIMA 类model ARIMA( df[value], order(2, 1, 2), seasonal_order(1, 1, 1, 7) )判断是否存在季节性的一个快方法看 ACF 图。如果 ACF 在固定间隔处比如滞后 7、14、21反复出现显著峰值几乎可以断定存在周期为 7 的季节性。5.2 怎么判断模型是不是过拟合ARIMA 过拟合的典型表现是训练集拟合得很好测试集误差反而很大。最常见的原因是 p 或 q 选得太大把随机噪声当成了规律学进来。我在网格搜索里发现一个规律如果最优模型的阶数很高比如 p 7、q 6基本可以判定过拟合。ARIMA 的经验阶数通常都在 0 到 5 之间超过这个范围要非常警惕。更稳妥的做法是做时序交叉验证把数据按时间顺序切段反复验证不同参数组合在未见数据上的表现。注意时间序列的交叉验证不能随机打乱必须按时间顺序切分否则等于偷看了未来数据验证结果没有意义。另外可以对比训练误差和验证误差两者差距越大过拟合越明显。如果训练集 RMSE 很小而验证集 RMSE 很大说明模型过于灵活应该考虑降低阶数。5.3 预测结果是一条直线怎么办这个问题出现的频率极高。模型跑出来预测结果几乎是一条水平线看着毫无信息量。原因通常有两个一是模型近似于白噪声模型历史数据本身就没有明显的自相关结构。比如一个本来就随机波动的序列ARIMA 找不到可用的规律预测自然回归到均值附近。这种情况换参数没用应该先审视数据本身是否可预测或者考虑引入更多特征、换用其他模型。二是差分次数 d 过大过度差分把序列的有用信号全抹掉了模型只剩一个漂移项可以预测。这时候降低 d 的阶数通常预测曲线就能恢复一些形状。我调试时经常先把 d 固定下来再用网格搜索找 p 和 q避免三个参数同时扰动互相掩盖问题。5.4 遇到季节性强的序列怎么处理季节性数据的处理比普通 ARIMA 复杂不少这里给出一个检查方法把序列按周或月汇总画箱线图如果不同周期分段的分布明显不同就说明存在季节性。更直接的做法是看 ACF 图刚才说过固定间隔处的显著峰值是季节性铁证。处理方向有两个。一是直接用 SARIMA把季节周期写成参数让模型自己去拟合季节效应。二是做 STL 分解Seasonal-Trend decomposition using Loess把序列拆成趋势、季节、残差三部分对残差部分套 ARIMA预测完再把季节成分加回去。第二种做法更灵活适合季节形态复杂、不只是简单正弦波的场景。两种方案我都实践过。简单场景比如周周期明显的日数据直接 SARIMA 就很稳复杂场景比如同时存在年周期和节假日效应的数据两阶段处理能让你分别检查每个部分的模型质量定位问题更清晰。最后分享一个我个人真实的体会看再多教程都不如拿一份自己手头的真实数据从头到尾跑一遍。你可以随便导出一份运营数据哪怕只是 Excel 里的销售明细从可视化开始一步步走到预测出置信区间。这个过程走通了你对时间序列预测的理解一定会比看十篇理论文章更深入。模型做出来不是终点能解释、能验证、能落地才是真本事。ARIMA 也许不会是你项目里最终的算法但它教给你的建模习惯会在之后用任何模型时都发挥作用。