
简介围绕时间序列预测中的趋势与季节性分析这份实战资源包面向具备一定基础的数据分析与气候预测学习者通过真实案例掌握基于自回归移动平均族模型的预测建模方法全程使用Python实现。资源共九个文件包含三个Jupyter笔记本、一个气温数据集、四个工程配置以及一个模块描述文件总体积约二点九五兆轻量易用。笔记本分别基于合成数据集和真实每日最低气温数据集进行建模完整覆盖趋势与季节性分解、模型选择与训练、误差评估及结果可视化等环节并示范了自回归与季节自回归模型的使用差异气温数据可直接替换输入配套工程配置便于在编辑器中还原开发环境。已有五百二十八人学习使用适合希望将时间序列理论落到代码实践、并能进一步拓展到温度与降雨量等气候指标预测场景的读者。1. 时间序列预测难点从来不在模型而在数据拆解做时间序列预测的同行应该都有同感模型本身翻不出太多花真正决定预测上限的是你能不能把趋势、季节性、循环和噪声四类成分从原始序列里干净地拆出来。这份「基于趋势和季节性的时间序列预测实战.rar」压缩包走的正是这条路线——用一套合成数据集和一份真实的最低温度日数据从 Pandas 的滑动窗口开始一路走到 STL 分解、SARIMA 建模和网格寻参全程用 Python 和 statsmodels 落地。适合刚入门气候预测、想把时间序列四成分搞清楚的新手也适合已经用 ARIMA 做过几个项目、但始终没把季节性拆明白的熟手。压缩包里除了两个 .ipynb 主笔记还带了 daily-minimum-temperatures.csv 这份经典温度数据和 .idea 工程配置打开就是能跑的完整工程。2. 先看懂数据再谈建模两个数据集的差异与可视化2.1 压缩包里的核心文件分别负责什么解压后先别急着跑代码把文件结构过一遍。.ipynb两个笔记是主角modeling_synthetic_dataset.ipynb用构造出来的合成序列讲原理modeling_temperature_dataset.ipynb用真实温度数据走完整流程。.idea目录、workspace.xml、misc.xml这类是 PyCharm 的工程配置和算法无关直接忽略.ipynb_checkpoints是 Jupyter 的自动备份目录也跳过。我一般拿到这类包的习惯是先打开daily-minimum-temperatures.csv确认数据规模再分别扫一眼两个笔记的 cell 结构。合成数据集的笔记重点看「构造逻辑」——它是用np.random叠加趋势项、季节项和噪声项生成的理解这段构造代码就等于拿到了拆解真实数据的钥匙。温度数据集的笔记则更接近实战里面出现了pd.to_datetime、按年/月重采样、滚动均值这些操作这些才是气候预测里天天要用的基本功。2.2 用 Pandas 加载温度数据先查索引再可视化温度数据是典型的日粒度单变量序列第一列是日期第二列是最低温度。加载代码不长但有个习惯建议从一开始就养成日期列转成DatetimeIndex后用sort_index()排序再做任何操作。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(daily-minimum-temperatures.csv, header0, names[date, temp]) df[date] pd.to_datetime(df[date]) df df.set_index(date).sort_index() print(df.shape) print(df.index.min(), df.index.max()) df[temp].plot(figsize(12, 4), titleDaily Min Temp) plt.show()header0, names[...]是显式指定列名避免原始 CSV 第一行被误读为表头set_index之后所有按时间的切片、重采样、滞后操作才能正常工作。print出首尾日期目的是确认数据覆盖范围——这份数据从 1981 年到 1990 年十年日粒度足够观察年季节性。直接 plot 全量数据密集的锯齿形曲线会淹没细节但这张图本身就有价值你能直观看到每年的波峰波谷基本对齐这就是季节性的原始证据。2.3 趋势和季节性分开看滚动均值与重采样的配合全量曲线太密肉眼很难同时判断趋势和季节性。常见做法是用 Pandas 的滚动窗口算出趋势线再用按月重采样把季节性压平。trend df[temp].rolling(window365, centerTrue).mean() df[temp].plot(figsize(12, 4), alpha0.4, legendTrue) trend.plot(figsize(12, 4), linewidth2, legendTrue) plt.title(Raw vs 365-day Rolling Mean) plt.show() monthly df[temp].resample(M).mean() monthly.plot(figsize(12, 3), markero, markersize3) plt.title(Monthly Mean Temperature) plt.show()rolling(365).mean()是把一年当作窗口求局部平均窗口内的高频波动被抹平剩下的曲线就是趋势成分的直观体现——注意这份温度数据的趋势并不明显十年里基本在水平线附近震荡靠肉眼只能看出季节波动。resample(M).mean()把日数据压成月均值每个月一个点季节性就成了一年内的正弦型起伏。走到这一步你已经完成了「人工版 STL 分解」滚动均值充当趋势提取器月度重采样充当季节性观察器。这也解释了为什么压缩包里的笔记要把这两段放在建模之前——先确认数据里存在可建模的季节模式再去谈 SARIMA 参数。3. 趋势与季节性分解STL 才是真正干活的工具3.1 为什么不用简单加法分解而要用 STL经典加法分解把序列拆成趋势 季节性 残差statsmodels 里的seasonal_decompose一行就能跑。但它的季节性假设是「每个周期的季节模式固定不变」——对温度数据来说这意味着每年冬天的形状都一模一样这显然和真实气候不符。STLSeasonal and Trend decomposition using Loess则允许季节性随时间缓慢变化周期形状可以逐年微调。这份压缩包的温度笔记选用 STL背后是经过考量的真实气象数据受厄尔尼诺、城市热岛等因素干扰季节模式并非严格重复STL 的稳健拟合能把这些异常值对趋势的拉扯降到最低。用 STL 做分解时周期参数必须写对。日数据的周期是365月度数据是12周数据是7。写错周期分解结果会很难看周期设小了季节性分量里会混入高频噪声周期设大了季节性会把趋势的拐点吃掉一半。from statsmodels.tsa.seasonal import STL res STL(df[temp], period365, robustTrue).fit() fig res.plot() fig.set_size_inches(12, 10) plt.show()robustTrue是 STL 的内置稳健模式迭代拟合时给极端残差降权温度记录里偶尔出现的异常低温日不会把趋势线拽偏。fit()返回的对象包含trend、seasonal、resid三个分量plot()直接画出四宫格从上到下是原始序列、趋势、季节性、残差。看这组图要学会读趋势分量如果接近直线说明长期没有明显升温或降温季节性分量是逐年重复但幅度略有变化的波形残差分量如果还残留明显的峰谷说明季节周期没设对或者数据里还有未建模的周内效应。3.2 从分解结果里读信号残差才是后续建模的告密者分解不是终点读完图之后要落到三个判断上。第一趋势是平稳的还是带斜率的——这决定 SARIMA 里差分的阶数d第二季节性波形的振幅是恒定还是逐年放大——这决定要不要对季节分量再做一次差分第三残差里有没有明显的自相关——如果残差在滞后 7 天或 30 天处仍有尖峰说明日粒度数据里还存在周效应或月度效应模型季节项没抓全。温度数据分解后的残差通常还会有点「记忆性」今天的异常低温明天大概率还是偏低。这是因为气温是一个惯性系统冷空气团不会一天就消散。这种情况下直接对原始序列建模也行但把残差的自相关结构交给 ARIMA 去拟合SARIMA 就会变得很臃肿。更稳的路线是先 STL 分解对趋势项做线性或差分建模对季节性项用傅里叶项或虚拟变量拟合最后对残差单独建一个低阶 AR 模型。这种「拆开分别建模」的思路正是从seasonal_decompose到STL再到 SARIMA 的进阶路径也是这份压缩包最想演示的思维方式。合成数据集笔记里大量随机序列测试目的就是训练你读残差图的能力。4. SARIMA 建模与参数寻优从 ACF/PACF 到网格搜索4.1 差分阶数 d 的判定以及 ACF/PACF 怎么读SARIMA 是 ARIMA 的季节扩展完整参数是(p, d, q) × (P, D, Q, m)。d是普通差分阶数D是季节差分阶数m是季节周期长度。拿到一组数据先判断dSTL 分解出的趋势分量如果是水平的d0有明显斜率d1温度数据做一阶差分后还要看方差是否稳定如果波动幅度跟着季节走那大概率还需要D1。判定p和q靠自相关图ACF和偏自相关图PACF。差分后的序列要已经平稳ACF 拖尾、PACF 在滞后 k 阶截断pk反过来 ACF 截断、PACF 拖尾qk。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf diff_series df[temp].diff().dropna() fig, axes plt.subplots(2, 1, figsize(12, 6)) plot_acf(diff_series, axaxes[0], lags120) plot_pacf(diff_series, axaxes[1], lags120) plt.show()lags120是为了看到 365 天季节周期附近的峰值——只画 30 个滞后周期信息会被截断。温度数据一阶差分后ACF 在滞后 7、14、21 附近会有小尖峰这是周内气象节奏的痕迹在滞后 365 附近出现显著正峰说明季节差分D1是必要的。PACF 在低阶截断得比较干净通常p取 1 或 2 就够。记住一个原则ACF/PACF 只是初筛给出的参数是搜索范围的下限不要把它当精确答案。4.2 拟合 SARIMA 模型参数怎么传statsmodels 的SARIMAX是当前主力接口支持外生变量和状态空间表示。温度数据集上用(2, 1, 2) × (1, 1, 1, 12)是月度重采样后的常用起点如果保持日粒度季节周期就得写 365状态空间矩阵会非常大拟合极慢。这份压缩包的温度笔记实际建议先按月聚合再上 SARIMA否则训练时间会是灾难。from statsmodels.tsa.statespace.sarimax import SARIMAX monthly df[temp].resample(M).mean() model SARIMAX( monthly, order(2, 1, 2), seasonal_order(1, 1, 1, 12), enforce_stationarityFalse, enforce_invertibilityFalse, ) fit model.fit(dispFalse) print(fit.summary())enforce_stationarityFalse和enforce_invertibilityFalse这两个参数容易被忽略实际上很重要。模型拟合时 statsmodels 默认会把 AR 和 MA 多项式强制限制在可逆域内一旦数据本身接近非平稳这个限制会导致优化器在边界上反复卡住拟合时间暴涨。关掉约束让优化器自由探索拟合稳定后最终结果一般会自然落回可逆区域。dispFalse关掉迭代日志否则终端会被刷屏。运行后先看fit.summary()里的P|z|列——某个系数显著大于 0.05说明这个参数可删对应缩小模型维度。4.3 用滚动预测替代一次性切分再用网格搜索收敛参数时间序列切分和普通机器学习不一样不能用随机打乱必须按时间顺序切。这份压缩包里提到的split_time_series_sets本质上就是让前面的数据训练、后面的数据验证但我更推荐「滚动预测」每次推进一个时间步模型用截至当前的全部数据重拟合并预测下一步。这样更贴近真实使用场景异常点不会被一次性切分躲过去。import numpy as np from statsmodels.tsa.statespace.sarimax import SARIMAX history monthly.iloc[:100] y_true monthly.iloc[100:120] predictions [] for i in range(len(y_true)): model SARIMAX(history, order(2, 1, 2), seasonal_order(1, 1, 1, 12)) fit model.fit(dispFalse) pred fit.forecast(steps1).iloc[0] predictions.append(pred) history pd.concat([history, pd.Series([y_true.iloc[i]], index[y_true.index[i]])]) print(pd.DataFrame({pred: predictions, true: y_true.values}).head())滚动预测的每一步都在重新拟合模型开销大但结果可信。把predictions与真实值对齐算 RMSE 和 MAE。网格搜索则是在参数候选集上循环候选集来自 ACF/PACF 的初判范围——比如p取 1 到 3q取 1 到 2P取 0 到 1D取 0 到 1组合数控制在 30 组以内每组用滚动预测评估一次。别一上来就全排列几十万组时间序列模型的拟合成本远高于普通机器学习搜索范围太大容易把机器拖死。5. 避坑手册时间序列预测最容易翻车的五个位置5.1 日期索引没排序滞后计算全部错乱现象df.shift(1)算出的滞后值和昨天对不上ACF 图上出现诡异的快速衰减。原因CSV 读取后日期顺序并不保证严格递增set_index后如果没有sort_index()Pandas 会按原始行序计算滞后。差一天的数据被当成滞后一期自相关结构直接失真。解决统一在加载后立即执行df df.set_index(date).sort_index()然后肉眼检查一次df.index.is_monotonic_increasing。这一步应该在所有数据预处理之前完成做成一个固定的函数也行。从那以后我每次拿到任何时间序列数据第一件事永远是排序索引哪怕 CSV 看起来是排好的也不跳过。5.2 季节周期 m 写错模型速度和效果双输现象日粒度温度数据直接把seasonal_order写成(1, 1, 1, 365)模型拟合耗时从几秒变成几十分钟甚至内存溢出。原因SARIMA 的季节周期越大状态空间表示中的状态向量维度越高。365 这个数字对应的是日粒度的年周期如果你的数据已经重采样成月m 必须改成 12。最典型的低级错误是月度数据用了 365或者日数据用了 12——前者慢到怀疑人生后者模型根本抓不住年周期。解决先明确数据的采样频率再换算季节周期。日数据建议先用月度重采样把问题缩小SARIMA 里写m12必须使用日粒度时也不要直接上 SARIMA改用带傅里叶项的SARIMAX或者干脆换 STL 分解后对残差建模。5.3 ACF/PACF 在非平稳序列上直接读参数取错现象PACF 图第一根柱子几乎等于 1后面全部贴着 0p看着像 0模型拟合后残差仍然显著自相关。原因序列还没做差分趋势成分残留在自相关函数里ACF 会呈现典型的「下降极慢」模式PACF 则被趋势主导。这时候读出的p和q全是假信号。解决先对原始序列做diff()差分一次后重画 ACF/PACF再读参数。期间配合adfuller做 ADF 检验p-value大于 0.05 就继续差分直到平稳为止。注意差分次数别贪多d超过 2 的模型基本无法解释也容易过拟合。5.4 一次性切分数据集忽视了时间顺序的泄露现象随手用train_test_split(test_size0.2)切分时间序列测试集的 RMSE 比验证集低一截上线后预测误差大很多。原因普通切分是随机打乱的时间序列一旦打乱模型在预测「未来」时其实已经偷看了「未来」的数据分布。短期来看拟合指标虚低长期来看模型对突变毫无准备。解决强制使用按时间顺序切分或滚动预测。最简单的是按位置切分train df.iloc[:int(len(df)*0.8)]test df.iloc[int(len(df)*0.8):]严格场景用滚动预测每个预测点只使用该点之前的数据这份压缩包里的split_time_series_sets就是干这个的。5.5 不看残差白噪声检验置信区间只是摆设现象模型的点预测看起来还行但预测区间窄得离谱95% 置信区间几乎贴着预测曲线。原因模型把残差里的自相关信息也当成噪声忽略掉了也就是说模型没有完全提取数据里的信息。违反模型假设的情况下forecast()给出的区间估计没有意义。解决拟合后对残差做acorr_ljungbox检验p-value 小于 0.05 说明残差仍有时间结构模型欠拟合回去调参数或换模型。这是压缩包里最容易漏掉的一步跳过的话后续所有预测区间都不能信。6. 一个值钱的进阶技巧用残差反推模型升级方向拟合完 SARIMA常规操作是看残差图觉得平了就算完事。但残差里往往还藏着一层信息如果 Ljung-Box 检验明确告诉你自相关还存在那就别直接调p、q了先画残差的频谱图。把残差做一次快速傅里叶变换看频谱在哪里出尖峰。如果尖峰集中在 365 天、182 天这样的年周期倍数位置说明季节项没拆干净最直接的应对是给SARIMAX加上傅里叶项而不是硬把P从 1 调到 3。傅里叶项能拟合任意形状的周期比 SARIMA 固定周期的季节差分更灵活。from statsmodels.tsa.statespace.sarimax import SARIMAX import numpy as np # 把年周期拆成傅里叶项作为外生变量传入 t np.arange(len(monthly)) seasonal_fourier np.column_stack([ np.sin(2 * np.pi * k * t / 12) for k in range(1, 3) ] [ np.cos(2 * np.pi * k * t / 12) for k in range(1, 3) ]) model SARIMAX(monthly, exogseasonal_fourier, order(2, 1, 2)) fit model.fit(dispFalse) print(fit.summary())这段代码把月周期拆成 4 列傅里叶特征由模型自己学习季节形状。好处是省掉季节差分项D之后模型状态更小训练速度更快对不规则季节模式的适应也比固定周期的 SARIMA 好。加完项之后重跑 Ljung-Box你会发现残差自相关被明显吃掉一截——如果还残留再补更高阶的傅里叶项。这是一个几乎所有时间序列项目都能复用的通用技巧用残差去指示模型欠拟合的方向而不是盲调参数。合成数据集笔记里构造的那些带相位漂移的季节信号就是为了验证这套「STL 拆解 → SARIMA 抓残差 → 傅里叶收尾」的组合拳。从那以后我每次建模都强制自己走一遍残差白噪声检验加频谱分析这个习惯帮我避开过至少三次「模型指标漂亮、上线即翻车」的尴尬。希望帮到你。本文还有配套的精品资源点击获取