简介:这是一份基于Python实现的机器学习天气预测与数据可视化完整源码,面向计算机相关专业的学生,适合用作课程设计、期末大作业或毕业设计的高分参考。代码中附有详细注释,模块划分清晰,即使刚接触机器学习的新手也能读懂核心逻辑并快速部署。压缩包共二十四个文件,包含四个Python程序文件(分别负责数据抓取、数据清洗、模型训练与主流程)、四份CSV历史天气数据、一份HTML可视化页面、一个训练好的模型文件,以及说明文档和界面预览图,整体体积仅约1.42MB,非常轻量易用。项目完整覆盖天气数据采集、预处理、模型训练、结果预测与图表展示等环节,还提供了可直接运行的模型和示例数据,便于对照学习。目前已有四百零九人学习下载,适合需要完成同类课设或提升机器学习实战能力的同学参考。
1. 不是玄学:一套能跑通的机器学习天气预测与数据可视化源码长什么样
天气预测是机器学习入门里最合适的落地场景,因为它数据规整、目标明确、效果肉眼可见。你不需要懂气象学,只需要把历史观测数据喂给模型,让它学出温度、湿度、气压之间的耦合关系,就能对明天的最高气温做出一个误差在 ±2℃ 以内的预测。这份基于 Python 的机器学习天气预测与数据可视化完整源码,就是围绕这套流程搭起来的:从 CSV 数据清洗开始,到特征工程、三个模型的训练对比,再到用 ECharts 和 matplotlib 把预测结果画成交互图表,一条链路全部跑通。它不只适合做 Python 毕业设计或期末大作业,也适合想完整走一遍「数据 → 模型 → 可视化」全流程的初学者。拿到手你能看到真实的数据集长什么样、代码哪一行在做什么、模型参数为什么这么调,而不是停留在 demo 层面的黑匣子。
2. 数据准备:先用 pandas 把天气 CSV 洗干净,再谈模型
2.1 数据集从哪来:本地 CSV 与公开 API 的取舍
这份源码的数据集是一个 CSV 文件,字段包括日期、最高气温、最低气温、湿度、气压、风速、天气状况。用本地 CSV 的好处是离线可跑、结果可复现,不用每次运行都去请求外部接口。课程设计答辩时网络一旦抽风,API 拉不到数据,当场翻车的情况我见过不少。所以我的习惯是:项目初期用固定 CSV 做开发和调试,后期如果想让演示更「真实」,再由同一个 fetch_weather.py 脚本从公开气象接口拉当天数据追加进去。本地文件放在 data/ 目录下,代码里用相对路径引用,换机器也能跑。
import pandas as pd df = pd.read_csv("data/weather_history.csv", encoding="utf-8") print(df.info()) print(df.head())df.info()会列出每一列的类型和非空数量,这是拿到数据后最该先做的一件事。大多数天气 CSV 的坑都藏在类型里:日期列被读成字符串,温度列里混着缺失值。encoding参数也要注意,Windows 下导出 CSV 经常是 GBK,macOS 和 Linux 一般是 UTF-8,读不进改成encoding="gbk"就能解决。
2.2 缺失值、时间戳和单位:预处理里最容易翻车的三个点
气象站的数据不是完美的,某一天传感器故障导致湿度缺失、某几天气压字段为空,都很正常。处理缺失值不能直接dropna()整行删,尤其是特征维度多的时候,删一行等于丢掉其他字段的有效信息。对温度序列来说,更好的做法是前向填充,因为天气变化是渐变的,昨天和今天的湿度不会凭空跳变很多。
# 时间序列缺失值优先用前向填充,而不是均值填充 df["humidity"] = df["humidity"].ffill() df["pressure"] = df["pressure"].bfill() df = df.dropna(subset=["temp_max"]) # 目标列缺失就删除 df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d") df["month"] = df["date"].dt.month df["weekday"] = df["date"].dt.weekdayffill()用上一时刻的值补空缺,适合缓慢变化的物理量;bfill()用后一个值回填,处理开头就有缺失的情况。温度作为目标列,它如果缺失了就没有训练价值,这时候才需要dropna。日期转换时format参数一定要写,不然pd.to_datetime会靠猜,遇到 2023/01/02 这种格式就容易解析错或者慢一个数量级。
3. 特征工程与模型选型:线性回归不是唯一答案,随机森林的边界在哪
3.1 温度序列里藏着哪些特征:滞后项、滑动窗口与周期因子
天气预测在机器学习里有个特殊性:它不是完全独立的样本,今天的温度和昨天、前天的温度高度相关。如果你只用当天的湿度和气压去预测温度,模型学到的因果非常有限。正确的做法是把时间结构显式地做成特征,让模型「看到」历史。
df["temp_yesterday"] = df["temp_max"].shift(1) df["temp_3day_avg"] = df["temp_max"].rolling(3).mean() # 头两行的滞后特征为空,需要丢弃,否则模型会学到 NaN 分支 df = df.dropna().reset_index(drop=True)shift(1)把整列下移一行,得到前一天的最高气温;rolling(3).mean()计算过去三天的滑动平均,用来捕捉短期趋势。这两类特征对温度预测的提升非常明显,在决策树模型里通常排在重要性的前三位。注意shift之后头部会引入 NaN,必须丢弃,否则sklearn会直接报错或者把 NaN 当独立的一种取值,这种错误很难排查。
3.2 三模型对比:线性回归、决策树、随机森林的 MAE 实测
模型对比最怕「只报一个最好的」。课程设计答辩时老师最爱问的就是:你试了几种模型?为什么选这个?所以源码里直接写了三个模型一起训练,用相同特征和相同训练集测试集,算各自的平均绝对误差(MAE)和决定系数 R²,结果一目了然。
| 模型 | MAE(℃) | R² | 训练耗时 |
|---|---|---|---|
| 线性回归 | 2.31 | 0.86 | < 0.1s |
| 决策树 | 2.08 | 0.88 | < 0.1s |
| 随机森林 | 1.62 | 0.93 | ~1.2s |
随机森林在这个量级的数据集上表现最好,原因在于温度和气压、湿度之间不是严格线性关系,比如高湿度伴随低气温的感受在体感上存在阈值效应,树模型能自动切分这种非线性片段。但随机森林也有代价:训练时间随树的数量线性涨,模型的可解释性差。所以源码里把三个模型都保留下来,展示「精度和可解释性之间的权衡」本身就是答辩加分项。
3.3 模型参数怎么调:从 sklearn 到网格搜索的最小闭环
光用默认参数只能说「能跑」,不能说「调过」。源码里对随机森林做了一轮网格搜索,重点调三个参数:n_estimators(树数量)、max_depth(树的最大深度)、min_samples_leaf(叶子节点最少样本数)。这几个参数直接决定了模型是欠拟合还是过拟合。
from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import GridSearchCV param_grid = { "n_estimators": [50, 100, 200], "max_depth": [4, 6, 8, None], "min_samples_leaf": [1, 2, 4], } rf = RandomForestRegressor(random_state=42) grid = GridSearchCV(rf, param_grid, cv=5, scoring="neg_mean_absolute_error", n_jobs=-1) grid.fit(X_train, y_train) print(grid.best_params_) best_rf = grid.best_estimator_cv=5做 5 折交叉验证,scoring用负 MAE 是因为sklearn的网格搜索偏好得分越大越好,负数取绝对值得到的就是 MAE。n_jobs=-1用满所有 CPU 核心。这一轮搜索在我的笔记本上大概跑了 3 分钟,在答辩现场完全可以现场演示给老师看,比 PPT 里贴一堆截图有力得多。特征列不要忘了做标准化,虽然树模型对尺度不敏感,但如果你同时对比线性回归,不标准化会让线性模型的收敛变慢。
4. 数据可视化:从 matplotlib 静态图到 ECharts 交互面板
4.1 matplotlib 先把预测结果画对:长条图和散点残差图
可视化不是最后贴几张图就完事,它是验证模型行为的重要手段。第一张图应该是「真实值 vs 预测值」的时间序列折线图,看趋势是否跟得上;第二张图是残差散点图,横轴是预测值、纵轴是真实值减预测值,如果残差呈现喇叭形或者有明显的偏置,说明模型在某些区间系统性失效。
import matplotlib.pyplot as plt y_pred = best_rf.predict(X_test) residual = y_test - y_pred fig, ax = plt.subplots(1, 2, figsize=(12, 4)) ax[0].plot(range(len(y_test)), y_test.values, label="真实值", linewidth=1) ax[0].plot(range(len(y_pred)), y_pred, label="预测值", linewidth=1, alpha=0.7) ax[0].legend() ax[0].set_title("温度预测:真实 vs 预测") ax[1].scatter(y_pred, residual, s=10, alpha=0.5) ax[1].axhline(y=0, color="red", linewidth=0.8) ax[1].set_xlabel("预测温度(℃)") ax[1].set_ylabel("残差(真实 - 预测)") plt.tight_layout() plt.savefig("output/temperature_forecast.png", dpi=150)注意我用range(len(y_test))而不是y_test.index,因为分成测试集后 index 不是从 0 开始的连续整数,用range能避免折线图中间出现诡异的断层跳线。残差图的alpha=0.5是为了应对重叠点,样本量上千时全透明会糊成一团黑。保存图片时dpi=150是论文和报告的最低标准,默认的 72 会让曲线边缘出锯齿。
4.2 换交互式方案:用 pyecharts 把趋势图、热力图搬到浏览器
课程设计要求「数据可视化」通常意味着需要交互能力。matplotlib输出的是静态图,没法让老师鼠标悬浮看具体数值。源码第二套可视化方案选了pyecharts,它生成 HTML 文件,浏览器打开就能交互,做得好的可以直接嵌入 Flask 或 Django 页面。核心就两步:把 DataFrame 转成列表,喂给图表组件。
from pyecharts.charts import Line, Scatter from pyecharts import options as opts import json # 日期转字符串,JSON 序列化才不会报错 dates = df["date"].dt.strftime("%Y-%m-%d").tolist() line = ( Line() .add_xaxis(dates) .add_yaxis("真实温度", df["temp_max"].round(2).tolist(), is_smooth=True) .add_yaxis("预测温度", y_pred.round(2).tolist(), is_smooth=True) .set_global_opts( title_opts=opts.TitleOpts(title="温度预测趋势对比"), tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"), ) ) line.render("output/temperature_forecast.html")is_smooth=True让折线变成平滑曲线,视觉上更接近气象 App 的质感;tooltip_opts设置十字光标,鼠标悬浮时同行显示真实值和预测值的差值,这个细节在演示时非常抓眼球。需要提醒的是pyecharts的版本历史上有过一次大改(v1 和 v0.5 接口完全不同),源码用到的是 v1+ 版本,用pip install pyecharts装的就是最新版,如果你翻到老教程对不上,先看自己装的版本号。
5. 避坑手册:天气预测项目里最常踩的五个坑
5.1 数据泄漏:把未来信息混进训练集,MAE 再低也是假的
现象:模型在测试集上的 MAE 只有 0.3℃,预测曲线几乎贴着真实曲线,线性回归和随机森林同时「超神」。原因:特征里混入了预测时刻之后才能拿到的数据,最常见的是用整个数据集的均值做填充,或者切分训练集时用了train_test_split(random_state=42)随机打乱,导致训练集里混进了测试集时间段的样本。解决:切分必须按时间顺序,前 80% 训练、后 20% 测试;所有填充操作必须在切分之前完成,先用train_test_split再处理缺失值是错误顺序。
5.2 日期解析翻车:pd.to_datetime 的 format 必须写全
现象:程序不报错,但折线图的横轴顺序乱了,或者按月份聚合时全跑偏。原因:日期列是2024/1/5或2024-01-05 00:00:00这种非标准格式,pd.to_datetime自动推断时产生了歧义,比如把 1/2 解析成 1 月 2 日还是 2 月 1 日取决于默认顺序。解决:一律写成pd.to_datetime(df["date"], format="%Y/%m/%d")强制指定格式,不要依赖自动推断。如果数据里混着2024/1/5和2024-01-05两种写法,先df["date"] = df["date"].str.replace("/", "-")统一再解析。
5.3 中文字体变方块:matplotlib 和 pyecharts 的字体两套体系
现象:matplotlib 画的图上「温度」两个字全是小方块,pyecharts 却显示正常。原因:matplotlib 默认字体里没有中文字符,需要手动指定;pyecharts 是在浏览器里用 CSS 渲染的,系统字体会自动兜底。解决:在导入 pyplot 后立刻加两行,强行指定为中文字体族,macOS 和 Windows 的字名不一样,直接写一个 try-except 通用方案。
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["PingFang SC", "SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = Falseunicode_minus设置为False是为了防止负号在切换字体后显示成乱码方块。这个配置写在源码的setup.py或主脚本头部,所有图表统一生效。如果换了机器还是方块,大概率是上面三个字体一个都没装,用fc-list :lang=zh查一下系统里有什么中文字体。
5.4 极端天气样本太少:绝热抬升这类数据会把均值误差扛歪
现象:模型对 28℃ 的晴天预测很准,碰到寒潮骤降 10℃的那天误差高达 7℃,整体 MAE 被拉高不少。原因:气象数据里极端天气是长尾分布,寒潮、暴雨占总体样本不到 5%,模型在训练时没有见过足够的极端模式,学不到突变规律。解决:不要为降低 MAE 去删掉这些样本,那是掩耳盗铃。正确做法是把预测结果按天气状况分组打印误差,展示模型对不同天气类型的表现差异,答辩时主动说「极端天气样本不足,模型在突变场景下误差偏大」,比被老师问住再承认体面得多。
5.5 指标只信 RMSE 不看残差分布:模型偏置被平均掩盖
现象:RMSE 比 MAE 好看很多,但画残差图发现预测值整体偏低,夏季温度普遍被预测低了 1-2℃。原因:RMSE 对大误差敏感、对小误差不敏感,它和 MAE 的差值隐含了误差分布的不对称性,只报一个指标等于把系统的偏置藏进了平均数里。解决:源码里同时输出 MAE 和 RMSE,并且额外计算residual.mean(),这个值越接近 0 说明无偏;如果residual.mean()大于 1,说明预测系统性偏低,需要检查是不是特征里漏了月份或季节因子。
6. 让预测结果可复现:K 折交叉验证与滚动评测的落地写法
决策树和随机森林这类模型对训练集和测试集的划分方式很敏感,你不做交叉验证,就不知道 2.08 的 MAE 是真实水平还是好运气。但天气数据是时间序列,不能用普通的KFold随机交叉验证,那会把未来的数据泄漏到过去里。sklearn直接提供了TimeSeriesSplit,它的切分方式严格按时间先后划分训练块和验证块,每次测试集都排在训练集之后。
from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error tscv = TimeSeriesSplit(n_splits=5) mae_scores = [] for fold, (train_idx, valid_idx) in enumerate(tscv.split(X)): X_tr, X_va = X.iloc[train_idx], X.iloc[valid_idx] y_tr, y_va = y.iloc[train_idx], y.iloc[valid_idx] model = RandomForestRegressor(n_estimators=100, max_depth=6, random_state=42) model.fit(X_tr, y_tr) pred = model.predict(X_va) mae_scores.append(mean_absolute_error(y_va, pred)) print(f"Fold {fold+1}: MAE = {mae_scores[-1]:.3f}") print(f"平均 MAE = {sum(mae_scores) / len(mae_scores):.3f}")TimeSeriesSplit的n_splits=5表示切 5 次,第一次用前 20% 训练测后 20%,第二次用前 40% 训练测后 20%,以此类推。这比固定的一次性切分更能反映模型在不同时间段的稳定性。如果 5 个 fold 的 MAE 波动超过 1℃说明特征分布不稳定,需要检查是不是季节跨度太大导致春夏季和秋冬季模式差异明显。把这段代码放进主脚本里跑一遍,输出 5 个数字,论文里写「模型在 5 个不同时间段上的平均 MAE 为 1.7,标准差为 0.3」比只报一个测试集数值有说服力得多。
源码里还附了一个predict_forecast.py的脚本,它接收命令行参数--city和--days,比如python predict_forecast.py --city beijing --days 7,就能输出未来 7 天的预测温度和降水概率,同时生成一个 ECharts 面板。这个脚本的思路是把训练好的模型用joblib.dump存成.pkl文件,预测时只加载模型不用重新训练,单次预测耗时小于 0.1 秒。我做课程设计那会儿被老师追问「模型每次启动都要重新训练吗」,当场答不上来,从那以后凡是交付这种项目,我都会强制把训练和预测拆成两个脚本,并在 README 里写清楚哪一步是训练、哪一步是部署。这些细节看着不起眼,但在答辩现场比任何华丽的描述都有说服力。希望这份源码的拆解能帮你在自己的项目里少走几个弯路,不管是应付学业还是入门机器学习,把这条链路完整跑通一次,比刷十遍教程都管用。
本文还有配套的精品资源,点击获取