
简介这份资源面向计算机、电子信息工程、数学等专业的大学生及时间序列预测初学者提供一套可直接运行的SARIMA建模完整方案用于课程设计、期末大作业或毕业设计中的预测类课题。压缩包共5个文件约61KB包含3个csv数据文件、1个xlsx表格和1个py源码文件数据与代码分离便于替换自有数据集进行实验。源码采用参数化编程季节周期、差分阶数等关键参数均可方便调整且配有保姆级注释几乎一行一注释帮助小白快速理解SARIMA的建模流程与编程思路。作者为某大厂资深算法工程师具备八年Matlab与Python算法仿真经验擅长智能优化算法、神经网络预测与信号处理等方向。目前已有448人学习下载适合希望从零掌握时间序列预测、并需要一份结构清晰、注释详尽的参考实现来推进课题或作业的读者。1. 从一份焦作空气质量数据说起SARIMA 到底能解决什么手里这份资源包叫「Python实现SARIMA时间序列预测完整源码和数据」解压后是SARIMA.zip里面躺着SARIMA.py、A.xlsx、A.csv、焦作.csv、焦作全.csv五个文件。别小看这几个文件它其实是一条完整的链路原始数据、清洗后的数据、建模脚本、可视化输出一个都不缺。SARIMA 全称 Seasonal Autoregressive Integrated Moving Average中文叫季节性差分自回归移动平均模型是经典统计时序里处理「带周期波动的单变量序列」最稳的一把刀。它不像 LSTM 那样需要堆数据、调网络也不像 Prophet 那样黑盒参数含义清晰适合做课程设计、期末大作业、毕业设计里那种「数据量不大、要求可解释、要画图交报告」的场景。焦作这份数据大概率是空气质量或气象类日频序列天然带年周期正好是 SARIMA 的主场。如果你正被「时间序列预测」四个字卡住不知道从哪下手这份带保姆级注释的源码能让你少走至少两周弯路。2. 拆包先看数据A.csv、焦作.csv 和焦作全.csv 到底怎么用2.1 三个数据文件的分工与字段预判拿到压缩包别急着跑SARIMA.py先把三个数据文件的关系理清楚否则后面报错你都不知道是数据问题还是代码问题。按常见工程习惯焦作全.csv是原始全量数据字段最全、行数最多可能包含时间戳、多个监测指标、站点编号等焦作.csv是从全量里筛出来的单变量序列通常只保留「日期 目标值」两列专门喂给 SARIMAA.csv和A.xlsx则是脚本运行后的输出或中间缓存比如预测结果、残差、评估指标。你可以用下面这段代码快速摸清每个文件的底细别凭文件名猜。import pandas as pd for f in [焦作全.csv, 焦作.csv, A.csv]: try: df pd.read_csv(f, encodingutf-8) except UnicodeDecodeError: df pd.read_csv(f, encodinggbk) # 国内数据常见 gbk print(f {f} ) print(形状:, df.shape) print(列名:, df.columns.tolist()) print(df.head(3)) print(df.dtypes) print()这段代码做了三件事自动尝试 utf-8 和 gbk 两种编码读取避免中文列名乱码打印形状和列名让你一眼看出哪个是宽表哪个是长表打印前 3 行和字段类型判断时间列是不是字符串、目标列有没有混入非数值。参数上唯一要改的是文件名列表如果你解压后路径不同把f换成相对路径即可。常见坑是焦作全.csv里时间列叫「时间」而不是「date」或者目标列里混了「—」「null」这类占位符读进来就是 object 类型后面resample直接翻车。2.2 把原始序列转成 SARIMA 能吃的格式SARIMA 对输入的要求很朴素一个按时间排序的 Series索引是 DatetimeIndex值是 float。很多新手卡在「我的数据是每天一条但日期不连续」或者「有重复日期」上。下面这段预处理我一般会固定写进脚本开头比直接pd.read_csv多花十行但能省掉后面两小时排查。import pandas as pd # 读取单变量序列假设焦作.csv 有 date 和 value 两列 df pd.read_csv(焦作.csv, encodinggbk) df[date] pd.to_datetime(df[date], errorscoerce) # 非法日期变 NaT df df.dropna(subset[date]) # 丢掉日期解析失败的 df df.sort_values(date).drop_duplicates(date) # 排序 去重 df df.set_index(date) series df[value].astype(float) # 按天重采样缺失值先线性插值再前后填充兜底 series series.resample(D).mean() series series.interpolate(methodlinear).ffill().bfill() print(序列长度:, len(series)) print(时间范围:, series.index.min(), -, series.index.max()) print(缺失值数量:, series.isna().sum())逻辑说明errorscoerce把无法解析的日期变成 NaT避免整列报错drop_duplicates(date)解决同一天多条记录的问题保留第一条resample(D).mean()把不规则时间戳统一到日频如果同一天有多条就取均值interpolate加ffill/bfill是双保险线性插值处理中间空洞前后填充处理首尾缺失。参数上D可以换成H小时、W周取决于你的业务粒度。注意如果缺失比例超过 20%插值就是自欺欺人得回去查数据采集环节这一步没有后悔药。2.3 平稳性检验与差分阶数 d、D 的确定SARIMA 的d和D不是拍脑袋定的得看 ADF 检验和季节差分后的结果。很多课程设计只写「取 d1」一问为什么就哑火。下面这套流程我每次都会跑一遍把图和数据一起贴进报告答辩时稳得很。import numpy as np from statsmodels.tsa.stattools import adfuller import matplotlib.pyplot as plt def adf_report(x, name): r adfuller(x.dropna(), autolagAIC) print(f{name}: ADF{r[0]:.4f}, p{r[1]:.4f}, 临界值{r[4]}) return r[1] # 原始序列 p0 adf_report(series, 原始序列) # 一阶普通差分 p1 adf_report(series.diff(1), 一阶差分) # 一阶季节差分假设周期 12月频日频年周期用 365 p2 adf_report(series.diff(1).diff(12), 一阶差分季节差分) series.plot(title原始序列) plt.show() series.diff(1).plot(title一阶差分) plt.show()判断标准p 值小于 0.05 认为平稳。如果原始序列 p 已经小于 0.05d0一阶差分后平稳d1季节差分后平稳D1。周期s要结合业务日频数据看年周期取 365月频取 12周频取 52。注意日频取 365 会让计算量暴涨常见做法是先聚合到周或月再建模或者用s7抓周内周期。这一步的图一定要留着报告里「数据探索」章节就靠它撑场面。3. SARIMA.py 逐段拆解参数化编程到底怎么改3.1 模型定阶p、q、P、Q 的网格搜索与 AIC 准则SARIMA.py号称参数化编程核心就体现在定阶部分。SARIMA 有七个参数(p,d,q)(P,D,Q,s)。d、D、s上一章定了剩下p、q、P、Q用网格搜索配 AIC 最小来选。AIC 越小模型在拟合优度和复杂度之间平衡得越好。下面这段是通用写法直接抄进你的脚本就能用。import warnings import itertools from statsmodels.tsa.statespace.sarimax import SARIMAX warnings.filterwarnings(ignore) # 网格搜索会刷屏先屏蔽 p q range(0, 3) P Q range(0, 2) d, D, s 1, 1, 12 # 按上一章结论改 results [] for pp, qq, PP, QQ in itertools.product(p, q, P, Q): try: model SARIMAX(series, order(pp, d, qq), seasonal_order(PP, D, QQ, s), enforce_stationarityFalse, enforce_invertibilityFalse) fit model.fit(dispFalse) results.append((pp, qq, PP, QQ, fit.aic)) except Exception as e: continue results.sort(keylambda x: x[4]) print(AIC 最小的前 5 组:) for r in results[:5]: print(fp{r[0]}, q{r[1]}, P{r[2]}, Q{r[3]}, AIC{r[4]:.2f})逻辑说明itertools.product生成所有参数组合SARIMAX是 statsmodels 里支持季节项的接口enforce_stationarityFalse和enforce_invertibilityFalse是为了让搜索过程不因参数越界中断定阶阶段先放宽最终模型再收紧。参数上range(0,3)表示 p、q 试 0 到 2range(0,2)表示 P、Q 试 0 到 1组合数 3×3×2×236 组跑几分钟能出结果。如果数据量大把范围再缩小或者用pmdarima的auto_arima自动搜。注意AIC 最小不代表残差一定白噪声定完阶还要看 Ljung-Box 检验这一步别省。3.2 训练集/测试集切分与滚动预测时序数据不能随机切分必须按时间先后切。常见做法是留最后 20% 做测试或者用滚动预测模拟真实场景。SARIMA.py里如果只做了静态预测建议你补上滚动预测报告里能多一个亮点。n len(series) train_size int(n * 0.8) train, test series[:train_size], series[train_size:] # 用最优参数拟合 best SARIMAX(train, order(1, 1, 1), seasonal_order(1, 1, 1, 12), enforce_stationarityTrue, enforce_invertibilityTrue).fit(dispFalse) # 静态预测一次性预测整个测试集 pred_static best.get_forecast(stepslen(test)) pred_mean pred_static.predicted_mean conf_int pred_static.conf_int() # 滚动预测每次只预测一步把真实值喂回去 history list(train) pred_roll [] for t in range(len(test)): m SARIMAX(history, order(1,1,1), seasonal_order(1,1,1,12)).fit(dispFalse) yhat m.forecast(steps1).iloc[0] pred_roll.append(yhat) history.append(test.iloc[t]) # 关键用真实值更新逻辑说明静态预测适合评估模型整体外推能力滚动预测更贴近线上「每天更新一次」的场景。get_forecast返回预测均值和置信区间画图时把conf_int一起画上报告显得专业。滚动预测里history.append(test.iloc[t])是灵魂如果误写成history.append(yhat)就变成纯自回归外推误差会累积到离谱。参数上stepslen(test)控制预测步数conf_int默认 95% 置信水平可用alpha0.05调整。3.3 评估指标MAE、RMSE、MAPE 怎么算怎么解读预测完不算指标等于白跑。三个常用指标各有脾气MAE 对异常值不敏感RMSE 放大大误差MAPE 是百分比但遇到零值会炸。下面这段一次算全。import numpy as np def metrics(y_true, y_pred): y_true, y_pred np.array(y_true), np.array(y_pred) mae np.mean(np.abs(y_true - y_pred)) rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) mask y_true ! 0 mape np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 return mae, rmse, mape mae, rmse, mape metrics(test.values, pred_mean.values) print(fMAE{mae:.4f}, RMSE{rmse:.4f}, MAPE{mape:.2f}%)逻辑说明mask y_true ! 0是为了避开 MAPE 除零这是血泪经验空气质量数据里偶尔真有 0。解读上MAPE 小于 10% 算优秀10% 到 20% 可接受超过 30% 就得回头查数据或换模型。注意测试集太短比如只有 5 个点时这些指标波动极大别拿单次结果下结论可以多切几段做交叉验证。4. 避坑指南SARIMA 实战里最容易翻车的五件事4.1 现象模型跑通但预测是一条直线原因d或D取得过大差分把序列的波动信息全抹掉了模型只能输出均值。解决回到第 2.3 节重新做 ADF 检验优先用最小差分阶数让序列平稳别习惯性d1到底。如果原始序列本身平稳d0完全合法。4.2 现象报错 LinAlgError: Singular matrix原因s设得太大而数据太短比如日频数据只有 200 条却设s365季节项矩阵秩亏。解决把数据聚合到周或月或者把s降到 7也可以给SARIMAX加enforce_stationarityFalse先跑通但最终模型要收紧。4.3 现象预测结果和真实值差一个数量级原因训练时用了差分预测时忘了inverse_transform或者手动还原。statsmodels 的get_forecast默认返回的是原始尺度但如果你自己做了diff再建模就得手动累加回去。解决统一用SARIMAX的order参数处理差分别在数据层手动diff这样预测输出自动是原始尺度。4.4 现象中文列名或路径导致读取失败原因Windows 下pd.read_csv默认 utf-8而国内 Excel 导出的 csv 常是 gbk路径里有中文也可能出问题。解决读取时显式写encodinggbk路径用pathlib.Path拼接或者把数据文件放到纯英文目录下。这个坑在课程设计里出现频率极高。4.5 现象AIC 很小但残差检验不通过原因网格搜索只优化了 AIC没管残差是否白噪声。残差有自相关说明模型没榨干信息。解决定阶后跑acorr_ljungbox(resid, lags[10])p 值大于 0.05 才算通过不通过就扩大搜索范围或者考虑加外生变量、换模型。别为了指标好看硬选 AIC 最小的那组。5. 从焦作数据到通用模板把 SARIMA.py 改成你自己的预测工具5.1 参数化改造三处改动适配任意单变量序列SARIMA.py最大的价值不是跑通焦作数据而是它的参数化结构能让你换成自己的数据。我一般会改三个地方第一把文件路径和列名抽成配置字典放脚本顶部第二把s、d、D和网格搜索范围做成命令行参数或配置文件第三把评估和画图封装成函数主流程只留五行。这样下次拿到新数据改配置就行不用动逻辑。CONFIG { file: 焦作.csv, encoding: gbk, time_col: date, value_col: value, freq: D, seasonal_period: 12, train_ratio: 0.8, p_range: range(0, 3), q_range: range(0, 3), P_range: range(0, 2), Q_range: range(0, 2), }逻辑说明把易变的东西集中管理是参数化编程的核心。seasonal_period换成 7 就是周周期换成 365 就是年周期注意数据量。train_ratio控制切分比例。这份配置字典可以直接被argparse或yaml读取做成命令行工具。5.2 验证方法三步确认你的模型不是「碰巧跑通」跑出结果别急着截图交差按这三步验一遍。第一步把训练集预测值和真实值画在同一张图上看拟合是否贴合如果训练集都拟合不好测试集免谈。第二步看残差图理想状态是围绕零均值随机波动没有明显趋势和周期。第三步做 Ljung-Box 检验acorr_ljungbox返回的 p 值全部大于 0.05 才算残差白噪声。这三步走完你的结论才站得住。from statsmodels.stats.diagnostic import acorr_ljungbox resid best.resid lb acorr_ljungbox(resid, lags[10, 20], return_dfTrue) print(lb) # p 值 0.05 表示不能拒绝「残差为白噪声」的原假设5.3 一个具体技巧用季节差分后的 ACF/PACF 图辅助定阶网格搜索费时如果只想快速定个大概看 ACF 和 PACF 图能省不少事。对季节差分后的序列ACF 在滞后s处有显著尖峰说明需要季节 MA 项QPACF 在滞后s处有尖峰说明需要季节 AR 项P。非季节部分同理ACF 拖尾 PACF 截尾取 AR反之取 MA。这方法不精确但能帮你把网格搜索范围从 0 到 5 缩到 0 到 2省一半时间。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf diff_series series.diff(1).diff(12).dropna() plot_acf(diff_series, lags40) plot_pacf(diff_series, lags40) plt.show()注意lags至少设到2*s否则季节尖峰看不到。图里置信区间默认 95%超出边界的滞后才算显著。这招我每次定阶前都会跑一遍比盲搜快得多。从那以后我每次拿到新的时序数据都强制先跑一遍 ADF 和 ACF/PACF再动网格搜索不然就是拿算力换心安。这份SARIMA.py加焦作数据的组合胜在注释细、结构清、数据真适合拿来当模板改。希望帮到你。本文还有配套的精品资源点击获取