资讯详情

时间序列预测Python实战:ARIMA/SARIMA与Prophet完整建模流程

📅 2026/10/12 22:48:07 | 华诺云谱 👁 阅读
时间序列预测Python实战:ARIMA/SARIMA与Prophet完整建模流程
简介针对Python时间序列预测的完整可运行代码包适合数据分析与机器学习学习者也适用于金融、气象、销售等需要趋势预测的场景。包内以181个可执行脚本为主配合32个示例数据文件和1份说明文档覆盖数据读取、清洗、缺失值处理、差分平稳化、模型构建、训练、评估与可视化等环节压缩包共214个文件整体仅237KB便携易用。通过太阳黑子、气温、每日出生人数等真实样例数据读者可动手验证经典统计模型、季节性模型与机器学习方法的差异学习如何选取评价指标并优化预测结果。已有7257人学习该资源既能作为课程配套练习也能帮助入门者建立完整的时间序列建模思路。1. 这份时间序列预测 Python 代码包三个数据集带你走完完整建模流程如果你正在学时间序列预测大概率卡在同一个位置理论看懂了 ARIMA 的公式但真拿到一份 CSV 数据不知道第一步该做什么。这份代码包恰好解决这个问题——它内置了三个经典数据集每日最低气温、太阳黑子数、每日女性出生人数覆盖了无季节性、强季节性、纯随机三种典型形态配套代码从数据读取一路写到模型评估。换句话说它不是一个泛泛的教程而是一套能直接跑通的完整建模流程。适合刚入门 Python 时间序列分析的读者也适合想快速对照标准实现方式的老手。我会在下面把每个文件怎么用、参数怎么调、坑在哪里逐一拆开讲透。2. 三个数据集的差异决定模型方向先搞清楚你在预测什么2.1 从文件名读懂数据形态气温、太阳黑子与出生人数压缩包里三个 CSV 文件不是随便放的它们分别对应时间序列预测里的三种典型场景。先看daily-minimum-temperatures.csv这是澳大利亚墨尔本 1981 到 1990 年的每日最低气温天然带明显的年度季节性——冬天冷夏天热周期固定适合用 SARIMA 或者 Prophet 这类能捕捉季节性的模型。再看sunspots.csv记录的是每月太阳黑子数量这个数据的特点是周期大约 11 年但周期长度和振幅都会波动不是严格的固定季节性更适合用来练习 ARIMA 或者状态空间模型这类能处理非平稳序列的方法。最后是daily-total-female-births.csv美国每日女性出生人数这个序列没有明显的趋势和季节性波动接近随机用它来理解预测的极限特别合适——有些序列本身可预测性就很低。import pandas as pd # 分别读取三个数据集注意日期列的解析 temps pd.read_csv(daily-minimum-temperatures.csv, parse_dates[Date], index_colDate) sunspots pd.read_csv(sunspots.csv, parse_dates[Month], index_colMonth) births pd.read_csv(daily-total-female-births.csv, parse_dates[Date], index_colDate) print(temps.head()) print(temps.index)这里parse_dates参数会把日期字符串转成DatetimeIndex这是后面所有时间序列操作的前提。如果没有正确解析日期索引pandas 的resample、shift、rolling这些方法都没法用。我一般会在读取后立刻打印index确认类型这一步能省掉后面排查索引问题的半天时间。三个数据集的索引频率也不同——气温和出生人数是日频太阳黑子是月频这个频率信息在建模时要传给模型后面我会细说。2.2 先用可视化建立直觉趋势、季节性与噪声在跑任何模型之前先把数据画出来看。这一步不是走形式而是通过肉眼判断序列里有没有趋势、季节性以及波动幅度是否随时间变化。matplotlib 是基础工具但直接画长序列的折线图容易糊成一团我习惯同时画原始序列和滚动均值滚动均值能平滑短期波动突出长期趋势。import matplotlib.pyplot as plt fig, axes plt.subplots(3, 1, figsize(12, 10)) # 气温数据能看到明显的年季节性 temps[1981:1982].plot(axaxes[0], titleDaily Min Temperatures) axes[0].set_ylabel(Celsius) # 太阳黑子周期长且不规则 sunspots.plot(axaxes[1], titleMonthly Sunspots) axes[1].set_ylabel(Count) # 出生人数无明显趋势接近随机波动 births.plot(axaxes[2], titleDaily Female Births) axes[2].set_ylabel(Count) plt.tight_layout() plt.show()画完图之后要做三件事第一判断趋势——长期来看序列是上升、下降还是水平第二判断季节性——是否在固定时间间隔内重复出现相似形态第三判断方差是否稳定——波动幅度是否随时间变化。这三个判断直接决定后面选哪个模型。气温数据有明显的年度季节性太阳黑子有长周期但不够规律出生人数则几乎没有可预测的结构。很多新手跳过这一步直接套 ARIMA结果模型拟合了一堆噪声预测出来全是直线问题就出在没有先用可视化确认数据特征。2.3 数据预处理里的两个关键动作缺失值填充与差分真实项目里的数据很少是干净的这份代码里的数据虽然相对规整但预处理步骤不能省。最常遇到的问题是缺失值处理方式取决于缺失的比例和位置。如果缺失值不多前向填充ffill或者线性插值interpolate都够用如果缺失较多可能要先用resample统一时间频率再插值。另一个关键操作是差分——ARIMA 模型要求输入序列平稳而差分是消除趋势最直接的手段。# 检查缺失值 print(temps.isnull().sum()) # 线性插值填充缺失值 temps_filled temps.interpolate(methodlinear, limit_directionboth) # 一阶差分消除趋势观察是否变得更平稳 temps_diff temps_filled.diff().dropna() fig, axes plt.subplots(2, 1, figsize(12, 8)) temps_filled.plot(axaxes[0], titleOriginal) temps_diff.plot(axaxes[1], titleFirst Differenced) plt.show()差分操作的意义在于把非平稳序列转换成平稳序列。以气温数据为例原始序列呈现明显的年度周期性均值在不同季节差别很大这不符合平稳性要求。做了一阶差分之后每个值变成当天与前一天的温度差周期性被大幅削弱。差分阶数不是越多越好——过度差分会让序列丢失太多信息噪声比例反而上升。判断差分效果的标准做法是观察差分后的序列是否在零附近随机波动也可以用 ADF 检验来量化判断这个我在下一章展开。3. 平稳性是 ARIMA 的入场券ADF 检验与定阶实战3.1 ADF 检验别用眼睛判断平稳性上一章提到差分后看起来平稳了但肉眼判断在论文和技术评审时站不住脚需要用统计检验来确认。ADFAugmented Dickey-Fuller检验是应用最广的平稳性检验方法它的原假设是序列存在单位根即非平稳。当 p 值小于显著性水平通常取 0.05时拒绝原假设认为序列平稳。from statsmodels.tsa.stattools import adfuller def test_stationarity(series, name): result adfuller(series.dropna()) print(f{name} ADF stat: {result[0]:.4f}) print(f{name} p-value: {result[1]:.4f}) if result[1] 0.05: print(f{name}: 平稳拒绝单位根假设) else: print(f{name}: 非平稳需要进一步差分) # 分别检验原始序列和一阶差分序列 test_stationarity(temps[1981], temps original) test_stationarity(temps[1981].diff().dropna(), temps diff1)注意我在这里用了temps[1981]而不是全量数据。时间序列的平稳性是随时间变化的用 10 年数据测试出来的结果可能和最近一年不同。实际操作中我会多测几个窗口——比如最近一年、最近三年和全量综合判断。如果全量数据平稳但最近一年不平稳说明序列的结构近期发生了变化这时候直接建模风险很大可能需要用更近的数据训练。ADF 检验输出的第一个值是检验统计量第二个是 p 值还有几个辅助参数这里没打印——滞后阶数、样本量等——一般不需要自己调整statsmodels 默认的autolagAIC会自动选最优滞后阶数这个设计比较合理多数情况直接用就行。3.2 ACF 与 PACF 定阶理解自相关图的信息确认序列平稳之后下一步是确定 ARIMA 模型的阶数——p自回归阶数、d差分阶数、q移动平均阶数。d 已经在差分时确定了p 和 q 看 ACF自相关函数和 PACF偏自相关函数图。这个环节是新手最容易懵的地方很多教程画了两张图然后说看图可知但没解释怎么看。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf # 以气温数据一阶差分序列为例 temps_diff temps.diff().dropna() fig, axes plt.subplots(2, 1, figsize(12, 8)) plot_acf(temps_diff, axaxes[0], lags40) plot_pacf(temps_diff, axaxes[1], lags40) plt.show()判定逻辑其实不复杂看 ACF 图如果自相关在某个滞后阶数之后突然截断掉到置信区间内这个截断的阶数就是 q 的候选值看 PACF 图如果偏自相关在某个滞后阶数之后截断这个阶数就是 p 的候选值。更常见的情况是两者都缓慢衰减拖尾这时候 p 和 q 都需要取较小的值再结合 AIC/BIC 去搜索。气温数据做一阶差分后仍有年度季节性残余ACF 在滞后 365 附近会出现明显峰值这说明仅靠非季节性 ARIMA 不够需要引入 SARIMA。这是后面第四章的内容。3.3 网格搜索定阶让 AIC 帮你做选择手动看图定阶尤其是 p 和 q 范围较大时效率太低且容易漏掉更好的组合。我习惯写一个小网格搜索遍历候选的 p、d、q 组合用 AIC赤池信息准则选最优。AIC 的哲学是在拟合优度和模型复杂度之间取平衡——AIC 越低越好但差值小于 2 的模型之间没有显著差异这时选参数更少的那个。import itertools import warnings from statsmodels.tsa.arima.model import ARIMA warnings.filterwarnings(ignore) best_aic float(inf) best_order None # 用全量气温数据做差分后搜索d 固定为 1 for p in range(0, 3): for q in range(0, 3): order (p, 1, q) try: model ARIMA(temps, orderorder) result model.fit() if result.aic best_aic: best_aic result.aic best_order order except Exception: continue print(fbest order: {best_order}, AIC: {best_aic})这段代码里有个细节值得注意搜索范围只取了 0 到 2因为气温数据本身结构并不复杂更高阶的参数容易过拟合。如果你处理的数据周期性更强可以适当扩大范围但搜索空间会指数增长——3 个参数各取 0 到 5 就有 216 种组合每种组合都要拟合一次模型数据量大时要等很久。实际工程中我会分两步先用小步长粗搜定位一个大致区域再在最优值附近细搜这样能省一半时间。另外ARIMA拟合时会输出大量日志加上warnings.filterwarnings(ignore)可以屏蔽收敛警告但注意——如果结果里出现大量ConvergenceError说明数据有问题或者模型阶数不合适不能一股脑忽略掉。4. 季节性数据用 SARIMA 与 Prophet两个方案的选型对比4.1 SARIMA 四个季节性参数的组合逻辑气温数据有明显的年度季节性直接用 ARIMA 拟合效果不会好因为误差项里还残留着季节周期信息。SARIMA 在 ARIMA 的基础上增加了四个季节性参数P季节性自回归阶数、D季节性差分阶数、Q季节性移动平均阶数和 s季节周期长度。对日频气温数据s365对月度数据s12。from statsmodels.tsa.statespace.sarimax import SARIMAX # 用 SARIMA(1,1,1)(1,1,1,12) 拟合月度太阳黑子数据 # 注意太阳黑子数据的周期约 11 年但如果用月度数据先用 s12 尝试 model SARIMAX(sunspots, order(1, 1, 1), seasonal_order(1, 1, 1, 12), enforce_stationarityFalse, enforce_invertibilityFalse) result model.fit(dispFalse) print(result.summary())seasonal_order的最后一个参数 s 是最需要谨慎的。气温数据是日频周期 365 天直接把 s 设为 365 会带来两个问题一是模型参数骤增季节性部分每个周期要估计多个系数样本量不够时拟合极不稳定二是计算量成倍增加。实际操作中我一般先把数据降采样到月频或者周频把 s 降到 12 或 52模型训练速度快很多预测效果也不差。太阳黑子数据的周期实际接近 11 年约 132 个月用 s12 只能捕捉到年度周期长周期部分要靠非季节性参数和差分去处理这是一个很常见的取舍——模型的周期假设和真实周期不完全一致时先保证主要周期被捕捉剩余部分用自回归项吸收。4.2 Prophet 默认配置与参数调优习惯Prophet 在处理带强季节性的序列时非常高效因为它把预测问题拆成了趋势项、季节项和节假日项的组合用类似广义加性模型的框架拟合。facebook 开源的这个库对新手友好默认参数在很多场景下表现就不错。但默认好用不等于不需要调参实际项目中我最常调三个参数changepoint_prior_scale控制趋势拐点的灵活度默认 0.05数据趋势变化剧烈时调到 0.1 以上但也会增加过拟合风险seasonality_prior_scale控制季节性的强度默认 10如果季节性在预测中表现过于强烈、把噪声也当成周期了可以降到 3 左右seasonality_mode设为multiplicative适用于季节性振幅随时间增大的序列比如太阳黑子这种周期波峰波谷差异明显的。from prophet import Prophet import pandas as pd # Prophet 要求两列ds日期、y数值 sunspots_prophet sunspots.reset_index() sunspots_prophet.columns [ds, y] model Prophet( changepoint_prior_scale0.08, seasonality_prior_scale5.0, seasonality_modemultiplicative ) model.fit(sunspots_prophet) # 预测未来 60 个月 future model.make_future_dataframe(periods60, freqMS) forecast model.predict(future) fig model.plot(forecast) plt.show()Prophet 内部会自动检测年度季节性但对太阳黑子这种 11 年周期默认检测不出来所以我会手动添加周期性成分model.add_seasonality(namesolar_cycle, period132, fourier_order6)。fourier_order控制拟合该周期的复杂度默认对年度季节性取 10但如果设定的周期很长需要把它调低防止过拟合。另一个常见坑是日期频率——make_future_dataframe需要指定freq参数月频数据要用MS月初而不是M月末否则预测结果的日期对齐会出问题。4.3 两种方案的适用边界SARIMA 和 Prophet 不是互斥关系更像是不同场景的不同工具。SARIMA 的优势在于对单个序列的拟合精度高尤其是数据量适中、周期稳定的时候缺点是参数搜索麻烦、对缺失值敏感、预测分布计算慢。Prophet 的优势是调参门槛低、对缺失值和异常值容忍度高、预测结果自带置信区间适合快速出基线结果缺点是可解释性相对弱而且对复杂非线性关系比如多序列交互支持有限。我的经验是先跑一版 Prophet 拿到基线建立对数据可预测性的基本认知再用 SARIMA 精调——如果 Prophet 的基线误差已经很低SARIMA 的优化空间有限不值得投入太多时间如果基线误差明显偏高说明序列结构有 SARIMA 能捕捉而 Prophet 忽略的部分值得深挖。5. 时间序列预测的常见问题与避坑指南5.1 数据泄漏用未来数据训练模型现象模型在训练集上误差极低验证集上一塌糊涂预测结果严重滞后——比如预测的下一天温度基本等于真实值前一天的温度。原因最常见的是在预处理阶段用了全量数据的统计量。比如用全量数据的均值填充缺失值或者用全量数据做标准化这些操作把未来的信息泄露进了训练集。另一个高频错误是网格搜索定阶时用了全量数据计算 ACF/PACF这相当于提前知道了整个序列的结构特征。解决所有预处理步骤都要在训练集内部完成。先切分数据再做填充、差分、标准化。填充缺失值用训练集部分的插值结果差分操作按时间顺序进行不用窗口外数据参与计算定阶搜索也只用训练集。我习惯把数据切分写成如下模式强制自己在每步操作前先确认训练集边界train temps[:1988] test temps[1989:1990] # 所有预处理、模型拟合都基于 traintest 只出现在最终评估5.2 差分还原预测值是差分结果不是原始量纲现象用 ARIMA 做预测得到的预测值画在图上和原始数据对不上数值差了一整截看起来模型完全无效。原因训练时做了差分模型学习的是差值的变化规律预测出来的也是差值。如果直接把预测的差值当成原始值输出量纲和实际数据完全对不上。解决预测之后必须做差分还原。如果只做了diff()一阶差分还原方式是预测值 上一个实际观测值 差分预测值如果做了多阶差分还原要逐层累加。statsmodels 的ARIMAResults对象在调用predict时如果传入typlevels参数会自动做这个还原但要注意它依赖训练数据的最后一个观测值预测时间越长误差累计越明显。# 正确用法用 typlevels 得到原始量纲的预测值 forecast result.predict(startlen(train), endlen(train)10, typlevels)5.3 时序交叉验证顺序错误随机打乱训练集现象用train_test_split默认参数切分时间序列或者更糟——在交叉验证时用KFold打乱数据顺序模型训练集里混入了比验证集更晚的数据。原因时间序列数据本质上是按时间排序的未来不能影响过去。很多从机器学习转过来的同学习惯性地用随机划分因为常规分类任务里这样做是对的但时间序列里它会制造虚假的高性能——模型实际是偷看了未来的答案。解决时间序列必须用法时序交叉验证最常用的是TimeSeriesSplit它保证每次训练集都在验证集之前。另一个替代方案是滚动的单步验证——每次用截至 t 的数据训练预测 t1然后不断向右推进窗口。后者计算量大但更接近实际业务中每天重新预测明天的场景。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, val_idx in tscv.split(temps): train_fold temps.iloc[train_idx] val_fold temps.iloc[val_idx] # 在 train_fold 上拟合模型在 val_fold 上验证5.4 日频数据的季节周期设置过大模型训练卡死现象气温数据日频直接设SARIMAX的seasonal_order(1,1,1,365)代码跑了几十分钟没结果甚至内存溢出。原因季节周期 365 意味着季节差分和季节 AR/MA 项要在最近 365 个观测上计算状态空间模型的滤波过程涉及高维矩阵运算计算复杂度和 s 近似线性增长s365 的成本是 s12 的几十倍。解决先把数据降采样到周频或月频再建模。气温数据降采样到月频后季节周期变成 12模型的训练时间从几十分钟降到几秒预测精度下降通常在一个可接受范围内。如果业务必须日频预测考虑用 Prophet——它的季节项用傅里叶级数拟合计算成本对周期长度的敏感度远低于 SARIMA。# 降采样到月频再拟合 SARIMA temps_monthly temps.resample(MS).mean() model SARIMAX(temps_monthly, order(1, 1, 1), seasonal_order(1, 1, 1, 12))这个坑太常见了我几乎每次处理日频数据都要提醒自己一遍不要为了理论上的更完整而承担不必要的计算成本先降采样跑通流程再评估是否需要回到日频精调。6. 滚动验证与残差分析给模型的可靠性做一次体检模型训练完成、误差指标也算了但预测效果好和模型可靠是两回事。可靠性的验证靠两个手段滚动验证和残差分析。滚动验证模拟的是真实预测场景——每次只用截至当前时刻的数据做训练预测未来一个或多个时间点然后窗口向前移动重复这个过程。相比一次性的训练/测试划分,这样验证出的误差更能代表模型上线后的真实水平。import numpy as np from statsmodels.tsa.arima.model import ARIMA def rolling_forecast(series, train_len, horizon, order(1, 1, 1)): predictions [] history series.iloc[:train_len].copy() for i in range(len(series) - train_len - horizon 1): model ARIMA(history, orderorder) result model.fit() pred result.forecast(horizon) predictions.append(pred) # 窗口向前移动把真实观测加入历史 history pd.concat([history, series.iloc[[train_len i]]]) return np.array(predictions)滚动验证输出的是一组预测值把它们和真实值对比计算 RMSE 和 MAE。但这里有个关键点RMSE 低不代表模型没问题还要看残差是否是白噪声——残差的自相关越小越好。如果残差里还有明显的自相关模式说明模型遗漏了某种结构可能是季节项阶数不对也可能是需要加入外生变量。画残差 ACF 图是最直接的检验方式。# 画残差 ACF 图 residuals result.resid plot_acf(residuals, lags40) plt.show()如果残差 ACF 在滞后 1 或滞后 2 处有明显峰值通常是模型欠拟合检查一下是否少加了 MA 项如果多个滞后处都有显著相关考虑原始序列是否存在未捕捉到的周期。这几步全部走完模型的可靠性才算在能交代的水平。从那以后我做任何时间序列任务都会强制走一遍这个流程——先看数据形态再定模型类型然后网格搜索定阶最后滚动验证加残差分析缺一步心里都不踏实。这套流程在这个包里都配好了对应代码照着跑一遍比自己从零写要省掉大量排查成本。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑