Python ARIMA销量预测实战:从数据清洗到滚动预测的完整指南
简介这份资源是面向Python数据分析初学者、毕业设计及课程设计学生的ARIMA时间序列销量预测完整项目包围绕销量预测这一典型场景提供从数据平稳化处理、模型定阶、参数估计到模型检验的全流程实现思路。包内共16个文件以py脚本、png图表、zbak备份、xls与xlsx数据表及md说明文档为主压缩包约255KB涵盖建模代码、销量时序图、差分后自相关与偏相关图、预测对比表等关键素材。项目采用每月上中下旬三次预测策略将月上旬与中旬实际销量作为先验知识提升当月预测精度并借助statsmodels完成窗口选择、自动定阶与平稳性检测。目前已有78人学习下载适合需要快速搭建预测模型、撰写论文或完成期末大作业的读者参考复用。1. 从一份销量流水到可复现的 ARIMA 预测这套模型到底解决什么问题手里有一张按天或按月记录的销量表老板要你给出未来 4 周的出货预估你第一反应可能是拉个移动平均或者拍脑袋给个增长率。但真实业务里销量序列往往同时带着趋势、季节波动和随机噪声简单平均要么滞后要么把旺季抹平。ARIMA自回归积分滑动平均就是专门处理这类单变量时间序列的经典统计模型配合 Python 的 statsmodels 库几十行代码就能跑出一套可解释、可复现的预测流程。它不依赖 GPU不需要海量数据对中小商家、区域仓、单店 SKU 的销量预测特别友好。这篇笔记面向两类人刚学完 Python 基础语法、想找一个完整数据分析项目练手的入门者以及手头有真实销量数据、需要快速搭一套基线预测的运营或数据从业者。整套流程从数据清洗、平稳性检验、定阶、训练、残差诊断到滚动预测每一步都有可抄的代码和参数说明跑完你就能判断这套模型在你的数据上到底值不值得上生产。2. 数据准备与平稳性ARIMA 能不能用先看这三步ARIMA 全称 Autoregressive Integrated Moving Average三个部分分别对应自回归项 AR(p)、差分次数 I(d)、滑动平均项 MA(q)。它的核心假设是经过 d 次差分后的序列是平稳的也就是均值、方差、自协方差不随时间漂移。如果原始销量有明显上升趋势或逐年放大的季节性直接丢进模型会得到虚假回归预测值要么一条直线要么剧烈震荡。所以落地第一步不是写模型而是把数据整理成模型能吃的格式再用统计检验确认平稳性。2.1 把销量流水整理成带时间索引的 Series真实数据通常来自 Excel 或数据库导出字段可能是「日期、门店、SKU、销量」还夹杂缺失和重复。ARIMA 只处理单变量等间隔序列所以要先聚合到统一时间粒度。下面这段代码演示从 CSV 读取、按天聚合、补全缺失日期、转成带 DatetimeIndex 的 Series。import pandas as pd import numpy as np # 读取原始销量流水假设字段为 date, store_id, sku_id, qty raw pd.read_csv(sales_raw.csv, parse_dates[date]) # 只取单个门店单个 SKU避免多序列混在一起 one raw[(raw[store_id] S001) (raw[sku_id] A1001)] # 按天聚合同一天多笔订单求和 ts one.groupby(date)[qty].sum().sort_index() # 补全缺失日期销量填 0无销售日 full_idx pd.date_range(ts.index.min(), ts.index.max(), freqD) ts ts.reindex(full_idx, fill_value0) ts.name qty print(ts.head()) print(序列长度:, len(ts))逻辑说明groupby(date)[qty].sum()把同一天的多条记录合并成一条这是 ARIMA 要求等间隔的前提。reindex补全缺失日期很关键因为 ARIMA 靠时间间隔计算差分和滞后日期跳跃会让差分错位。参数上freqD表示按天如果业务是按周汇总就改成W按月改成MS月初。填 0 还是插值要看业务无销售日填 0 合理但如果是数据采集故障导致的缺失应该用interpolate()插值否则会把故障当成真实零销量污染自相关结构。2.2 平稳性检验ADF 与差分次数 d 的确定整理好序列后先画图看趋势和季节再用增广迪基-福勒检验ADF判断平稳性。ADF 的原假设是「序列存在单位根即非平稳」p 值小于 0.05 才能拒绝原假设、认为平稳。from statsmodels.tsa.stattools import adfuller def adf_report(series, name): result adfuller(series.dropna(), autolagAIC) print(f{name} ADF统计量: {result[0]:.4f}, p值: {result[1]:.4f}) return result[1] # 原始序列 p0 adf_report(ts, 原始序列) # 一阶差分 p1 adf_report(ts.diff(), 一阶差分) # 二阶差分 p2 adf_report(ts.diff().diff(), 二阶差分)逻辑说明autolagAIC让 statsmodels 自动选择滞后阶数避免手动指定。判断规则是原始序列 p 值小于 0.05d 取 0一阶差分后 p 值小于 0.05d 取 1以此类推。绝大多数销量序列一阶差分就够了d 很少超过 2。参数上不要盲目追求 p 值极小差分次数过多会过度消除信息导致 MA 项难以估计。如果一阶差分后 p 值在 0.05 附近徘徊结合时序图判断有明显趋势就取 d1趋势已被业务解释如促销一次性拉高可以考虑 d0 加外生变量。2.3 用 ACF 和 PACF 图定下 p 和 q 的初始范围确定 d 之后用自相关函数ACF和偏自相关函数PACF图给出 p、q 的候选值。经验规则是ACF 拖尾、PACF 截尾p 取 PACF 截尾处的滞后ACF 截尾、PACF 拖尾q 取 ACF 截尾处的滞后。实际销量数据往往两者都拖尾这时不要死磕图形直接用网格搜索加 AIC 准则更稳。import matplotlib.pyplot as plt from statsmodels.graphics.tsaplots import plot_acf, plot_pacf diff_ts ts.diff().dropna() fig, axes plt.subplots(2, 1, figsize(10, 6)) plot_acf(diff_ts, lags30, axaxes[0]) plot_pacf(diff_ts, lags30, axaxes[1], methodywm) plt.tight_layout() plt.savefig(acf_pacf.png, dpi120)逻辑说明lags30表示看 30 期内的相关性日销量建议看 30 到 60 期月销量看 12 到 24 期。methodywm是 Yule-Walker 估计样本量小时比默认方法稳定。图形只用来缩小范围比如 PACF 在 1、2 阶后落入置信带就把 p 的候选定在 0 到 3ACF 类似处理 q。真正的定阶交给下一章的 AIC 网格搜索避免人眼误判。3. 定阶、训练与诊断把 ARIMA 调成能用的基线数据准备好之后核心工作是在候选的 (p,d,q) 空间里找到 AIC 或 BIC 最小的组合然后检查残差是不是白噪声。这一步决定了模型是「能跑」还是「能用」。很多人跑完fit()看到 summary 就结束结果预测出来偏差很大问题往往出在定阶太随意或残差还有结构。3.1 用 AIC 网格搜索代替肉眼定阶AIC 在拟合优度和参数数量之间做权衡值越小越好。下面这段代码在 p、q 各 0 到 5 的范围内搜索d 用上一章确定的值。import warnings from statsmodels.tsa.arima.model import ARIMA warnings.filterwarnings(ignore) # 忽略不收敛的警告搜索阶段正常 d 1 # 由上一章 ADF 检验确定 best_aic np.inf best_order None results_table [] for p in range(0, 6): for q in range(0, 6): try: model ARIMA(ts, order(p, d, q)) res model.fit() results_table.append((p, d, q, res.aic, res.bic)) if res.aic best_aic: best_aic res.aic best_order (p, d, q) except Exception as e: continue print(最优阶数:, best_order, AIC:, round(best_aic, 2))逻辑说明ARIMA(ts, order(p,d,q))里ts是带 DatetimeIndex 的 Seriesstatsmodels 会自动识别频率。res.aic和res.bic都记录下来AIC 偏向预测BIC 偏向简约样本量大时两者结论接近。参数上搜索范围 0 到 5 对大多数销量序列足够如果数据有强周季节p 或 q 可能要放到 7但这时更推荐用 SARIMA。warnings.filterwarnings(ignore)只是屏蔽搜索阶段的不收敛提示最终模型要单独检查收敛状态不能一直屏蔽。3.2 训练最终模型并读懂 summary 里的关键指标拿到最优阶数后重新拟合重点看系数显著性、Ljung-Box 检验和残差正态性。final_model ARIMA(ts, orderbest_order) final_res final_model.fit() print(final_res.summary()) # Ljung-Box 检验残差是否白噪声 from statsmodels.stats.diagnostic import acorr_ljungbox lb acorr_ljungbox(final_res.resid, lags[10, 20], return_dfTrue) print(lb)逻辑说明summary 里coef是各 AR、MA 项系数P|z|小于 0.05 表示显著如果大量系数不显著说明阶数偏高可以回退一档。Ljung-Box的 p 值大于 0.05 表示残差没有明显自相关模型把结构提取干净了如果 p 值很小说明还有信息没被捕获常见原因是漏了季节项或需要增加 p、q。参数上lags一般取 10 和 20日数据可以取到 30。残差图也要看理想情况是围绕零线随机波动如果出现喇叭口说明方差非齐性可以考虑对数变换后再建模。3.3 残差诊断三个必须看的图和一个必须算的统计量残差诊断是模型上线前的最后一道关。下面代码一次性输出残差时序图、直方图、Q-Q 图和 ACF 图。import scipy.stats as stats resid final_res.resid fig, axes plt.subplots(2, 2, figsize(12, 8)) axes[0, 0].plot(resid) axes[0, 0].set_title(残差时序) axes[0, 1].hist(resid, bins30) axes[0, 1].set_title(残差分布) stats.probplot(resid, distnorm, plotaxes[1, 0]) axes[1, 0].set_title(Q-Q图) plot_acf(resid, lags30, axaxes[1, 1]) axes[1, 1].set_title(残差ACF) plt.tight_layout() plt.savefig(resid_diag.png, dpi120)逻辑说明残差时序图看有没有趋势或周期残留直方图和 Q-Q 图看是否近似正态偏离严重会影响置信区间残差 ACF 看有没有滞后相关。参数上bins30是经验值样本少就减到 15。如果 Q-Q 图两端偏离但 Ljung-Box 通过预测点估计仍然可用只是区间估计要谨慎。这一步的结论直接决定模型能不能进入滚动预测不要跳过。4. 避坑与排查ARIMA 落地时最容易翻车的五个地方ARIMA 代码短但坑不少。下面五条是我在真实销量数据上反复踩过的每条按现象、原因、解决写清楚。坑一预测值是一条水平直线。现象是 forecast 输出几乎不变。原因是 d 取 0 且数据有趋势模型把趋势当成噪声。解决重新做 ADF 检验确认 d 至少为 1如果业务上趋势确实存在不要为了平稳性强行 d0。坑二Ljung-Box 不通过但不知道怎么调。现象是残差 ACF 在滞后 7 或 12 处明显超出置信带。原因是销量有周或月季节普通 ARIMA 抓不住。解决改用 SARIMA在order外加seasonal_order(P,D,Q,s)s 取 7 或 12或者先做季节差分再建模。坑三样本量太小导致不收敛。现象是fit()报错或系数标准误极大。原因是序列长度不足 30 个点参数估计不稳定。解决至少保证 50 个以上时间点数据实在少就降低 p、q 上限到 2或者改用指数平滑做基线。坑四缺失值填 0 后自相关结构被破坏。现象是模型把无销售日当成真实零销量预测偏低。原因是补全策略选错。解决区分「真实零销量」和「数据缺失」前者填 0后者用interpolate(methodtime)插值并在建模前用ts.isna().sum()确认缺失比例。坑五用全量数据训练后直接预测未来没有留验证集。现象是模型在训练集上表现很好上线后偏差大。原因是过拟合且没有滚动验证。解决留出最后 20% 做测试集用model.fit()只训练前 80%再滚动预测测试集计算 MAE、RMSE 对比基线。5. 滚动预测与效果验证把模型推到能上线的最后一步定阶和诊断通过后真正决定这套模型值不值得投入的是滚动预测表现。静态的一次性预测只能看趋势滚动预测模拟真实业务中「每天用最新数据重训、预测下一天」的节奏得到的误差才有参考价值。下面这段代码实现单步滚动预测并和「用历史均值预测」的朴素基线对比。from sklearn.metrics import mean_absolute_error, mean_squared_error train_size int(len(ts) * 0.8) train, test ts[:train_size], ts[train_size:] history list(train) predictions [] for t in range(len(test)): model ARIMA(history, orderbest_order) res model.fit() yhat res.forecast(steps1)[0] predictions.append(yhat) history.append(test[t]) # 把真实值加入历史滚动前进 mae mean_absolute_error(test, predictions) rmse np.sqrt(mean_squared_error(test, predictions)) print(fARIMA 滚动预测 MAE: {mae:.2f}, RMSE: {rmse:.2f}) # 朴素基线用前一天真实值作为预测 naive_pred [test[i-1] for i in range(1, len(test))] naive_mae mean_absolute_error(test[1:], naive_pred) print(f朴素基线 MAE: {naive_mae:.2f})逻辑说明history.append(test[t])是滚动预测的关键每预测一步就把真实观测值并入训练集模拟线上每天重训的场景。参数上steps1表示单步预测业务要预测未来 7 天就改成steps7但注意多步预测误差会累积建议同时输出置信区间。对比基线很重要如果 ARIMA 的 MAE 没有明显低于朴素基线说明这套数据用 ARIMA 性价比不高应该考虑换模型或加外生变量。验证通过后把最终模型封装成可复用函数固定随机种子和阶数输出预测值和 95% 置信区间。我自己的习惯是每次上线前跑一遍滚动验证把 MAE 和基线差距记在项目笔记里下次换 SKU 或换门店时直接对比避免重复踩同样的坑。这套流程不复杂但每一步都做实ARIMA 就能成为你销量预测工具箱里最稳的那块基线。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →