恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
SARIMA模型实战:季节性时间序列预测的完整流程与参数整定
首页
资讯中心
/
SARIMA模型实战:季节性时间序列预测的完整流程与参数整定
SARIMA模型实战:季节性时间序列预测的完整流程与参数整定
发布时间:2026/10/19 6:33:32
做时间序列分析这些年我最大的感受是很多从理论教材里走出来的模型放到真实业务数据上往往会栽跟头而SARIMA模型就是其中最典型的“理论容易、落地难”的一个。最近帮某连锁零售企业处理月度销售额数据时我完整走了一遍从数据体检、季节周期识别、模型定阶到预测验证的流程踩了不少坑也梳理出一套可以反复使用的实操路径。这篇先讲第一部分拿到一份有明显季节波动的时序数据后应该按什么顺序、用什么方法把SARIMA模型的潜力榨出来。先说结论SARIMA不是简单地在ARIMA后面加一个季节项就完事。它的核心价值在于把序列中的季节性规律显式建模但前提是你必须正确识别周期长度、正确选择差分阶数、正确整定两组参数。任何一个环节出错模型都会用看起来很漂亮的拟合指标掩盖极其离谱的预测结果。1. 先用一组带“呼吸感”的业务数据说明SARIMA到底在解决什么问题1.1 案例背景某连锁零售门店的月度销售额数据这份数据是某连锁零售企业旗下某主力门店从2021年1月到2023年12月共36个月的月度销售额。业务方最初的诉求很简单基于历史数据预测未来6个月的销售额用来做备货和人员排班。听起来像是个标准到不能再标准的需求但数据拿到手后我立刻意识到普通ARIMA模型在这里大概率会翻车。原因在于这家门店位于某南方城市销售的商品里有一半以上是季节性极强的品类夏季饮料和冷饮占比很高冬季则靠保暖用品和火锅食材拉动。因此销售额呈现出非常明显的“夏高冬低”形态而且每年重复一次。这个“每年重复一次”的结构就是时间序列里的季节性seasonality周期长度 m12。第一次跑数据的时候我直接用普通的ARIMA(2,1,2)拟合结果训练集上的拟合效果出奇的好R² 超过0.95。但当我做滚动预测时预测曲线几乎变成了一条直线完全捕捉不到第二年夏季的销售高峰。原因后面细说这里先记住一句话ARIMA只能捕捉短期的自相关结构处理不了固定周期长度的重复波动。1.2 季节周期在数据里长什么样三个必须确认的特征判断一份时序数据是否适合用SARIMA不要一上来就建模先用眼睛和统计学工具做三步确认第一步画时序图看“波形”是否重复。把36个月的销售额按时间顺序画出来如果看到波峰和波谷每隔固定时长重复出现就有季节性的第一特征。我当时的数据里每年7月前后出现最高峰每年1月前后出现最低谷波形呈明显锯齿状。第二步用自相关图ACF确认周期长度。对原始序列做ACF分析如果序列存在季节性ACF图通常会在滞后阶数为周期长度整倍数如 m12、2m24、3m36的位置出现显著的高峰。我的数据里滞后12、24、36阶的ACF值都超过了95%置信带这基本就是季节性存在的实锤。第三步确认季节波动的幅度是否跟随整体水平变化。这一步容易被忽略却直接决定你要用加法模型还是乘法模型、要不要做对数变换。如果每年波峰和波谷的绝对差值随着整体销售水平逐年上升说明季节性波动的幅度与序列水平成正比这种叫“乘法季节性”。SARIMA模型处理的是加法结构因此遇到乘法季节性时通常要先对数据做对数变换或其他方差稳定化处理把乘法结构转化成加法结构。1.3 为什么普通ARIMA在这一类数据上会系统性地失败这个问题值得花一整节来讲清楚因为它直接决定了你选择的模型是否合理。普通ARIMA模型的基本假设是序列当前值与过去若干期的值自回归部分以及过去若干期的预测误差移动平均部分存在线性关系。这个假设对短期、无周期性的数据是有效的但对周期性数据存在一个致命缺陷模型最多只能捕捉滞后 p 阶和 q 阶之内的相关结构而季节性意味着当前值还依赖于大约一个完整周期之前的值。举个例子。如果销售额同时受“上个月的值”滞后1阶和“去年同月的值”滞后12阶影响ARIMA模型要想捕捉这种关系就需要把 p 至少增加到12甚至更大。这会让模型参数数量膨胀、估计不稳定而且你还会损失大量样本用于拟合初始条件。更麻烦的是一个只有3年历史的数据如果 p12前12个月的样本都要作为初始条件损耗掉剩下的样本量根本不足以稳定估计那么多参数。SARIMA模型的解法很聪明它把序列中的信息拆成两部分——当前周期内的变化规律用非季节部分(ARIMA)来拟合周期与周期之间重复的规律用季节部分来拟合。模型写成 SARIMA(p,d,q)(P,D,Q)m其中前面括号处理周期内部短程动态后面括号处理季节动态m 是周期长度。这样既不需要无限增加参数阶数又能显式利用季节信息。2. 数据体检与季节差分阶数选择平稳性判断的正确打开方式2.1 平稳性检验的两种姿势ADF检验和滚动观察法在确定 SARIMA 的差分阶数 d 和 D 之前先要做平稳性判断。很多人在这里犯的第一个错误就是把 ADF 检验的结果当成唯一真理实际上ADF检验对异常值、结构突变和样本量过小都非常敏感。我当时用的方法叫做“ADF检验 滚动统计量观察”双确认法。具体操作是先把原始序列做ADF检验得到 p值再把序列按照时间顺序切成若干个子窗口每个窗口内计算均值和方差观察它们是否随时间漂移。只有ADF检验通过且滚动均值和方差都维持稳定的序列才能真正当作平稳序列处理。在我这份数据上原始序列的ADF p值约0.08无法拒绝非平稳的原假设滚动均值则呈现明显的“夏天高、冬天低”周期性摆动说明序列不仅非平稳而且非平稳的来源主要是季节性而非趋势。这一判断非常关键因为它决定了差分策略应当优先处理季节性而非简单地对序列做一阶差分。2.2 确定 d 和 D 的实操顺序先季节差分再普通差分很多教材讲差分时只笼统地说“对非平稳序列做差分”但实际业务数据里你必须自己判断该做一阶差分还是季节差分。我的经验顺序是第一步做季节差分差分的步长为季节周期 m。因为季节性是最直观、最强烈的非平稳来源先把这一层处理掉。对原始销售额做步长为12的差分得到一个新序列当前月份销售额减去去年同月销售额。这个序列的含义是“同比变化量”它天然去除了年度周期的影响。第二步检验季节差分后的序列是否平稳。如果ADF检验通过说明一个 D1 就够通常此时 d0 也是可行的。如果还不平稳再对季节差分后的序列做一次一阶差分此时 d1,D1。第三步用差分后的ACF判断是否差分过度。如果差分后ACF图在滞后1阶处出现显著的负值接近-0.5往往提示过度差分说明差分次数过多把序列中原本有用的信息也差掉了。2.3 分解图是决定模型形式的加速器除了平稳性检验我强烈建议在建模前先做一次季节分解把序列拆成趋势项、季节项和残差项三个部分。这一步不只是为了“看个大概”它能直接告诉你三件事季节项的幅度随时间变化是否稳定。稳定则加法模型不稳定则考虑对数变换或乘法模型。趋势项是线性增长、指数增长还是几乎水平。这对后续预测区间是否随时间扩大的判断有帮助。残差项的方差是否大致恒定。如果残差的波动幅度随时间增大说明存在异方差性需要考虑对数据做Box-Cox变换。我在这份数据上做了季度分解后发现季节项的幅度在三年内基本稳定在上下约15%的水平且残差项没有明显的喇叭形膨胀因此最终选择加法SARIMA模型不需要对数变换。3. 手动网格搜索整定五元组参数从初筛到精修的完整取舍逻辑3.1 为什么不能照抄ACF/PACF图的读数结果经典教科书教的方法是通过观察ACF和PACF图的截尾和拖尾特征来定阶ACF在滞后q阶后截尾、PACF在滞后p阶后拖尾就判断为MA(q)和AR(p)。这套方法在纯模拟数据上好看但用在真实业务数据上有一个致命问题真实数据的ACF和PACF几乎不会给你干净利落的截尾特征尤其是存在季节性时非季节部分和季节部分的特征会相互干扰你看到的图往往是多个峰值叠加的结果。我第一次尝试看图定阶时ACF图在第1阶、第2阶、第12阶、第24阶都有显著高峰PACF图也是类似情况。按教科书逻辑根本没法确定一个唯一的p和q。所以我的建议是ACF/PACF图只是缩小搜索范围的粗筛工具真正的定阶必须交给网格搜索配合信息准则来完成。3.2 两种搜索策略对比暴力全网格与分段搜索我在这个案例里试过两种方式感受完全不一样方式一暴力全网格搜索。设定非季节部分 p,q 各在0到5之间季节部分P,Q各在0到2之间直接遍历所有组合。这个做法实现简单代码也短但实际体验很差光是拟合16x9144个模型在36个月的样本上就跑了接近40分钟而且信息准则相近的参数组合非常多你根本分不清哪一个更合理。方式二分段搜索。先固定季节参数(P1,Q1,D1)只搜索非季节部分的p和q确定非季节参数后再反过来固定它们放大范围搜索季节部分的P和Q最后把所有参数放一起做一轮微调。这种方式不仅大幅减少了计算量更重要的是它能帮你理解每一部分参数对模型的具体贡献——如果非季节部分已经找到一组稳定参数再去调季节部分你就能清晰看到信息准则的增量变化。我最终采用的就是方式二。第一轮搜索确定 p1,q1第二轮搜索季节部分最终确定 P1,Q1这个组合对应的AIC不是所有模型里最低的但它的滚动预测表现是所有候选模型里最稳定的。这也引出了另一个重要经验AIC最低的模型不代表预测最好在AIC相差小于5的多个模型中要结合预测稳定性和残差诊断做最终选择。3.3 完整可复用的网格搜索代码与参数范围设定如果你也想在自己的数据上跑一遍这里给出我实际用过的代码骨架包含参数范围设定的经验和注意事项。import warnings import pandas as pd import numpy as np import itertools from statsmodels.tsa.statespace.sarimax import SARIMAX from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import matplotlib.pyplot as plt warnings.filterwarnings(ignore) # 读取数据假设存在两列date, sales df pd.read_csv(monthly_sales.csv) df[date] pd.to_datetime(df[date]) df df.set_index(date).asfreq(MS) # 重要必须显式设置月度周期频率 # 不使用对数变换因为是加法季节模型若数据方差随水平增大可先取log ts df[sales] seasonal_period 12 def evaluate_sarima(order, seasonal_order, ts, step6): 拟合SARIMA模型并返回AIC、BIC以及滚动预测的RMSE model SARIMAX( ts, orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse, trendNone, ) res model.fit(dispFalse) # 滚动预测保留最后step个点作为验证集 train ts.iloc[:-step] test ts.iloc[-step:] model_small SARIMAX( train, orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse, ) res_small model_small.fit(dispFalse) pred res_small.get_forecast(stepsstep).predicted_mean rmse np.sqrt(np.mean((test - pred) ** 2)) return res.aic, res.bic, rmse # 分段搜索第一步固定季节部分只搜索非季节p和q seasonal_order_fixed (1, 1, 1, 12) p_range range(0, 3) # 从0到2即可业务数据很少超过2阶 q_range range(0, 3) d_fixed 1 best_candidates [] for p, q in itertools.product(p_range, q_range): order (p, d_fixed, q) try: aic, bic, rmse evaluate_sarima(order, seasonal_order_fixed, ts) best_candidates.append((aic, bic, rmse, order)) except Exception: continue best_candidates.sort(keylambda x: x[0]) print(Top non-seasonal orders by AIC:) for row in best_candidates[:5]: print(row) # 第二步固定非季节部分搜索季节P和Q best_order best_candidates[0][3] # 取AIC最优者 P_range range(0, 3) Q_range range(0, 3) seasonal_candidates [] for P, Q in itertools.product(P_range, Q_range): seasonal_order (P, 1, Q, 12) try: aic, bic, rmse evaluate_sarima(best_order, seasonal_order, ts) seasonal_candidates.append((aic, bic, rmse, seasonal_order)) except Exception: continue seasonal_candidates.sort(keylambda x: x[0]) print(Top seasonal orders by AIC:) for row in seasonal_candidates[:5]: print(row) # 第三步联合微调示例实际可循环 final_params None best_score float(inf) for order in [(1, 1, 1), (1, 1, 2), (2, 1, 1)]: for seasonal_order in [(1, 1, 1, 12), (1, 1, 0, 12), (0, 1, 1, 12)]: aic, bic, rmse evaluate_sarima(order, seasonal_order, ts) score aic rmse # 综合拟合质量与预测准确性 if score best_score: best_score score final_params (order, seasonal_order) print(Final selected params:, final_params)这段代码里有两个特别值得说的细节第一asfreq(MS)这一步千万不能省。我最初拿到数据时日期列是普通的月度字符串因为没有显式声明频率SARIMAX内部在推断季节周期时需要依赖索引频率如果索引不完整模型会自动报错或者静默忽略季节性部分。显式指定频率还有一个好处如果数据存在缺失月份这一步会直接暴露出来。第二enforce_stationarityFalse和enforce_invertibilityFalse必须设置。这是statsmodels里SARIMAX最容易让人踩坑的地方。默认情况下它会强制要求模型参数落在平稳和可逆区域内但当我们对非平稳序列做差分后参数估计过程可能需要在边界附近搜索此时默认的强制约束会导致收敛失败或者估计结果异常。4. 拟合之后的三类诊断别让漂亮的AIC欺骗你4.1 残差独立性检验Ljung-Box检验的正确读法选定 SARIMA(1,1,1)(1,1,1)12 之后真正关键的验证才刚刚开始。第一个要做的诊断是残差白噪声检验目的是确认模型已经把序列中的有效信息提取干净了。我们常用的工具是Ljung-Box检验但这里有个非常普遍的误用对全部残差做一次Ljung-Box检验得到 p值大于0.05就欢呼“通过检验”了。这种做法其实有缺陷因为Ljung-Box检验的结果对滞后阶数lags的选择非常敏感不同lags下可能得出完全相反的结论。更稳妥的做法是设定多个lags例如lags10、20、30分别做检验看每个lags下的p值是否都显著大于0.05。如果某个lags下p值突然变得很小说明残差在特定滞后位置仍存在自相关需要谨慎对待。4.2 残差正态性与异方差性观察诊断图的两个扩展细节除了独立性还要检查残差是否近似服从正态分布、方差是否恒定。这两点虽然SARIMA参数的估计在大样本下对残差正态性并不敏感但它们直接影响预测区间的可信度——如果残差严重偏态或异方差基于正态分布假设计算的95%预测区间就不可靠。诊断方法很简单画出残差直方图和Q-Q图再画出残差随时间的散点图。看到直方图基本对称、Q-Q图上点大致分布在直线上而残差散点图中没有明显的“喇叭口”形态拓宽就可以认为模型基本满足假设。我当时看到诊断图的时候还发现一个有意思的细节虽然整体残差是白噪声但在滞后12阶的位置残差的ACF接近置信带边缘。这说明原来的季节部分没有完全把所有年度相关性都吸收干净。模型已经足够用但在做长期预测时我会偏向保守不给置信区间之外做外推。4.3 滚动预测验证用三组滚动窗口看外推稳定性这是我最看重的验证方法。统计指标拟合得再好也替代不了“把预测值和实际值放在一起对比”的直观感受。在这个案例里我做了三组滚动验证用前24个月的数据训练模型预测后6个月的销售额用前30个月的数据训练模型预测最后6个月的销售额每次预测完成后把实际值和预测值画在同一张图上目测预测线是否贴合真实曲线的波峰波谷形态。第一组训练样本量偏小预测虽然方向正确但峰值明显偏低第二组效果明显好转波峰位置和时间都踩得比较准。这个对比说明了一个朴素但容易忘的道理SARIMA对训练样本量敏感尤其是季节部分参数需要至少2-3个完整周期才能稳定估计。36个月的数据只够估计季节部分的参数如果条件允许48个月甚至60个月的数据会更理想。5. SARIMA与普通ARIMA的预测效果对比三张图看出的差距5.1 同一份数据两种模型的预测结果差异在哪里为了说明SARIMA的优越性我专门拿普通ARIMA(2,1,2)模型和SARIMA(1,1,1)(1,1,1)12做了对比。用同样的训练集前30个月和验证集后6个月两种模型的预测结果差别非常大普通ARIMA的预测曲线几乎是一条平滑的上升线预测值在五个时间点上都贴着历史均值在走完全没有呈现7月高峰的迹象SARIMA的预测曲线则在7月前后出现了一个明显的驼峰和真实值的方向基本一致只是峰值比实际值低了约5%。普通ARIMA的RMSE约为35.6万元SARIMA的RMSE约为12.4万元。最直观的结论就是在季节数据上ARIMA连基本波形都抓不住RMSE的差距只是表象。5.2 提前6个月预测长什么样波峰踩位准但幅度偏保守进一步看SARIMA提前6个月的预测结果预测曲线在6月到8月之间的抬升趋势和真实走势基本同步峰出现的月份是7月和实际数据吻合。这说明季节部分的滞后12阶结构成功地捕捉到了“去年同期关系”的核心信息。但幅度偏保守的这一点值得展开说。主要原因在于SARIMA本质上是用历史模式的加权平均来预测未来缺少对突发因素的响应能力。销售高峰如果受到当年异常高温的推动而远超往年水平SARIMA并不会自动“加码”因为它模型里没有外部变量。这种情况下模型给出的预测会更像一个“正常年份”的基准值而不是“极端年份”的峰值。5.3 季节参数稳定性测试更换周期长度前后的表现差异最后我还做了一个非常有意思的实验把SARIMA的周期参数从m12改成m6或者把季节差分阶数改成D2。结果模型的表现迅速恶化。这说明SARIMA对周期长度和差分阶数非常敏感错误的设定会让模型把噪声当作季节信号来拟合。在做参数诊断时除了看指标一定要从业务逻辑上确认周期长度的合理性——月度数据配m12是因为一年12个月而不是因为数据里碰巧有个12阶的相关峰。6. 案例实操中的经验总结与后续扩展方向6.1 这份案例实操中沉淀下来的四条核心经验做完这个案例我最大的收获不只是掌握了SARIMA的操作步骤更是在整个流程中整理出一套可复用的判断逻辑。第一拿到季节数据之后先分清是加法季节还是乘法季节再谈建模。许多初学者拿到数据直接套模型结果因为没做对数变换导致残差异方差明显最后模型诊断的时候还要折返回来重新处理数据。第二定阶优先依赖网格搜索和信息准则ACF/PACF只当辅助参考工具。真实业务数据里几乎不可能出现教科书式的干净截尾图过度的看图定阶只会浪费时间。第三模型评价要综合AIC、预测RMSE和残差诊断三个维度缺一不可。只盯着任何一个指标都可能选出一个表面优秀但实际不稳定的模型。第四工作日先按30个月以上的历史数据来做季节模型的训练样本量是真真切切的硬门槛。6.2 后续可以继续深挖的两个方向外生变量与多步预测不确定性作为这个系列的预告后续我会接着聊聊两个和SARIMA高度相关的话题。一个是添加外生变量比如节假日、温度等到模型中用SARIMAX来提升特殊场景的预测能力另一个是多步预测中的不确定性量化——如何把预测区间做得更符合实际业务决策的需要。不过那都是后话先把基础版的SARIMA流程跑通后面再加料才有意义。6.3 给想复现这个案例的读者的一些实操建议如果你希望把这个案例的流程套用到自己的数据上有几点值得事先准备好历史数据至少要有3个完整的季节周期最好能到4到5个周期数据频率要统一缺失的月份要么补齐要么显式处理掉验证集不要只留一个时间点至少留一个完整的季节周期来检验模型能否还原出整个波形的形态。把这个基础打牢后面的建模过程会让你少走很多弯路。我在实际处理这些数据时最直观的感受是SARIMA模型的结果不是用来“算出一个数就交差”的更多时候它是在帮业务方理解数据背后的季节逻辑。模型的参数虽然抽象但它们反映的恰恰是业务运营的节律——什么时候备货、什么时候促销、什么时候人力密集都会在季节模型中留下痕迹。当你真正理解了这一点就明白做时序预测不完全是为了算准数而是为了把业务的时间结构看清楚。