天气预测机器学习实战:从数据清洗到可视化完整教程

发布时间:2026/9/12 20:42:22
天气预测机器学习实战:从数据清洗到可视化完整教程 简介基于Python的机器学习天气预测与数据可视化项目面向毕业设计、期末大作业与课程设计场景适合具备一定Python基础、希望快速搭建完整预测与可视化流程的读者。压缩包共24个文件包含4个Python源码、4个CSV数据文件、12张图表图像、1个HTML前端页面以及说明文档与模型文件整体仅1.42MB结构清晰便于部署复现。源码配有注释从数据清洗、模型训练到结果可视化均有完整实现涵盖GetData、ProcessData、GetModel、main等关键模块可帮助新手理解天气预测任务的数据流转与建模思路。项目另附天气网展示页面和图表输出便于直接用于汇报展示已有403人学习下载属于导师认可的高分项目适合作为课程设计参考也可在此基础上扩展算法、调整特征或接入实时数据以提升预测表现。1. 为什么天气预测能撑起一个高分项目“天气预测”四个字听起来像气象台的专利但放到机器学习课程项目里它是少有的能同时覆盖回归、分类、时间序列和数据可视化四个知识点的题目。你需要清洗两年日观测记录剔除传感器故障带来的脏数据把日期转换成特征再训练模型预测第二天的最高气温最后把对比结果画成图。这条链路走通数据进、结论出的闭环就成了。选它的另一个理由是汇报友好。评分人不一定懂气象但都能判断“预测 26 度实际 28 度”这个误差是否合理。指标不用解释太久看图就明白了而能“看图说话”正是数据可视化这个题眼的价值。同一份数据特征工程和评测方式不同分数差距可以很大。如果你正在准备课程设计、毕业设计或复试机试这篇博文按“数据管道—建模—可视化—项目收尾”的顺序展开每一章都带代码和参数说明直接对照改成自己的项目即可。2. 构建数据管道用 Python 把天气数据变成训练样本2.1 数据源与目录设计自带数据集再加可选增量脚本标题既然强调“完整源码全部数据”项目交付时就应该把数据作为仓库的一部分同时保留一个更新脚本。这样评委第一次运行不依赖外网想换城市或扩时间范围时又可以随时拉新数据。我一般会在项目里放一个这样的结构weather_project/ ├── data/ │ ├── raw_weather.csv │ └── processed/ # 清洗后的中间结果 ├── scripts/ │ ├── fetch_weather.py # 可选拉取最新观测 │ ├── prepare_data.py # 清洗与特征工程 │ └── train_model.py # 训练与评估 └── outputs/ # 图、模型、指标统一输出这样安排有两个好处评分人只跑 prepare 和 train 就能复现全部结果不需要联网想验证模型对新数据的表现时执行 fetch 脚本更新 raw_weather.csv 即可。数据获取脚本独立还有一个工程上的意义原始数据只读清洗结果输出到 processed后续实验全部从 processed 读取避免反复执行清洗步骤产生不一致。数据获取脚本的常见形态是调公开天气接口。下面这段用 open-meteo 的 archive 接口拉两年逐日气象观测不需要注册 API key适合做课程设计# fetch_weather.py —— 拉取过去两年的逐日观测 import requests import pandas as pd from datetime import datetime, timedelta end datetime.now() start end - timedelta(days730) url https://archive-api.open-meteo.com/v1/archive params { latitude: 39.9042, longitude: 116.4074, start_date: start.strftime(%Y-%m-%d), end_date: end.strftime(%Y-%m-%d), daily: [ temperature_2m_max, temperature_2m_min, precipitation_sum, wind_speed_10m_max, weather_code, ], timezone: Asia/Shanghai, } data requests.get(url, paramsparams, timeout30).json()[daily] df pd.DataFrame(data).rename(columns{time: date}) df.to_csv(data/raw_weather.csv, indexFalse) print(fsaved {len(df)} rows)代码逻辑不复杂但有两个参数容易漏。第一timezone 必须显式指定否则接口默认 UTC中国区域日期整体偏移 8 小时后面做日期特征全部错位。第二训练集必须用 archive 历史归档接口不能用 forecast 预报接口因为预报值是模型要逼近的目标拿它当特征会产生标签泄漏。字段落盘前顺手把 time 重命名为 date能省掉清洗阶段一遍列名映射。拉回来的字段结构大致如下这也是常见气象数据集的最小字段集列名含义是否入模date观测日期转成周期特征temperature_2m_max当日最高气温回归任务标签、分类任务特征temperature_2m_min当日最低气温特征precipitation_sum24 小时累计降雨量特征且是分类标签来源wind_speed_10m_max10 米高度最大风速特征weather_codeWMO 天气现象编码过滤或分组使用最后一行 weather_code 是标准化的天气现象编码晴天为 0降雨从 51 起它直接进模型意义不大更适合做数据筛选条件比如只保留特定天气状态或者派生一个“是否极端天气”的二值特征。2.2 数据清洗缺失值、异常值、重复记录分开处理真实观测数据最常见的三个问题是传感器断传导致整行缺失、异常高温记录、以及同一日期重复存储。清洗顺序建议固定为先去重排序再过滤异常最后补缺失# prepare_data.py —— 清洗与特征衍生 import numpy as np import pandas as pd df pd.read_csv(data/raw_weather.csv) df[date] pd.to_datetime(df[date]) # 1) 按日期去重并保证时间索引连续 df df.drop_duplicates(subsetdate).sort_values(date).reset_index(dropTrue) # 2) 最高气温做物理范围过滤超出范围视为传感器故障 df df[(df[temperature_2m_max] -60) (df[temperature_2m_max] 55)] # 3) 数值列线性插值降水列不做插值 num_cols [temperature_2m_min, temperature_2m_max, wind_speed_10m_max] df[num_cols] df[num_cols].interpolate(limit_directionboth) df[precipitation_sum] df[precipitation_sum].fillna(0)注意降水列的处理逻辑和其他数值列不一样。降水为 0 是有效观测代表晴天不是缺失。如果对降水列做线性插值很可能在连续晴天中间补出一个 2 毫米的小雨污染后续的降雨分类模型所以降水缺失宁可填 0。温度列插值也有前提连续缺失超过 7 天说明设备长时间离线那段数据应该整体舍弃插值会造出虚假的平滑趋势。这个阶段最容易犯的错是“先插值再去重”。如果重复行里有真实值和异常值插值会先把异常值算进邻近点再去重时污染已经扩散。所以顺序必须固定为去重、过滤、插值三步。2.3 特征衍生滞后值、滑动平均与周期编码天气数据是时间序列模型不能只知道“今天”至少要让模型知道“昨天发生了什么”“过去一周大概什么水平”“现在是一年中的哪个季节”。三个常用特征组件分别是滞后特征、滑动平均和周期编码# 在 prepare_data.py 中继续 df[temp_lag_1] df[temperature_2m_max].shift(1) df[temp_ma7] df[temperature_2m_max].rolling(7).mean() day_of_year df[date].dt.dayofyear df[sin_day] np.sin(2 * np.pi * day_of_year / 365.25) df[cos_day] np.cos(2 * np.pi * day_of_year / 365.25) # 分类任务的标签明天是否有降水 df[rain_tomorrow] (df[precipitation_sum].shift(-1) 0.1).astype(int) df df.dropna(subset[temp_lag_1, temp_ma7])shift(1) 取前一天最高气温rolling(7) 取过去一周平均这两个特征对温度预测的贡献通常最大。周期编码用 sin 和 cos 两个维度表示“一年中的第几天”如果只保留 sin12 月 31 日和 1 月 1 日几乎相等没问题但 3 月和 9 月也会出现相同值模型分不清春季和秋季。sin/cos 组合本质上把日期投射到圆上的一个点使得季节距离连续单调。还有一个方向问题。滞后特征用 shift(1)取的是过去数据分类标签 rain_tomorrow 用 shift(-1)取的是未来数据。特征必须全部来自过去标签可以来自未来但两列不能混进同一个特征矩阵。如果误把 shift(-1) 的结果放进 X模型等价于偷看了明天的答案测试集指标会虚高到不真实。2.4 训练集与验证集拆分按时间切不要随机打乱初学机器学习的人最常在这个环节翻车。天气数据带时间顺序随机 train_test_split 会把 1 月的数据分进训练集2 月的数据分进验证集模型学到的其实是对“邻近日期”的记忆而不是泛化能力。正确做法是使用 TimeSeriesSplit或者至少保证验证集时间上晚于训练集from sklearn.model_selection import TimeSeriesSplit X df[[temp_lag_1, temp_ma7, sin_day, cos_day, temperature_2m_min, wind_speed_10m_max]] y df[temperature_2m_max] tscv TimeSeriesSplit(n_splits5) for fold, (train_idx, val_idx) in enumerate(tscv.split(X)): print(ffold {fold 1}: ftrain {train_idx.min()}~{train_idx.max()}, fval {val_idx.min()}~{val_idx.max()})TimeSeriesSplit 的每一折训练集都比上一折多一段数据验证集始终紧跟在训练集之后模拟了“用过去预测未来”的真实场景。后面所有模型评测都基于这个切分方式指标才有报告价值。3. 机器学习建模回归和分类两个任务分别怎么做3.1 先把预测目标定义清楚天气预测在机器学习里不是一个任务而是两个。预测明天最高气温是回归任务输出连续数值预测明天下不下雨是分类任务输出 0 或 1 的概率。两个任务共用同一套特征矩阵只是标签列不同。这个设定对课程设计非常有利一套数据预处理流程能产出两个完整实验报告里的实验对比部分天然就多一个维度。回归任务关注误差的大小分类任务关注排序能力和阈值选择。下面分开建模先做回归再做分类。3.2 回归任务用一个评估函数统一比较模型先写一个统一的交叉验证评估函数后续所有回归模型都走这个入口避免每换一个模型就复制一段评测代码from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error def evaluate_regressor(model, X, y): mae_list [] for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] model.fit(X_train, y_train) pred model.predict(X_val) mae_list.append(mean_absolute_error(y_val, pred)) return np.mean(mae_list) linear make_pipeline(StandardScaler(), LinearRegression()) print(linear MAE:, evaluate_regressor(linear, X, y)) rf RandomForestRegressor( n_estimators200, max_depth8, min_samples_leaf5, random_state42, ) print(random forest MAE:, evaluate_regressor(rf, X, y))选择平均绝对误差 MAE 作为主指标是因为温度预测的误差以摄氏度为衡量单位MAE 含义直白评委一听就懂。RMSE 会放大离群日的惩罚在天气这种噪声较大的场景里容易让模型去迁就极端天气。线性回归在温度预测上的 MAE 通常落在 2 到 3 度随机森林可以压到 2 度以内具体数值和城市气候有关但结构性的结论是一致的树模型对非线性关系和特征交互更友好。随机森林的三个参数需要理解而不是照抄。n_estimators200 在日尺度天气预报上已经足够加到 1000 棵提升有限但训练时间线性增长。max_depth8 限制单棵树深度防止树去记忆某一天的极端气温。min_samples_leaf5 保证每个叶子节点至少 5 个样本让预测结果更平滑避免在寒潮和热浪这种异常日上抖动太剧烈。3.3 分类任务降雨概率预测用 ROC-AUC 而不是准确率降水预测的标签是“明天是否降雨”这类数据通常存在类别不平衡多数城市一年中晴天多于雨天。如果直接看准确率模型全部预测“不下雨”也可能拿到 70% 的准确率但没有任何实用价值。正确做法是用 ROC-AUC 评估排序能力用混淆矩阵观察误报和漏报from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score target df[rain_tomorrow] clf RandomForestClassifier( n_estimators300, max_depth6, min_samples_leaf10, random_state42, ) auc_list [] for train_idx, val_idx in tscv.split(X): model clf model.fit(X.iloc[train_idx], target.iloc[train_idx]) prob model.predict_proba(X.iloc[val_idx])[:, 1] auc_list.append(roc_auc_score(target.iloc[val_idx], prob)) print(rain AUC:, np.mean(auc_list))分类器用 predict_proba 输出降雨概率而不是直接输出 0 或 1因为 AUC 的计算依赖概率排序不需要先定阈值。这种评估方式贴合实际使用场景气象预报通常给出降水概率再由人决定是否带伞。max_depth6 比回归模型更浅原因同样是防止模型记住某些特定年份的偶然天气组合。两个任务的对比可以整理成一张表放进项目报告里比较维度温度回归降雨分类标签类型连续数值二值类别主指标MAEROC-AUC特征要求滞后、均值、周期同上对降水滞后更敏感常见错误插值温度导致预测过平滑类别不平衡下只看准确率3.4 时间序列交叉验证下最容易犯的错用 KFold 替代 TimeSeriesSplit 是时间序列项目里最典型的问题。KFold 随机打乱数据后折叠内部可能同时出现 1 月和 7 月的数据模型在训练时已经“见过”验证集附近的样本分布评估结果偏乐观。对天气这种强自相关的数据偏差尤其明显因为相邻日期的气温高度相似。一个可复现的验证方法是把 KFold 的 MAE 和 TimeSeriesSplit 的 MAE 同时打印出来前者通常会明显优于后者。这个对比本身就可以写进项目报告的“评估方法”章节用来解释你为什么选择按时间切分而非随机切分。4. 数据可视化让真实值和预测值在同一张图里直接对话4.1 静态底图Matplotlib 画预测对比与残差分布建模完成后第一步可视化一定是真实值与预测值的时间序列对比。注意这里要画验证集上的预测而不是训练集上的拟合值否则图上的贴合程度没有说服力import matplotlib.pyplot as plt val_idx list(tscv.split(X))[-1][1] # 取最后一折验证集 X_val X.iloc[val_idx] y_val y.iloc[val_idx] pred rf.fit(X.iloc[:val_idx[0]], y.iloc[:val_idx[0]]).predict(X_val) val_df df.iloc[val_idx].copy() val_df[pred] pred fig, axes plt.subplots(2, 1, figsize(12, 8), sharexTrue) axes[0].plot(val_df[date], val_df[temperature_2m_max], label真实值, linewidth1.5) axes[0].plot(val_df[date], val_df[pred], label预测值, linewidth1.5, alpha0.8) axes[0].set_title(最高气温预测 vs 真实值) axes[0].legend() residual val_df[temperature_2m_max] - val_df[pred] axes[1].hist(residual, bins30, edgecolorwhite) axes[1].set_title(预测残差分布真实值 - 预测值) plt.tight_layout() plt.savefig(outputs/temp_forecast.png, dpi150)残差直方图如果近似以 0 为中心的正态分布说明模型没有系统性偏差。如果整体偏正模型系统性地低估温度偏负则高估。这类信息很难从指标里直接读出来但图上非常直观。两张图上下拼接纵轴自动对齐适合直接放进论文或答辩 PPT。如果季风条件下的系统偏差存在你会看到残差分布虽然对称但方差偏大说明模型没能捕捉部分气候波动。4.2 交互式面板Plotly 做带时间范围选择的气象大屏静态图适合论文交互图适合现场演示。Plotly 的 write_html 能生成一个独立 HTML 文件评分人双击即可打开不需要安装 Jupyter 或额外服务。用两个子图叠加气温和降水把“降水后气温下降”这类现象直接展示出来import plotly.graph_objects as go from plotly.subplots import make_subplots fig make_subplots(specs[[{secondary_y: True}]]) fig.add_trace( go.Scatter(xval_df[date], yval_df[temperature_2m_max], name真实最高温, linedict(color#1f77b4)), secondary_yFalse, ) fig.add_trace( go.Scatter(xval_df[date], yval_df[pred], name预测最高温, linedict(color#ff7f0e, dashdash)), secondary_yFalse, ) fig.add_trace( go.Scatter(xval_df[date], yval_df[precipitation_sum], name日降水量, linedict(color#2ca02c)), secondary_yTrue, ) fig.update_layout(hovermodex unified, title气象预测与观测对比) fig.write_html(outputs/weather_dashboard.html)hovermodex unified 让鼠标划过时同时显示同一时间的温度和降水两个数值。看板里如果还想达到企业级数据可视化大屏的展示效果可以把 Plotly 的图拆分到多个 HTML 区块再用 Flask 或 Streamlit 拼装成整屏布局。原理都一样图表组件化数据对接同一个 DataFrame。4.3 特征重要性让模型决策过程变成答辩素材随机森林自带特征重要性输出这是课程项目里性价比最高的可视化素材。用水平条形图画出来可以直接支撑“模型学到了什么”这段论述importances pd.Series( rf.feature_importances_, indexX.columns, ).sort_values(ascendingTrue) importances.plot.barh(figsize(8, 4)) plt.xlabel(feature importance) plt.tight_layout() plt.savefig(outputs/feature_importance.png, dpi150)在温度预测任务里你通常会发现 temp_lag_1 和 sin_day/cos_day 排在前列这是符合气象常识的昨天的气温是今天气温最强的单变量信号季节周期是第二个强信号。把这些观察到的话写进报告比单纯贴一张图更有说服力。5. 把“高分项目”和普通作业拉开差距的 3 个细节5.1 模型持久化训练一次处处复用很多项目把训练和预测写在同一段脚本里每次运行都重新训练这在演示时很浪费时间也容易出现结果不可复现的问题。规范做法是把训练好的模型和指标落盘from joblib import dump dump(rf, outputs/model_rf.joblib) dump({mae: np.mean(mae_list), features: list(X.columns)}, outputs/metrics_rf.json)模型文件加指标文件一起输出后再做预测或写演示页面时只加载不重训。joblib 对 numpy 数组序列化的效率比 pickle 高是 scikit-learn 生态里的常用做法。指标单独存 JSON 格式方便后续在不同模型间做横向对比也可以让自动化评分程序直接读取。5.2 一个入口脚本跑完整条流程加分的关键一步是把 fetch、prepare、train 封装成一个带参数的入口。用 argparse 控制模型选择和是否重新拉取数据# train_model.py import argparse parser argparse.ArgumentParser() parser.add_argument(--model, defaultrf, choices[rf, linear]) parser.add_argument(--city, defaultbeijing) args parser.parse_args()这样整个项目就可以用一行命令复现全部实验。课程设计答辩时现场演示“python train_model.py --model rf --city beijing”然后展示输出结果比一步步点 Jupyter 单元格专业得多也给评分人留下工程规范的印象。5.3 月度误差分析把模型失效场景找出来比较推荐的收尾技巧是对预测误差做月度聚合输出一张误差分布表。按月份分组计算平均绝对误差定位模型在哪些月份表现差、为什么差val_df[month] val_df[date].dt.month monthly_mae ( (val_df[temperature_2m_max] - val_df[pred]) .abs() .groupby(val_df[month]) .mean() ) monthly_mae.to_csv(outputs/monthly_mae.csv)夏季雷阵雨集中、春秋两季气温波动大通常会造成 MAE 明显抬升。这张表配合特征重要性图能支撑起报告里“局限性与改进方向”整节内容。如果误差集中在换季月份说明周期特征还不足以描述天气的突变下一步可以考虑加入气压或露点温度特征这也为论文留出了明确的后续工作空间。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询