资讯动态

天气预测项目实战:时间序列数据处理与模型评估全流程

发布时间:2026/10/9 22:46:55 来源:尧图企业网站定制
简介面向正在完成期末大作业或毕业设计的计算机专业学生这套基于Python的机器学习天气预测与数据可视化完整源码提供了从数据处理到结果展示的完整项目链。资源共24个文件压缩包约1.43MB包括4个Python脚本、4个CSV训练与测试数据集、12张项目运行截图、1个模型文件、1个HTML可视化页面及说明文档清晰覆盖数据清洗、特征处理、模型训练、天气预测与可视化展示等核心环节。源码经过本地编译调试获指导老师认可并评定98分难度适中目前已吸引124人学习下载。结合readme说明和源码读者可学会如何用日期与地理数据训练模型并输出预测结果也可借助示例数据和预训练模型快速验证算法效果适合作为课程设计参考或项目实战模板。1. 天气预测项目看着简单分数差距却全藏在数据处理里机器学习的天气预测项目常被外界看作“拿历史温度跑个回归”但真正动手的人都知道卡住你的从来不是模型而是时间序列数据那一堆坑时区、缺失值、滞后特征、训练集与测试集的切分方式。同样一份天气数据有人直接随机切分跑出漂亮的R²有人按时间切分后才发现模型在短期预测里根本没有泛化能力前者是典型的数据泄漏假象。这个标题下要交付的不只是源码能跑通而是把“天气数据 → 特征工程 → 模型测评 → 数据可视化”整条链路做成一个可解释、可复现的高分项目。适合课程设计、毕业设计也适合想用一份真实时序数据练手机器学习的开发者。2. 数据获取与预处理天气预测的胜负手在喂给模型之前的每一步拿到一份天气数据先不要急着建模。天气预测项目的实际工作量里建模只占三成剩下七成都在数据清洗和特征工程上。许多同学最后分数不理想不是因为模型选错而是因为喂给模型的数据本身就带着时区错乱、缺失值和“未来信息”。以常见的逐日气象记录为例数据通常来自某开放数据平台的CSV导出或者课程素材包字段一般包含日期、平均气温、最高气温、最低气温、降水量、相对湿度、风速、天气状况等。下面的表格是最常见的字段形态具体以你拿到的数据为准。字段类型说明常见缺失情况datedatetime记录日期偶发缺行tempfloat当日平均温度较多缺失temp_maxfloat当日最高温度偶发缺失precipfloat当日降水量干燥地区大量为0humidityfloat当日平均相对湿度偶发缺失weatherobject天气状况文本描述少量缺失2.1 数据源与字段一开始就要把时间列当成索引拿到数据后我一般先不急着训练先把DataFrame的info、describe和isnull各看一遍把字段类型和取值范围记住。这一步能提前暴露很多问题比如最高温度出现99、日期被读成字符串、降水量全是负值等。如果连数据长什么样都不清楚就直接跑模型后面出了问题根本不知道去哪排查。import pandas as pd df pd.read_csv(weather_data.csv, parse_dates[date], index_coldate) print(df.info()) print(df.describe()) print(df.isnull().sum())逻辑说明parse_dates[date]让pandas把日期列解析为DatetimeIndexindex_col设置日期为索引这是后续滞后特征和时间序列切分的地基。describe能快速暴露异常取值范围如果看max发现温度到了99说明里面有传感器错误记录。这里多做一步后面能少踩三个坑。关于缺失值先量化再决定处理方式。如果整列缺失超过30%这一列基本可以放弃不要硬填如果只是零星缺失才需要走插值或填充。天气预测项目的训练数据通常是逐日粒度几千行规模少量缺失完全可以通过时间维度上的插值弥补。2.2 缺失值与异常值温度用插值降水用零填充天气数据最常见的三个问题某些日期完全没有记录、传感器异常导致温度突跳、降水字段空值。处理原则按业务来——温度是连续变量缺失时线性插值比均值填充更合理因为气温变化本身带有连续性降水是累积量没有记录通常更接近0而不是平均值。import numpy as np # 温度线性插值两端也允许补齐 df[temp] df[temp].interpolate(methodlinear, limit_directionboth) # 降水缺失当天视为无降水 df[precip] df[precip].fillna(0) # 异常突跳相邻日温度差超过15度视为记录错误置空后重插 df[temp_spike] df[temp].diff().abs() df.loc[df[temp_spike] 15, temp] np.nan df[temp] df[temp].interpolate() df df.drop(columns[temp_spike])参数说明interpolate的limit_directionboth允许序列开头和结尾的缺失也参与插值diff()计算相邻日差值abs()取绝对值阈值为15度对应“单日温度骤变15度以上”的业务异常。置空后再插值相当于把突跳点抹平模仿人工修正。要注意15度这个阈值不是死的高纬度地区冷锋过境时温差可能更大建议先画出温度曲线看毛刺在哪再定阈值。2.3 特征工程把天气序列改成监督学习能吃的表格天气预测本质是时间序列预测但多数课程项目会把它转成监督学习用前几天的温度和其他气象变量预测今天的温度。构造方式就是滞后特征、滚动统计、时间编码三件套。滞后特征让模型看到“昨天多热”滚动统计让模型看到“最近一段时间的趋势”时间编码让模型感知“现在是什么季节”。for lag in [1, 2, 3, 7]: df[ftemp_lag_{lag}] df[temp].shift(lag) df[temp_roll_mean_7] df[temp].rolling(7, min_periods3).mean() df[month] df.index.month df[dayofyear] df.index.dayofyear df df.dropna()参数说明lag取1、2、3是捕捉近几日温度的惯性lag取7是捕捉周周期——天气有“上周同期”的参考价值rolling(7, min_periods3)表示至少3个有效值就计算滚动均值避免序列开头直接变NaNmonth和dayofyear让模型感知季节与年内位置。如果数据里还有湿度、气压这类变量同样可以加滞后和滚动但不要一次性把所有列的滞后全塞进去特征一多、样本一少就过拟合。特征构造完做一个快速相关性检查确认哪些特征和目标是线性相关的这个结果也能和后面的模型特征重要性互相印证。features [temp_lag_1, temp_lag_2, temp_lag_3, temp_lag_7, temp_roll_mean_7, month, dayofyear, humidity_lag_1] print(df[features [temp]].corr()[temp].sort_values(ascendingFalse))逻辑说明corr默认用皮尔逊相关系数只看线性关系某些特征如month、dayofyear虽然不是线性相关但后面树模型仍能从中挖出非线性规律。这个列表可以帮你判断特征是否冗余如果湿度滞后和其他特征相关系数过高就要考虑是否保留。3. 模型训练与评估先跑基线再决定要不要上XGBoost和LSTM模型选择这条线我见过两种极端一种是一上来就堆LSTM训练半天效果还不如线性回归另一种是只跑了个线性回归就当交差。比较稳的路线是先跑线性基线摸清数据和指标的量纲再上XGBoost看提升幅度数据量足够大时再考虑LSTM。这样每一步都有对照写报告时也能讲清楚“为什么选这个模型”。3.1 时间序列切分这个步骤决定你的分数是真是假很多同学习惯直接用train_test_split默认参数下它会随机打乱数据。但天气数据是时间序列随机打乱后训练集里混入了未来日期的信息测试集等于开卷考试分数虚高得没有参考价值。正确的切分是严格按时间顺序前80%做训练后20%做测试。train_size int(len(df) * 0.8) train, test df.iloc[:train_size], df.iloc[train_size:] print(ftrain: {train.index[0]} ~ {train.index[-1]}, {len(train)} rows) print(ftest: {test.index[0]} ~ {test.index[-1]}, {len(test)} rows)逻辑说明df在上一章已经做过滞后特征和dropna删除的是最前面的缺失行所以这里的iloc切片严格保持时间顺序。train是模型见过的历史test是模型没见过的未来这样评估出来的误差才是真正部署时的误差。顺手打印train和test的时间范围防止切片切错方向。3.2 线性回归基线先把下限摸出来线性回归在这个项目里的定位不是最终模型而是“秤”——用最简单的模型量出指标的下限。如果线性回归MAE已经在2℃以内后续模型优化的空间就有限应该把精力放在特征而不是模型上如果线性回归MAE高达5℃说明特征和温度的关系不是简单线性树模型才可能有明显提升。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np features [temp_lag_1, temp_lag_2, temp_lag_3, temp_lag_7, temp_roll_mean_7, month, dayofyear] X_train, y_train train[features], train[temp] X_test, y_test test[features], test[temp] lr LinearRegression() lr.fit(X_train, y_train) pred_lr lr.predict(X_test) print(R2:, r2_score(y_test, pred_lr)) print(RMSE:, np.sqrt(mean_squared_error(y_test, pred_lr))) print(MAE:, mean_absolute_error(y_test, pred_lr))评估指标我的习惯是三个一起看R²看整体解释度RMSE对误差大的点更敏感MAE语义最直白——平均差几度。对于温度预测业务上可接受误差通常在2℃以内如果MAE超过3℃说明特征或模型还有明显短板。3.3 XGBoost三个参数调明白就够了XGBoost在中小规模天气数据上是性价比最高的选择训练快、自带正则、能处理特征间的非线性关系。参数上我一般只用一套保守配置重点调三个n_estimators、learning_rate、max_depth。其余参数保持默认等这三个调完再考虑subsample这类防过拟合手段。from xgboost import XGBRegressor xgb XGBRegressor( n_estimators300, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42 ) xgb.fit(X_train, y_train) pred_xgb xgb.predict(X_test) print(R2:, r2_score(y_test, pred_xgb)) print(RMSE:, np.sqrt(mean_squared_error(y_test, pred_xgb))) print(MAE:, mean_absolute_error(y_test, pred_xgb))参数说明learning_rate0.05配合n_estimators300是“小学习率加多棵树”的经典组合比直接怼500棵默认学习率的树更稳max_depth4限制单棵树深度防止对训练集里的噪声建模subsample0.8和colsample_bytree0.8分别对样本和特征做随机抽样抑制过拟合。如果数据量很小几百行n_estimators建议降到150以内避免训练时间拉长还过拟合。再看一下特征重要性这既是调参依据也是报告里论证特征工程价值的好材料。通常temp_lag_1会排第一dayofyear和temp_roll_mean_7紧随其后说明滞后特征和时间编码确实在起作用。importance pd.Series(xgb.feature_importances_, indexfeatures).sort_values(ascendingFalse) print(importance)逻辑说明feature_importances_输出的是增益占比值越大代表该特征在节点分裂时贡献越多。如果month接近0、month和dayofyear同时出现可以删掉贡献低的那一个减少维度如果lag_7比lag_3还重要说明这个城市的天气具有很强的周周期性值得在报告里单独提一句。3.4 LSTM样本量不够大时优先级往后放LSTM不是不能做天气预测但多数课程项目只有几百到几千条逐日记录LSTM在小样本上很难发挥优势还容易把大量时间花在调试输入形状和归一化上。我的判断标准是数据不足1万条时XGBoost几乎总是更省心只有要做未来多天多步预测、或者明显需要长序列依赖时才认真考虑LSTM。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() scaled scaler.fit_transform(df[[temp]]) def build_sequences(data, lookback7): X, y [], [] for i in range(lookback, len(data)): X.append(data[i-lookback:i]) y.append(data[i, 0]) return np.array(X), np.array(y) X, y build_sequences(scaled, lookback7) train_X, train_y X[:train_size], y[:train_size] test_X, test_y X[train_size:], y[train_size:] model Sequential([ LSTM(64, input_shape(7, 1)), Dense(1) ]) model.compile(optimizeradam, lossmse)这里只是最小示例实际训练前还需要把其他特征也做归一化并拼进三维张量。LSTM的输入必须是(samples, timesteps, features)三维结构这是它与XGBoost最根本的区别MinMaxScaler会把温度缩放到0~1区间预测完再inverse_transform还原成真实温度。跑之前先把这两步写对否则Loss会一直是NaN。样本量小的时候不建议硬上这是血泪经验。4. 数据可视化预测结果、特征分布与误差分析一次画全“完整源码”里数据可视化的篇幅通常不少但很多同学只画了一张预测对比图就完事。高分项目的可视化应该回答三个问题预测准不准、误差长什么样、数据本身有什么规律。围绕这三个问题用matplotlib配合seaborn画四类图就够预测对比曲线、残差分布、相关性热力图、季节性趋势图。图表不是越多越好每张图都要能支撑报告中的一句结论。4.1 预测与真实曲线对比一眼看出谁跟得更紧选一段连续时间窗口比如测试集前60天把真实温度和线性回归、XGBoost的预测值画在同一张图上。为什么不画全部因为测试集可能有几百上千条记录全画上折线会糊成一片反而看不出差异。import matplotlib.pyplot as plt plt.figure(figsize(14, 5)) plt.plot(test.index[:60], y_test[:60], labelactual, linewidth1.6) plt.plot(test.index[:60], pred_lr[:60], labellinear, linewidth1.2, linestyle--) plt.plot(test.index[:60], pred_xgb[:60], labelxgb, linewidth1.2, alpha0.85) plt.legend() plt.xlabel(date) plt.ylabel(temperature) plt.title(Weather Forecast Comparison) plt.tight_layout() plt.savefig(forecast_compare.png, dpi200)参数说明figsize(14, 5)用宽画布适配时间序列横轴dpi200保证图片插入Word或PPT时依旧清晰alpha0.85让XGBoost曲线不至于完全盖住真实线。如果对比的模型再多建议配色用同一色系深浅区分避免报告里出现红红绿绿的一堆线。4.2 残差分析模型哪里不行残差图全招了残差等于真实值减预测值。画直方图看误差是否呈零均值正态分布画散点图看误差是否随温度变化而变化。如果直方图整体偏左说明模型系统性地高估如果散点呈喇叭形说明温度越高误差越大模型对极端高温识别不足。这一块在答辩里非常加分因为它说明你不只会调参还会从误差看模型边界。residual y_test - pred_xgb plt.figure(figsize(14, 4)) plt.subplot(1, 2, 1) plt.hist(residual, bins40, edgecolorwhite) plt.axvline(0, colorred, linestyle--) plt.xlabel(Residual) plt.title(Residual Distribution) plt.subplot(1, 2, 2) plt.scatter(y_test, residual, s8, alpha0.6) plt.axhline(0, colorred, linestyle--) plt.xlabel(Actual Temp) plt.ylabel(Residual) plt.title(Residual vs Actual) plt.tight_layout() plt.show()逻辑说明bins40把直方图分得足够细便于观察误差分布形态散点图每个点代表测试集里一天的真实温度和对应误差。判断时要结合业务如果残差在夏季普遍为负说明模型在高温日总预报偏低可以考虑在特征里加入“昨日最高温差”或“连续高温天数”这类特征。4.3 相关性与季节性趋势数据探索部分的标配相关性热力图和按月平均温度折线是报告“数据探索”章节最常见的两张图。热力图让读者快速理解特征之间的共线性比如temp_lag_1和temp_roll_mean_7高度相关说明这两个特征传递的信息有重叠按月聚合的曲线则直观呈现出全年温度的单峰或双峰形态为季节特征的重要性提供佐证。import seaborn as sns corr df[features [temp]].corr() plt.figure(figsize(10, 8)) sns.heatmap(corr, annotTrue, fmt.2f, cmapcoolwarm, center0) plt.title(Feature Correlation) plt.tight_layout() plt.show() monthly_mean df[temp].resample(M).mean() plt.figure(figsize(10, 4)) monthly_mean.plot() plt.title(Monthly Average Temperature) plt.xlabel(month) plt.ylabel(temperature)参数说明annotTrue在格子里显示相关系数数值fmt.2f保留两位小数center0让色调以零为中心分布负相关和正相关一眼区分resample(M)按自然月聚合后求均值把每日温度的毛刺抹平。如果手里数据覆盖多年还可以按年份分色画多条线能看出不同年份同月的气温波动。4.4 中文字体与坐标轴格式不显眼但能毁掉整张图如果报告要求中文标题不设置字体的话matplotlib默认字体渲染不出中文图上全是方框。这是可视化环节最常见的翻车现场分数再高也会被扣印象分。plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False参数说明SimHei是Windows自带黑体Linux环境需要改为系统里实际安装的中文字体axes.unicode_minus设置为False防止负号在中文环境下显示成方框。这段配置建议放在脚本最前面统一设置而不是每张图单独写。每年都有项目折在图片乱码上提前写上就是给自己省事。5. 天气预测项目最容易翻车的5个坑与排查方案这个项目的坑不在模型里而在数据处理和评估方式里。下面的五条都是实际做这个项目时最容易踩的每一条都按“现象 → 原因 → 解决”展开排查方法可以直接抄。5.1 特征泄漏R²高得不真实先别高兴现象模型R²超过0.97测试集误差小到不可思议高得让人发慌。原因最常见的是把原始temp列当作特征塞进了模型或者用shift(-1)构造了“未来温度”的滞后特征还有人用train_test_split时没设shuffle随机打乱把未来日期混进了训练集。解决在建模前对特征清单做一层断言保护。assert temp not in features, target leaked into features这句断言会直接让程序报错阻止训练是排查的第一步。然后检查滞后特征的构造方向shift(1)是取前一天shift(-1)是取后一天后者就是泄漏。最后确认数据划分方式是按时间切片而非随机打乱。每加一个新特征都问一句“在做预测的当天这个值真的能拿到吗”拿不到的统统不该出现。5.2 时间戳格式问题日期列读出来是字符串现象画图时横轴顺序乱、滞后特征全变成NaN、按年度切片报错。原因CSV里日期格式五花八门2024/1/5、20240105、05-Jan-24各不相同pd.read_csv默认把它当作普通字符串。解决读入时显式解析然后检查索引类型。df pd.read_csv(weather_data.csv, parse_dates[date], index_coldate) print(df.index.dtype)如果date_format不匹配导致解析失败可以先手动指定格式pd.to_datetime(df[date], format%Y/%m/%d)。判定成功的标准就是df.index.dtype显示为datetime64类型横轴顺序问题自然消失。5.3 缺失值填错方式温度用0填充冬季预测直接被拉偏现象模型在冬季预测值系统性偏低MAE比夏季高一截。原因温度列缺失后用fillna(0)填充把无记录填成了0度冬天真实温度接近0时误差还不明显但秋季和春季的数据也被拉出一个假低温。解决连续变量用interpolate只有降水这类本身接近0的量才用fillna(0)。如果时间跨度大线性插值可能留下不自然的直线段这时候配合前后7日均值做平滑填充也可以重点是别用全局常量去填时序数据。5.4 天气状况类别直接硬编码成数字现象把晴天、多云、雨天编码成0、1、2后模型学出“数值越大天气越恶劣”的假规律看到3就预测低温。原因有序整数编码本质上给类别强加了顺序关系但天气类型之间没有这种顺序。解决用one-hot编码或者干脆不放入模型。weather_dummies pd.get_dummies(df[weather], prefixweather, dummy_naFalse) df pd.concat([df, weather_dummies], axis1)参数说明prefixweather让生成的列名称为weather_晴、weather_雨这样的形式dummy_naFalse表示不单独为缺失值生成一列。如果天气类型取值很多但样本量小one-hot也会带来维度膨胀这时候优先考虑去掉这个特征别让模型为稀疏列付出过拟合代价。5.5 多步预测的误差滚雪球现象单步预测MAE只有1.8℃做成“未来7天预报”后第5天开始误差直接冲到5℃以上。原因多步预测需要把上一轮预测值当作下一轮输入误差会随步数累积这是时序预测的固有特性不是模型坏了。解决用滚动评估模拟真实场景每一步都回填当天的真实观测值再预测下一天。def rolling_eval(model, df, features, start, end): preds, actuals [], [] hist df.loc[:start].copy() for d in df.loc[start:end].index: X hist[features].iloc[-1:].values preds.append(model.predict(X)[0]) actuals.append(df.loc[d, temp]) hist df.loc[:d].copy() return preds, actuals逻辑说明hist在每轮预测后更新为截至当前日的真实数据所以X里的滞后特征全部来自真实观测而不是上一轮的预测值。这个评估方法比一次性递归预测更贴近真实部署场景报告里单独画一条“第1天到第7天误差曲线”让误差随步数增长的趋势透明可见反而比刻意隐藏更可信。6. 把项目收尾成高分交付用置信区间和多步误差说话高分项目与跑通项目的分界线不在于模型有多高级而在于你有没有把预测的不确定性讲清楚。只给一条预测曲线读者分不清它到底靠不靠谱给预测值加上一个误差区间整张图的说服力立刻不一样。做法很简单——用测试集残差的标准差构造±2σ区间约95%的误差会落在这个带内。sigma residual.std() plt.figure(figsize(14, 5)) plt.plot(test.index[:60], y_test[:60], labelactual, linewidth1.6) plt.plot(test.index[:60], pred_xgb[:60], labelprediction, linewidth1.2) plt.fill_between(test.index[:60], pred_xgb[:60] - 2 * sigma, pred_xgb[:60] 2 * sigma, alpha0.2, colororange, label±2σ interval) plt.legend() plt.title(Prediction with Uncertainty Interval) plt.tight_layout() plt.show()参数说明fill_between填充的是预测值上下各两个标准差的区域alpha0.2做成半透明。σ越大说明模型在测试集上的整体误差越分散区带越宽这个带子在答辩时可以直接回答“你的预测到底准不准”这个问题——不发誓准确而是给出误差边界。同时建议把多步预测的误差曲线补上对测试集连续预测1到7天分别计算每一天的MAE画成折线。误差从第1天的1.8℃爬到第7天的4.2℃这个上升趋势本身就是一个有价值的结论说明模型做短期预报可靠、做长期预报需要谨慎。配合这个曲线你的评估就形成了完整闭环数据清洗 → 特征工程 → 模型对比 → 残差分析 → 不确定性量化。我第一次做这个项目时只交了单步预测加R²答辩被追问“未来一周到底准不准”当场答不上来。后来把滚动评估和±2σ区间补上整个项目才从“跑通了”变成“讲清楚了”。这里还建议把项目按目录组织好data放原始数据和清洗后的数据notebooks放探索和建模的脚本results放预测结果和图片这样别人拿到源码能直接复现每一步。希望你拿到这份源码后把最后这几步也做完真正把天气预测和可视化的链路打通。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑