Python时间序列分析实战:降雨量预测建模与验证
简介面向气象类专业学生、数据科学初学者及需要完成课程设计或毕业设计的人群压缩包提供了一套基于时间序列分析的降雨量预测Python项目源码。项目以历史气象数据为基础融合ARIMA统计建模与LSTM深度学习两条技术路线覆盖数据清洗、标准化、缺失值处理、模型训练与评估等完整环节同时可结合温度、湿度、气压等多变量提升预测精度支持小时、日、月多时间尺度输出并内置模型对比与误差分析思路便于理解不同算法的适用场景。整体约11.12MB内含可运行的Python工程代码与数据处理、模型构建、评估等核心模块目录结构清晰可直接运行和二次扩展。目前已有161人学习浏览适合作为课程设计参考、毕业设计选题实践或气象预报、农业水资源管理场景的入门解决方案。1. “cs.zip”里的降雨量预测到底在解决什么问题从网上下载到的“python项目基于时间序列分析的降雨量预测cs.zip”本质上是一套以 Python 脚本为主的小型时间序列分析工程历史降雨量数据进去模型把未来一段时间的降雨趋势和数值推出来。它解决的不是气象台那种精细数值预报而是让你用统计方法从序列自身规律里找答案——适合正在学时间序列分析、想拿真实数据练手的 Python 初学者也适合要交课设、或者给农业排灌和水文估算做初判的从业者。这类压缩包多数能跑通但结果合不合理往往取决于你愿不愿意把数据口径、模型参数和验证方式这三件事补齐。2. 时间序列分析的建模主线为什么先谈平稳性再谈差分和定阶2.1 降雨量预测为什么默认从 SARIMA 起步如果你拿到降雨量数据后第一反应是堆特征、上线性回归大概率会在验证集上被教育。因为降雨量序列不是独立样本今天的值跟昨天高度相关还带明显的年周期。普通回归模型假设样本独立这个假设在气象数据上基本不成立。SARIMA季节性差分自回归滑动平均模型的核心思路是把“过去的自己”当特征再用季节差分把周期项显式写进公式里。它比 LSTM 更适合这种几百条月度数据的小样本场景训练快、参数有数学含义、每一步预测都可以解释。LSTM 当然能拟合但数据量小的时候训练很不稳定调参更像玄学。所以拿到这个 cs.zip先别急着上深度学习默认从 SARIMA 起步是性价比最高的路径。2.2 平稳性检验不是走过场ADF 与 KPSS 的配合时间序列分析里第一个必须先过的关卡是平稳性。一个均值在漂移、方差在变化的序列直接建模就是在拟合噪声。我一般会同时看 ADF 和 KPSS 两个检验单看一个容易被异常点带偏。import pandas as pd from statsmodels.tsa.stattools import adfuller, kpss df pd.read_csv(rainfall.csv, parse_dates[date], index_coldate) series df[rainfall].dropna() # ADF 检验原假设是存在单位根非平稳 adf_stat, adf_p, *_ adfuller(series, autolagAIC) print(fADF p-value: {adf_p:.4f}) # KPSS 检验原假设是序列平稳 kpss_stat, kpss_p, *_ kpss(series, regressionct) print(fKPSS p-value: {kpss_p:.4f}) if adf_p 0.05 and kpss_p 0.05: print(结论序列平稳可以进入定阶环节) else: print(结论序列非平稳先做差分再回来检验)代码里autolagAIC表示自动选择最优滞后阶数月度数据下我会把maxlag显式设为 12因为降雨量的年度相关最多滞后 12 个月。KPSS 的regressionct表示允许趋势项存在避免把带趋势的序列误判成非平稳。两个检验结论要配合看ADF 说平稳、KPSS 说非平稳说明序列可能带确定性趋势用差分比用去趋势更稳反过来 ADF 说非平稳、KPSS 说平稳多半是样本太短或存在强季节性这时优先做季节差分而不是一阶差分。差分不是越多次越好绝大多数降雨量数据差分一次就够差到两次以上就要警惕过度差分。2.3 ACF/PACF 定阶速查AR、MA 还是 ARMA平稳性过了下一步是给 ARIMA(p,d,q) 里的 p 和 q 找候选值。这里不是靠肉眼猜而是看自相关图ACF和偏自相关图PACF的截尾、拖尾形态。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import matplotlib.pyplot as plt fig, axes plt.subplots(2, 1, figsize(10, 6)) plot_acf(series, lags24, axaxes[0]) plot_pacf(series, lags24, axaxes[1]) plt.tight_layout() plt.show()判断规则可以记成一张速查表模型ACF 形态PACF 形态AR(p)拖尾缓慢衰减p 阶后截尾突然掉进置信区间MA(q)q 阶后截尾拖尾ARMA(p,q)拖尾拖尾实际操作中2 阶以内的截尾才算可靠信号超过 2 阶还在置信区间外跳说明序列没洗干净回去检查差分和季节项。看到 PACF 在滞后 1 处截尾我就把 p1 放进候选ACF 在滞后 2 处截尾就把 q2 放进候选。定阶是个“先看图缩小范围、再用 AIC 精确比较”的过程不是让脚本随便给一个数就开跑。3. 降雨量数据清洗缺失值、异常值和聚合口径决定预测上限3.1 缺失值0 和 NaN 要分开处理气象站数据最常见的坑是把缺测记成 0或者把 0 和 NaN 混在一起。如果你直接df.fillna(0)模型会把“没下雨”和“没数据”当成同一件事雨季的低估会非常明显。我的处理原则是0 是有语义的真实观测NaN 是未知必须先标记出来再决定回填策略。import pandas as pd import numpy as np def clean_rainfall(df: pd.DataFrame, target: str rainfall) - pd.DataFrame: df df.sort_values(date).reset_index(dropTrue) df[date] pd.to_datetime(df[date]) # 只把非有限值当作缺失0 保留原样 df.loc[~np.isfinite(df[target]), target] np.nan # 连续缺失超过 3 天用当月均值回填短缺失用前后值填充 df[missing_run] ( df[target].isna().astype(int) .groupby(df[target].notna().cumsum()) .cumsum() ) month_mean df.groupby(df[date].dt.month)[target].transform(mean) long_missing (df[target].isna()) (df[missing_run] 3) df.loc[long_missing, target] month_mean[long_missing] df.loc[df[target].isna(), target] df[target].ffill().bfill() return df.drop(columns[missing_run])这里length of missing_run的统计是关键连续缺失 3 天以内用线性前向填充可以接受超过 3 天就属于结构性缺测用当月历史均值回填更稳。transform(mean)是按月份分组计算均值不会把 1 月的均值填到 7 月去。参数target可以直接接“rainfall”列名如果你拿到的压缩包里字段叫precip改一下参数就行。3.2 异常值负降雨量与被气候学上限卡掉的极端值降雨量为负是设备故障这个没有争议。但异常大值要小心处理一场台风带来的小时雨量可能远超正常范围直接删掉会把极端天气事件从样本里抹掉预测雨季峰值时会系统性偏低。我一般分两步走。def cap_rainfall_outliers( df: pd.DataFrame, target: str rainfall, multiplier: float 5.0 ) - pd.DataFrame: df df.copy() # 负值直接视为缺测后续走缺失回填 df.loc[df[target] 0, target] np.nan # 上限取 99 分位数和 IQR 上限两者中更小的 q99 df[target].quantile(0.99) iqr_upper df[target].quantile(0.75) multiplier * ( df[target].quantile(0.75) - df[target].quantile(0.25) ) upper min(q99, iqr_upper) # 超过上限的样本标记为 NaN交给回填逻辑而不是直接删除 df.loc[df[target] upper, target] np.nan return df注意我用了quantile(0.99)和multiplier5双保险99 分位数能挡住离群点IQR 方法能适应不同量级的数据。multiplier调成 3 会更激进适合日尺度数据月度累计降雨量我一般不动这个参数。处理完异常值记得再做一次缺失回填让管道顺序变成“清洗缺失 - 修正异常 - 再回填”否则会出现新的空洞。3.3 聚合口径日尺度还是月尺度直接影响模型选择很多 cs.zip 里给的是日降雨量但日尺度序列的问题是 0 值太多、偏态严重SARIMA 拟合出来残差往往不服从正态假设。我拿到数据后的第一步不是建模而是问业务上要预测什么粒度的结果。要做月度趋势预判就把日数据聚合成月要预警具体哪天可能有强降雨才保留日尺度。# 月度累计降雨量用 sum 而不是 mean monthly df.set_index(date)[rainfall].resample(M).sum() # 周尺度同样用 sum适合做短期趋势观察 weekly df.set_index(date)[rainfall].resample(W).sum()resample(M)是按自然月聚合resample(W)默认按周日到周六聚合如果你的数据从周三开始周边界会跟业务日历错位这时改用resample(W-WED)指定周起始日。聚合口径一旦定下来模型的m参数也就确定了月份数据m12周数据m52。这一步别偷懒你的预测上限在数据清洗时就定死了。4. 在 Python 里跑通降雨量预测最小可行代码与参数调优4.1 环境准备Python 安装、虚拟环境与依赖先解决环境问题。Python 安装时记得勾选 “Add Python to PATH”否则后面pip命令会报“不是内部或外部命令”。装完之后我不会把包直接装进全局环境而是给这个项目单独建一个虚拟环境避免和别的项目抢 numpy、pandas 版本。python -m venv .venv source .venv/bin/activate # Linux / macOS # .venv\Scripts\activate # Windows pip install --upgrade pip pip install pandas numpy statsmodels matplotlib scikit-learn pip install pmdarimapmdarima是 statsmodels 的封装提供auto_arima自动搜索参数写课设和快速验证时非常方便。注意如果报 numpy 版本冲突常见解法是固定numpy1.26.4再装 statsmodels新版本 numpy 对旧版 statsmodels 的 C 扩展不兼容。安装库的方法就是pip install 包名不用去官网手动下 whl除非你处在完全离线的环境。4.2 数据切分与训练auto_arima 搜索边界建模最忌讳直接拿全量数据训练再“看拟合效果”。时间序列必须按时间顺序切分我习惯用最后 12 个月做验证集剩下的做训练集。import pandas as pd from pmdarima import auto_arima from sklearn.metrics import mean_squared_error df pd.read_csv(rainfall.csv, parse_dates[date], index_coldate) series df[rainfall].resample(M).sum().dropna() train series.iloc[:-12] # 最后 12 个月留作验证 test series.iloc[-12:] model auto_arima( train, seasonalTrue, # 开启季节分量 m12, # 月度周期 start_p0, max_p3, start_q0, max_q3, dNone, # 让算法自动做单位根检验 D1, # 季节差分固定 1 次 stepwiseTrue, # 用逐步搜索速度更快 traceTrue, # 打印搜索过程 error_actionignore, suppress_warningsTrue, n_fits10, ) model.fit(train) pred model.predict(n_periodslen(test)) rmse mean_squared_error(test, pred, squaredFalse) print(f验证集 RMSE: {rmse:.2f} mm/月)dNone表示让算法用 ADF 检验自动判断差分数但我会给它边界如果业务上知道序列年际波动很大直接让d1更省时间。D1是季节差分次数月度降雨量通常 1 次就够设成 2 会明显过差分。stepwiseTrue用启发式搜索而非全组合几百条数据的项目跑起来只要十几秒traceTrue能看到它到底试了哪些参数组合这对后续调参很有参考价值。4.3 必须手动确认的几个参数m、D、d、max_orderauto_arima不是全自动的魔法它只是帮你缩小搜索范围最后几个关键参数务必自己过一遍。参数含义我一般怎么设m季节周期长度月度数据 12周数据 52日数据谨慎设 7D季节差分次数先设 1残差还有季节相关再加到 2d非季节差分次数0 或 1超过 2 基本是过度差分max_p/max_q非季节 AR / MA 最大阶数3 以内足够超过 3 更可能过拟合max_order总阶数上限不设的话 stepwise 可能搜出 pq 很大的怪模型我看候选模型不只盯着 AIC还会手动回看 ACF/PACF 图验证阶数是否合理。auto_arima搜出一个ARIMA(1,1,1)(1,1,1)[12]我会去图上确认滞后 1 和滞后 12 附近确实有显著相关如果图上是干净的就说明它选出来的结构是数据里真实存在的规律而不是搜索过程的偶然结果。这套“脚本搜索 人工验证”的组合比纯手动定阶快比纯自动可信。5. 降雨量预测里的 4 个高频踩坑与排查记录5.1 预测曲线退化成一条水平线现象模型训练完成预测区间看起来很正常但曲线几乎是一条水平线所有预测值都等于最后一个训练样本的数值置信带收得很窄。原因这是过度差分的典型症状。你把d设成了 2 甚至 3序列被差分成近乎白噪声模型学不到任何可预测的结构向前多步预测时就退化成“最后一点的延续”。另外数据有明显年周期却没用seasonalTrue也会出现同样的水平线。解决办法先看 ADF 检验结果如果原序列已经平稳直接让d0如果差分一次后平稳d1足够了。把seasonalTrue, m12打开让季节项参与预测。修正后重新看残差图如果残差还有明显尖峰说明季节信息没提完再调整D。水平线不是一个“可接受的结果”它是模型在告诉你我没从数据里学到任何周期和趋势。5.2 预测值出现负数现象测试集的预测结果里出现 -3.2 mm 这种数字画出来曲线掉到 0 轴以下业务上一眼就知道不合理。原因SARIMA 的误差项假设服从正态分布但降雨量是典型的右偏分布大量样本集中在 0 附近少数极端大雨把均值拉高。模型的正态假设在小雨月份会把条件期望推成负值。解决办法训练前对序列做 Box-Cox 变换让数据分布更接近正态拟合完再逆变换回来。降雨量恰好满足 Box-Cox 要求的非负条件。from scipy import stats # 训练集做 Box-Cox 变换 transformed, lam stats.boxcox(train) # 对变换后的序列建模预测结果逆变换回原始尺度 pred_inv stats.inv_boxcox(pred, lam)lam由极大似然估计自动求出不需要手调。注意stats.boxcox要求输入严格大于 0如果序列里有 0 值先给所有样本加一个小常数0.1变换完再减回去。用np.clip(pred, 0, None)把负值截断是治标不治本因为点预测本身偏移了截断只会掩盖问题。5.3 测试集 RMSE 比训练集离谱地高现象训练集上拟合误差很小RMSE 看着不错但验证集上误差翻倍甚至更高而且预测曲线明显滞后于实测曲线。原因最常见的操作失误是用了train_test_split(random_state42)随机切分。时间序列的相邻样本高度相关随机切分时测试集里混入了训练集邻近时段的数据相当于提前把答案泄露给了模型验证集误差自然虚低。解决办法永远按时间顺序切分测试集必须是训练集之后的那段数据。cutoff len(series) - 12 train series.iloc[:cutoff] test series.iloc[cutoff:]另一个容易被忽视的现象是预测越往远期越不准。你用第 1 个月的数据去预测第 12 个月和用前 11 个月预测第 12 个月误差完全不是一回事。评估时固定预测步长比如统一只做一步预测不同模型之间才有可比性。5.4 季节性峰值完全没预测出来现象每年雨季的峰值在预测曲线上明显被低估甚至整个峰值期都是平的但非汛期的预测凑合能看。原因m参数不对。月度数据设m12是对的但你拿到的可能是日数据直接建模或者D0导致完全没有季节差分模型的季节分量形同虚设。另一个可能数据里混了多个气象站不同站的雨季月份不一致周期被平均掉了。解决办法建模前先用seasonal_decompose看一眼周期到底有多强。from statsmodels.tsa.seasonal import seasonal_decompose result seasonal_decompose(series, modeladditive, period12) result.plot()如果分解图里季节性分量明显再看自相关图滞后 12 处是否显著。确认有周期后把seasonalTrue, m12, D1这三个参数同时设上。多站点数据就按站点拆开分别建模千万别把不同雨季规律的数据揉成一个序列。6. 验证模型不是看曲线对齐滚动预测、残差检查与出图技巧6.1 滚动预测用 walk-forward validation 逼近真实部署一次性预测 12 个月看着很爽但实际使用场景更多是“每个月更新一次数据预测下个月”。这种滚动更新的方式叫 walk-forward validation比固定切分更能反映模型的真实水平。history list(train) real list(test) preds [] for t in range(len(real)): model auto_arima( history, seasonalTrue, m12, d1, D1, max_p2, max_q2, error_actionignore, suppress_warningsTrue, ) yhat model.predict(n_periods1)[0] preds.append(yhat) history.append(real[t]) # 把真实观测值喂回历史模拟在线更新 rmse mean_squared_error(real, preds, squaredFalse) print(f滚动预测 RMSE: {rmse:.2f} mm/月)这段代码每一步只预测一个月然后立刻把真实值加入历史。n_periods1是核心一步预测的误差是所有预测里最小的用它做基准如果你想压测模型在 3 个月、6 个月前的预判能力把n_periods改成 3 或 6但预测起点也要相应后移。滚动预测慢是慢一点但对几百条的月度数据来说完全可接受。6.2 残差与诊断图模型可靠性的最后一道关口RMSE 低不代表模型没问题。我会看plot_diagnostics()的四张图残差时序、残差直方图、QQ 图、自相关图。残差应该像白噪声没有明显趋势QQ 图上点基本贴在对角线附近。如果残差在滞后 12 处还有显著自相关说明季节信息没提干净回去把D加到 2 或换更大的P。Ljung-Box 检验可以量化这件事from statsmodels.stats.diagnostic import acorr_ljungbox residuals model.resid() lb acorr_ljungbox(residuals, lags[12], return_dfTrue) print(lb[lb_pvalue])p 值大于 0.05 说明残差是白噪声模型把能提取的信息都提取完了小于 0.05 就回到参数选择这一步继续调。6.3 出图细节横坐标太密集时怎么处理交付项目时最重要的产出往往是那张预测对比图。画图时有个高频小问题时间跨度长横坐标标签挤成一团数字叠在一起。解决办法是旋转刻度并限制刻度数量。import matplotlib.pyplot as plt plt.figure(figsize(10, 4)) plt.plot(test.index, real, label观测值, markero) plt.plot(test.index, preds, label预测值, markerx) plt.xticks(rotation45) # 旋转 45 度避免标签重叠 plt.gca().xaxis.set_major_locator(plt.matplotlib.dates.MonthLocator(interval2)) plt.ylabel(降雨量 (mm)) plt.legend() plt.tight_layout() plt.savefig(rainfall_pred.png, dpi300)MonthLocator(interval2)让横坐标每两个月才出一个刻度比手动写plt.xticks的列表省事。dpi300保证导出图片在报告里放大不糊。我早期做这类项目时也在随机切分上翻过车后来把滚动预测和残差诊断固化成标准流程才敢把模型结果交出去给业务方看。这类项目值得做的原因不是模型多高级而是它逼你把时间序列分析从“跑通脚本”推进到“对结果负责”每一步踩坑都算数。希望帮到你。本文还有配套的精品资源点击获取