☰
Python ARIMA销量预测实战:从数据清洗到滚动预测的完整指南
2026/9/28 13:31:23 网站建设 项目流程

简介:这份资源是面向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(), freq="D") ts = ts.reindex(full_idx, fill_value=0) ts.name = "qty" print(ts.head()) print("序列长度:", len(ts))

逻辑说明:groupby("date")["qty"].sum()把同一天的多条记录合并成一条,这是 ARIMA 要求等间隔的前提。reindex补全缺失日期很关键,因为 ARIMA 靠时间间隔计算差分和滞后,日期跳跃会让差分错位。参数上freq="D"表示按天,如果业务是按周汇总就改成"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(), autolag="AIC") 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(), "二阶差分")

逻辑说明:autolag="AIC"让 statsmodels 自动选择滞后阶数,避免手动指定。判断规则是:原始序列 p 值小于 0.05,d 取 0;一阶差分后 p 值小于 0.05,d 取 1;以此类推。绝大多数销量序列一阶差分就够了,d 很少超过 2。参数上不要盲目追求 p 值极小,差分次数过多会过度消除信息,导致 MA 项难以估计。如果一阶差分后 p 值在 0.05 附近徘徊,结合时序图判断:有明显趋势就取 d=1,趋势已被业务解释(如促销一次性拉高)可以考虑 d=0 加外生变量。

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, lags=30, ax=axes[0]) plot_pacf(diff_ts, lags=30, ax=axes[1], method="ywm") plt.tight_layout() plt.savefig("acf_pacf.png", dpi=120)

逻辑说明:lags=30表示看 30 期内的相关性,日销量建议看 30 到 60 期,月销量看 12 到 24 期。method="ywm"是 Yule-Walker 估计,样本量小时比默认方法稳定。图形只用来缩小范围,比如 PACF 在 1、2 阶后落入置信带,就把 p 的候选定在 0 到 3;ACF 类似处理 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 的 Series,statsmodels 会自动识别频率。res.aic和res.bic都记录下来,AIC 偏向预测,BIC 偏向简约,样本量大时两者结论接近。参数上搜索范围 0 到 5 对大多数销量序列足够,如果数据有强周季节,p 或 q 可能要放到 7,但这时更推荐用 SARIMA。warnings.filterwarnings("ignore")只是屏蔽搜索阶段的不收敛提示,最终模型要单独检查收敛状态,不能一直屏蔽。

3.2 训练最终模型并读懂 summary 里的关键指标

拿到最优阶数后重新拟合,重点看系数显著性、Ljung-Box 检验和残差正态性。

final_model = ARIMA(ts, order=best_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_df=True) 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, bins=30) axes[0, 1].set_title("残差分布") stats.probplot(resid, dist="norm", plot=axes[1, 0]) axes[1, 0].set_title("Q-Q图") plot_acf(resid, lags=30, ax=axes[1, 1]) axes[1, 1].set_title("残差ACF") plt.tight_layout() plt.savefig("resid_diag.png", dpi=120)

逻辑说明:残差时序图看有没有趋势或周期残留;直方图和 Q-Q 图看是否近似正态,偏离严重会影响置信区间;残差 ACF 看有没有滞后相关。参数上bins=30是经验值,样本少就减到 15。如果 Q-Q 图两端偏离,但 Ljung-Box 通过,预测点估计仍然可用,只是区间估计要谨慎。这一步的结论直接决定模型能不能进入滚动预测,不要跳过。

4. 避坑与排查:ARIMA 落地时最容易翻车的五个地方

ARIMA 代码短,但坑不少。下面五条是我在真实销量数据上反复踩过的,每条按现象、原因、解决写清楚。

坑一:预测值是一条水平直线。现象是 forecast 输出几乎不变。原因是 d 取 0 且数据有趋势,模型把趋势当成噪声。解决:重新做 ADF 检验,确认 d 至少为 1;如果业务上趋势确实存在,不要为了平稳性强行 d=0。

坑二: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(method="time")插值,并在建模前用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, order=best_order) res = model.fit() yhat = res.forecast(steps=1)[0] predictions.append(yhat) history.append(test[t]) # 把真实值加入历史,滚动前进 mae = mean_absolute_error(test, predictions) rmse = np.sqrt(mean_squared_error(test, predictions)) print(f"ARIMA 滚动预测 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])是滚动预测的关键,每预测一步就把真实观测值并入训练集,模拟线上每天重训的场景。参数上steps=1表示单步预测,业务要预测未来 7 天就改成steps=7,但注意多步预测误差会累积,建议同时输出置信区间。对比基线很重要:如果 ARIMA 的 MAE 没有明显低于朴素基线,说明这套数据用 ARIMA 性价比不高,应该考虑换模型或加外生变量。

验证通过后,把最终模型封装成可复用函数,固定随机种子和阶数,输出预测值和 95% 置信区间。我自己的习惯是每次上线前跑一遍滚动验证,把 MAE 和基线差距记在项目笔记里,下次换 SKU 或换门店时直接对比,避免重复踩同样的坑。这套流程不复杂,但每一步都做实,ARIMA 就能成为你销量预测工具箱里最稳的那块基线。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询