资讯中心

Python时间序列降雨量预测:ARIMA/SARIMA建模与实战解析

📅 2026/9/28 12:08:50
Python时间序列降雨量预测:ARIMA/SARIMA建模与实战解析
简介基于Python与时间序列分析的降雨量预测系统项目定位为课程设计与后端开发方向的完整源码包面向气象、农业领域的数据分析初学者及有Python基础的开发者可支撑从历史降雨数据清洗、缺失值与异常值处理到ARIMA、SARIMA等模型训练与未来趋势预测的完整流程。压缩包约42.49MB工程结构包含数据预处理模块、时间序列分析模块与可视化模块的Python脚本并配有安装运行说明、功能使用文档及依赖库配置指南便于使用者对照源码理解每个环节的实现细节。该项目已有153人学习。通过阅读和运行源码读者既能获得一个可扩展的降雨量预测原型也能掌握pandas、NumPy、statsmodels等库在时间序列场景中的实际组合方式学习缺失值处理、异常值识别、模型参数调优与结果可视化的通用思路对农业灌溉计划安排、气象灾害预警等实际业务具有直接参考价值。1. python项目基于时间序列分析的降雨量预测cs.zip先搞清楚你拿到的是什么把cs.zip解压后里面通常是一个可以直接跑的 Python 项目数据文件、训练脚本、预测脚本可能还有一个 README。这套基于时间序列分析的降雨量预测项目核心不是拿深度学习硬怼而是把历史降雨量当成一个按时间排序的信号用 ARIMA、SARIMA 这类经典时间序列模型去估计下一阶段会下多少雨。对刚开始做数据分析的初学者它是一条能完整走完“清洗—建模—验证”的 Python 源码对气象、水利、农业方向的工程师它比堆大模型更轻、更好解释而且跑起来不挑机器。2. 降雨量为什么用时间序列分析先把模型选型逻辑说透2.1 降雨数据不是普通表格自相关与平稳性先分清很多人第一次打开降雨量 CSV 时会下意识把它当成监督学习问题湿度、气压、温度是 X降雨量是 y。这么想不算错但会漏掉降雨数据最重要的一个性质——自相关。今天下了大雨明天继续下雨的概率就是比晴天更高上个月的降雨量对下个月也有影响。普通表格里的样本要求相互独立而时间序列里的每一条数据都和后一条有前后关联直接套用随机划分的机器学习流程会让模型“看着很强换时段就废”。时间序列分析里还有两个绕不开的词平稳性和季节性。平稳性指序列的均值、方差不会随着时间发生剧烈漂移季节性是降雨量最典型的特征雨季旱季交替、汛期集中出现都会让序列呈现固定的周期。一个降雨量预测项目能不能跑通很大程度取决于先回答“这个序列平稳吗、季节周期是多长”而不是先纠结用哪个模型。2.2 ARIMA、SARIMA 和 LSTM小项目选型的三个判断标准我在搭建这类预测原型时选型一般只看三个标准。第一个是数据量。如果只有一个站点、几千条日尺度数据LSTM 很容易训练不足ARIMA 反而更稳。只有当你手上有几十万条逐小时数据、多站点多特征时深度学习才有发挥空间。第二个是周期是否固定。降雨的季节性在多数地区近似固定SARIMA可以显式地设置周期比如m12表示月周期、m24表示小时周期LSTM 也能学周期但需要更多数据去“悟”。第三个是解释和部署成本。气象业务里经常要回答“为什么预测这么高”ARIMA 能给出清晰的自回归系数LSTM 只能给一张注意力热图。所以对于cs.zip这种自带数据的 Python 项目最稳妥的路径是先用pandas做数据可视化再跑seasonal_decompose看趋势和季节然后从SARIMA起步。不要一上来就翻 Python 量化交易教程里的 LSTM 代码降雨量预测的数据量级和序列特性都不支持那么重的模型。2.3 建模前必看的三张图自相关图、偏自相关图和季节分解打开 Jupyter 或 VS Code先把降雨量画成曲线。曲线能告诉你有没有明显周期但看不到周期内部的滞后关系。这时候要跑三样东西plot_acf看自相关函数plot_pacf看偏自相关函数seasonal_decompose把序列拆成趋势、季节、残差三部分。对降雨量月数据如果 ACF 在 12、24、36 阶处出现递减的尖峰说明季节周期是 12 个月SARIMA的m就取 12如果 PACF 在 1 阶或 2 阶后突然截断p大概率是 1 或 2。这一步做扎实后面调参才不会变成玄学。3. 把原始气象数据整理成可训练的时间序列清洗、重采样与特征工程3.1 时间戳清洗数据里最容易被忽略的第一道坎拿到降雨量 CSV 后我通常先把日期列解析成DatetimeIndex然后统一频率。很多公开数据源的日期格式五花八门有2023/5/1 8:00也有20230501080000后者必须显式指定format才能解析快、不报错。CSV 如果是从 Excel 导出的还可能混进时区偏移或NaN时间这一步不清理后面所有模型都会在索引上翻车。import pandas as pd # parse_dates 让 pandas 直接把字符串列变成时间类型 df pd.read_csv( rainfall.csv, parse_dates[date], index_coldate, infer_datetime_formatTrue, ) # 统一为连续时间戳去掉重复和排序问题 df df[~df.index.duplicated()].sort_index() # 依次检查缺失量与时间跨度 print(df.info()) print(df.isna().sum()) print(df.index.min(), df.index.max())这里infer_datetime_formatTrue能自动识别常见日期格式但如果遇到20230501080000这种纯数字字符串它可能识别不出来。保险做法是用pd.to_datetime(df[date], format%Y%m%d%H%M%S)。参数说明parse_dates负责在读取阶段完成解析index_col确定哪一列作为序列索引duplicated()筛掉重复时间点sort_index()保证数据按时间升序。降雨量预测模型对顺序极其敏感索引乱序是最隐蔽的坑之一。3.2 缺失值与异常值处理不要先把降雨量填成 0降雨量数据的缺失很常见常见做法是ffill或线性插值。但这里有两个坑连续长时间缺失不能直接前向填充否则等于把昨天的雨“复制”到了整个旱季降雨量本身也不是线性变化插值只适合短时间窗口。我一般这样处理缺失长度小于等于 3 个时间步用线性插值大于 3 个时间步就标记为NaN建模时整段丢弃异常值则用向下裁剪加窗口判断。# 降雨量不可能为负先用下限裁剪 df[rain] df[rain].clip(lower0) # 缺失较短用线性插值缺失较长先保留 NaN df[rain] df[rain].interpolate(methodlinear, limit3) # 超过 3 个连续缺失的直接剔除 df df.dropna(subset[rain]) # 用滚动中位数识别极端异常超过 5 倍中位数则替换 rolling_med df[rain].rolling(7, centerTrue).median() df.loc[df[rain] rolling_med * 5, rain] rolling_med这段代码的逻辑分三层首先做业务约束降雨量负值直接归零其次做插值限制limit3只填充近期缺失防止长缺失被凭空补出来最后做异常替换不删除整行而是用窗口内中位数顶替。注意rolling(7, centerTrue)以当前点为中心取前后 3 天用于捕捉局部稳定水平如果数据是小时粒度这个窗口要改成 24 或 48不要照搬日尺度参数。3.3 特征工程滞后项、滑动均值与季节哑变量如果你打算用 SARIMA模型内部会自动处理滞后项特征工程可以很轻但如果你准备切换成随机森林或 XGBoost就必须手动把“时间”这个信息拆成机器能看懂的列。常见做法是构造滞后特征、滑动平均和季节标记。以小时数据为例过去 1、2、3、24 小时的降雨量就是最有解释力的候选特征同时加入“是否是雨季”这类日期型特征可以让模型更容易学到旱季和雨季的差异。for lag in [1, 2, 3, 24]: df[frain_lag_{lag}] df[rain].shift(lag) # 最近 3 小时滑动平均 df[rain_rolling_3h] df[rain].rolling(3).mean() # 小时数据下用月份表示季节周期 df[month] df.index.month # 删除构造特征产生的前 24 行空值 df df.dropna() print(df.head())这段代码里shift(lag)把过去第lag个时刻的降雨量移到当前行相当于给模型提供“昨天下了多少雨”的信息rolling(3).mean()生成短时平滑值用来逼近当前天气系统的延续性。month则是让树模型也能感知季节。需要特别注意的是这些特征建模前必须dropna否则带空值的样本会进入模型导致预测时无法对齐特征数量。3.4 数据集切分按时间切不能随机打乱训练集、验证集、测试集的切分是时间序列项目最容易出致命错误的地方。普通机器学习用train_test_split(random_state42)时间序列绝对不能用。随机打乱会把未来的数据泄露到训练里模型看似精度很高实际只是背下了答案。正确做法是按时间顺序切并且最好做滚动验证。# 按时间顺序切分前 80% 训练后 20% 测试 cutoff int(len(df) * 0.8) train df.iloc[:cutoff] test df.iloc[cutoff:] print(f训练区间: {train.index.min()} - {train.index.max()}) print(f测试区间: {test.index.min()} - {test.index.max()})注意这里的cutoff必须基于排序后的df不能先sample再切。我习惯把测试集的起始时间打印出来一方面是确认前后区间不重叠另一方面是防止原始数据本身乱序导致测试集混进早期数据。这个动作虽然简单却能避免以后跟同事争论“为什么模型在测试集上效果那么好”这种尴尬问题。4. 用 statsmodels 跑通最小可用的降雨量预测代码、参数与输出4.1 平稳性检验ADF 测试写进脚本里在拟合任何 ARIMA 类模型前先做一次 ADF 平稳性检验。如果一个序列不平稳模型的自回归部分会出现伪回归预测值跟着随机游走漂移。对降雨量项目检验结果往往不理想尤其是月降雨量接近旱季时会出现接近零的长尾这时不要急着差分先看一下差分几阶能让 p 值低于 0.05。from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf # 对训练集做 ADF 检验 adf_result adfuller(train[rain]) print(ADF p-value:, adf_result[1]) # 如果不平稳做一阶差分后再看一次 if adf_result[1] 0.05: diff_series train[rain].diff().dropna() print(diff_series)ADF 的p-value小于 0.05 表示拒绝“存在单位根”的原假设序列可视为平稳。如果第一次检验不通过代码会继续生成差分序列下一步把差分阶数d1带进模型。还有一个容易被忽略的点ADF 检验最好对训练集做不要对全量数据做。因为测试集信息一旦参与检验就相当于提前看到了未来这也是一种软性的数据泄漏。4.2 拟合最常用的 SARIMA 模型核心参数逐个说当数据有明显季节周期时SARIMA 是最省心也最不容易翻车的选择。它可以同时建模非季节性部分order(p,d,q)和季节性部分seasonal_order(P,D,Q,m)。对月降雨量m12对小时降雨量如果希望捕捉“一天内的降雨规律”m24也是常见取值。下面是一段最小可用的拟合代码。from statsmodels.tsa.statespace.sarimax import SARIMAX model SARIMAX( train[rain], order(2, 1, 1), seasonal_order(1, 1, 1, 12), enforce_stationarityFalse, enforce_invertibilityFalse, missingdrop, ) fit model.fit(dispFalse) print(fit.summary())参数说明order(2,1,1)里的p2表示使用最近两期降雨量的自回归项d1表示做一阶差分q1表示使用一期预测误差的移动平均seasonal_order(1,1,1,12)表示季节自回归、季节差分、季节移动平均周期为 12。两个enforce_*参数建议设为False否则 statsmodels 会强制要求模型满足数学上的稳定条件导致部分偏离典型时序特征的降雨数据拟合失败。missingdrop则是让模型自动跳过缺失区间而不是整体报错。4.3 预测未来一段时间的降雨量点预测和置信区间都要看模型拟合完后不要只拿predict出一条线建议用get_forecast同时取点预测和置信区间。降雨量本身就是高方差的自然过程置信区间能直观告诉你模型的确定性有多低。如果区间宽度达到单点预测值的数倍说明模型已经意识到“未来完全看不准”这个信号比任何指标都值得关注。# 预测测试集长度的未来时间步 forecast fit.get_forecast(stepslen(test)) pred_mean forecast.predicted_mean pred_ci forecast.conf_int() # 保存模型与预测结果后续部署直接加载 fit.save(rainfall_model.pkl) # 用 matplotlib 画真实值、预测值和置信区间 import matplotlib.pyplot as plt plt.plot(test.index, test[rain], labelactual) plt.plot(test.index, pred_mean, labelforecast) plt.fill_between( test.index, pred_ci.iloc[:, 0], pred_ci.iloc[:, 1], colorgray, alpha0.2, ) plt.legend() plt.show()get_forecast(stepslen(test))会严格按测试集长度生成预测pred_exog是额外解释变量的位置这里没有外生变量所以不需要传。conf_int()默认给出 95% 置信区间如果有业务需求可以在get_forecast里传alpha0.1得到 90% 区间。在 Python 数据分析与可视化里把真实值和预测叠在一张图上往往比打印 RMSE 更能暴露模型滞后问题。4.4 参数怎么调AIC 不是唯一标准很多网上教程让你写两层循环搜索(p,d,q)组合然后选 AIC 最小的那个。这么做能跑但放在降雨量项目里有两个问题一是数据量大时网格搜索极慢尤其seasonal_order有 4 个维度二是 AIC 最小化会牺牲可解释性比如出现p7, q0这种诡异组合业务上很难解释为什么七天前的降雨对今天影响最大。我常用的做法是先用plot_acf和plot_pacf把候选阶数缩到两三个再手动比较 AIC。最终选模型时除了 AIC还要看残差是不是白噪声。如果残差里仍然有明显自相关说明季节周期没设对这时候调m比调p和q更重要。5. 降雨量预测项目的常见坑与排查数据泄漏、静默报错和过拟合5.1 现象训练集效果很好测试集预测曲线几乎是一条直线这是时间序列项目里最典型的“翻车现场”。训练集上 RMSE 很低一到测试集预测值要么接近均值要么只跟着最近一期缓慢平移完全没抓住降雨峰值。原因通常是两处一是在构建滞后特征时直接用全量数据计算了均值、标准差或滑动窗口测试集信息已经渗入训练二是在切分数据后没有重新训练特征工程里的rolling窗口导致测试集的特征值使用了未来数据。解决方法是把数据清洗和特征构造放在切分之后只基于训练集拟合均值、标准差等统计量测试集仅做变换切分方式可以改用TimeSeriesSplit做滚动验证而不是用普通KFold。5.2 现象预测值出现负数降雨量变成“晴天倒扣雨”回归模型的输出本质上是连续值如果训练集里降雨量分布严重右偏、大多数时间接近 0模型学到的条件均值在小雨时段就可能低于 0。查了半天不是代码 bug而是模型本身没有业务上下界。解决办法有两个层次第一层是输出层做clip(lower0)但这只能治标第二层是对序列做np.log1p(rain)变换让模型在近似正态的空间里工作预测后再用np.expm1还原。对降雨量这种零值很多的序列log 变换配合clip是常规做法能明显减少负数输出。5.3 现象ADF 检验说平稳但预测结果总是滞后一期预测曲线永远比真实曲线慢一步。这个问题在气象预报里经常被归结为“模型没有本领”。其实背后的原因是降雨量本质上是近乎随机的过程时间序列模型给出的条件均值天然会向近期观测值靠拢所以一步预测表现尚可多步预测就开始滞后。解决办法不是换 LSTM而是分析数据粒度。日尺度数据如果降雨事件是半天内发生的滞后是正常的把数据聚合成周尺度预测目标变成“周总降雨量”滞后问题会明显减弱。另一个有效手段是加入外生变量比如气温、湿度、气压让模型在状态变量上获得更多信息而不是只依赖降雨自身历史。5.4 现象模型拟合过程静默卡死内存被打满SARIMA 的拟合复杂度会随样本量增加而明显上升尤其当你把逐 5 分钟降雨量灌进去时几万行数据就可能让 Python 进程长时间无响应且不报错。原因是 statsmodels 的状态空间模型对每个时间步做卡尔曼滤波数据越长矩阵运算越重。解决方法是先把时间跨度缩小到一个合适的建模窗口例如只取最近三年日数据如果业务要求高时间分辨率那就先按天聚合再做小时级预测时单独建一个高峰时段子模型。还有一个技巧是把频率统一成连续的规则间隔时间序列模型最怕不规则采样宁可损失一点精度也要保证索引间隔一致。5.5 现象季节分解报错提示时间索引重复或频率未知季节分解要求时间索引严格等距而降雨量原始数据经常因为雨量计检查维修出现采样间隔不均或重复记录。遇到这类错误时先去df.index检查重复和最小值间隔然后用df.resample(D).sum()或者mean()重新聚合。注意逐小时数据聚合到日的时候如果缺失时段过多直接求和会把缺失时段当成 0这会严重低估降雨总量。正确做法是设定min_count20表示一天至少要有 20 小时原始观测否则该日标记为缺失再走上一节的缺失处理流程。6. 让降雨量预测真正可信滚动回测、残差检验与阈值预警先回到最核心的问题你的模型不是要跑出一张好看的曲线而是要能应对“换一段时间后性能不崩”。我验证降雨量预测模型时不会只做一次训练集/测试集切分而是用滚动回测。from statsmodels.tsa.statespace.sarimax import SARIMAX # 滚动窗口每次仅用过去 train_len 期预测未来 horizon 期 def rolling_predict_sarima(data, train_len720, horizon24, step72): history data.iloc[:train_len].copy() preds [] while len(history) horizon len(data): fit SARIMAX( history[rain], order(2, 1, 1), seasonal_order(1, 1, 1, 24), enforce_stationarityFalse, enforce_invertibilityFalse, ).fit(dispFalse) pred fit.get_forecast(stepshorizon).predicted_mean preds.append(pred) history data.iloc[:len(history) step] return pd.concat(preds)滚动回测的核心是模拟“今天之前只用过去的数据预测未来然后时间窗口后移”。代码里train_len720表示 720 小时horizon24表示一次预测未来一天step72是每三天重训一次。这样做比单次切分更接近真实部署也能发现模型是否在某个汛期阶段系统性失效。回测之后还要查残差。用 Ljung-Box 检验残差自相关p_value低于 0.05 就说明模型把该学的时间关系漏掉了。from statsmodels.stats.diagnostic import acorr_ljungbox resid fit.resid ljung_box acorr_ljungbox(resid, lags12, return_dfTrue) print(ljung_box)最后一步是阈值预警。降雨量业务真正关心的不是“明天精确到 1 毫米”而是“明天会不会超过暴雨级别”。所以我会把预测结果转成二分类是否超过20mm/h再计算命中率。只要预警命中率稳定单点预测误差大一点也可以接受。这几年我养成的习惯是模型跑完先打印滞后图、残差图和滚动回测结果三张图都说得通才谈上线否则永远只是在调参数。希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案