
简介基于Python机器学习ML的天气预测与可视化项目源码及配套全部数据主要面向计算机相关专业正在做课程设计、期末大作业的学生也适合需要完整项目实战练习的初级学习者。项目覆盖数据抓取、清洗、特征处理、模型训练到可视化展示的完整流程可帮助快速理解机器学习落地路径也能直接用于毕设或课设的代码演示与扩展。压缩包共24个文件以CSV数据文件、Python脚本、JPG结果图为主另有Markdown说明文档、pickle模型文件以及HTML可视化页面从数据到模型再到展示均有对应模块。资源包大小约1.42MB下载即用已经严格调试保证可运行。当前已有249人学习下载适合希望快速获取可运行工程、并在此基础上进行功能优化和数据替换的读者。1. 拿到「95 分大作业」zip先别急着跑先看它是否闭环拿到基于 python 机器学习(ml) 的天气预测和天气可视化源码全部数据95分以上大作业.zip第一反应别是搓手期待而是先检查三样东西数据是不是完整、代码是不是真能跑、可视化是不是能证明模型有效。以我帮人调过不少课程设计项目的经验看这种压缩包的真正价值不在那个「95 分」而在于它是否具备从原始数据到建模再到图表验证的完整闭环。很多人下载同类源码后直接跑一下就交了结果换数据、改参数立刻翻车。这篇笔记就按这条闭环展开数据怎么整理、特征怎么造、模型怎么选、可视化怎么做以及最容易被扣分的几个隐藏坑。适合正在做天气预测大作业、或者想快速搭一个机器学习落地 demo 的人照着做。2. 天气数据与特征工程为什么同一个 CSV有人能做出 95 分2.1 先认字段日级别天气预测最容易起步天气预测类课程设计最常见的数据载体是一份日级别的历史 CSV一行代表一天。字段一般包括日期date、当天最高气温tmax、最低气温tmin、天气现象weather晴/雨/阴这种文本、湿度humidity、气压pressure、风向风力wind_dir、wind_level。如果压缩包里正好是这种结构开局就很顺如果给的是逐小时观测记录你需要先按天聚合再把最高温、最低温这些目标列提炼出来。常见做法是先写几行代码确认数据到底长什么样别急着建模import pandas as pd df pd.read_csv( weather.csv, parse_dates[date], # 解析成 datetime后面才能做时间切分 encodingutf-8 ) print(df.shape) print(df.head()) print(df.dtypes)parse_dates这一步比想象中重要。很多 CSV 里的日期是字符串不转成 datetime后面做时间切分、提取季节特征都会处处碰壁。df.shape先看规模如果数据不足 300 行模型几乎没有泛化能力你应该优先去找更长年份的数据而不是在调参上耗时间。预测目标也最好先定下来。常见做法是预测「明天最高气温 tmax」连续值回归评分直观报告好写预测「是否下雨」则变成二分类适合想换口味的人但展示时多一张混淆矩阵更好。我这里按回归任务往下讲分类版本只需要把评估指标换成准确率和 F1。假如压缩包里的数据不多或者字段不全常见做法是去公共气象历史数据网站按城市导出多年的日观测记录清洗成上面这种 CSV。注意别只下一年的数据通常三年起步、五年更稳训练样本越足后面模型误差才压得下来。拿到别人的源码也不要急着跑先确认它的数据目录下是不是有完整 CSV如果只有代码没有数据跑通后换上自己的数据验证一次才算真正吃透这套方案。2.2 缺失值处理fillna(0)是天气任务里的大忌新手面对温度、湿度、气压里的 NaN第一反应往往是fillna(0)这在天气任务里是灾难。气温出现 0℃ 并不极端但它不是合理的中位数替代会让模型在某些样本上严重失真。更稳的顺序是先看缺失率再按时间属性决定填充策略。# 1. 缺失率检测 miss df.isnull().mean() print(miss[miss 0]) # 2. 先保证行顺序插值才不会乱 df df.sort_values(date).reset_index(dropTrue) # 3. 连续字段按时间插值 df[tmax] df[tmax].interpolate(methodtime) df[humidity] df[humidity].interpolate(methodtime) df[pressure] df[pressure].interpolate(methodtime) # 4. 类别字段用前一天的值向前填充 df[weather] df[weather].ffill()interpolate(methodtime)会按时间间隔计算缺失值比默认的线性插值更贴合气温日变化的规律。ffill()对类别字段是合理的默认选择昨天晴、今天缺很大程度上就是「维持原状」。这里还要额外检查一类脏数据tmax 小于 tmin 的记录多半是录入错误直接剔除比让模型硬学更安全。湿度超过 100、气压跑出正常范围也是类似情况用布尔过滤比用「平均化」处理更干净# 过滤明显异常最高温低于最低温的记录直接去掉 df df[df[tmax] df[tmin]].copy() # 过滤湿度越界如果原始数据有湿度字段 df df[(df[humidity] 0) (df[humidity] 100)].copy()提示缺失率超过 40% 的字段别硬留。课程设计看的是你「有没有处理缺失值」这个动作不是一个坏字段被你硬救回来没有把握就删列写报告时还能强调你做了字段筛选。2.3 特征工程给模型一双看得懂「季节」的眼睛原始 CSV 里日期只是一个字段但模型不理解「5 月应该比 1 月热」。我们需要把日期拆成数值特征让模型能找到季节规律。这个环节是拉开分数差距的关键。# 从 date 里提取三个时序特征 df[dayofyear] df[date].dt.dayofyear # 一年中的第几天范围 1~365 df[month] df[date].dt.month # 月份1~12 df[weekday] df[date].dt.weekday # 星期几0~6 # 特征列 features [dayofyear, month, weekday, humidity, pressure] X df[features] y df[tmax]dayofyear是这类任务里性价比最高的特征它给模型一条连续的季节曲线模型可以从「第 200 天」推断出盛夏。weekday对天气预测的实际贡献有限但它代表你考虑过周期性写报告时不会被挑理。容易踩的坑是直接把「年份」加进特征年份更像时间戳而不是预测因子放进特征后模型容易在训练集和测试集之间学出假趋势。文本字段weather也不能直接进模型。树模型随机森林、GBDT可以直接用整数编码df[weather_code] df[weather].astype(category).cat.codes线性模型则建议做 one-hot。原因在于线性回归假设特征之间的数值大小有意义把晴天编码成 1、雨天编码成 2会让模型误以为雨天比晴天的数值更大而树模型在分裂时只看类别边界整数编码不伤效果。如果想让线性模型更强一点可以把日期循环特征也补上dayofyear是连续递增的12 月 31 日与 1 月 1 日在数值上隔了 364但实际只差一天正弦余弦成对特征能表达这种循环关系import numpy as np df[season_sin] np.sin(2 * np.pi * df[dayofyear] / 365) df[season_cos] np.cos(2 * np.pi * df[dayofyear] / 365) features_all [season_sin, season_cos, month, weekday, humidity, pressure, weather_code]模型训练前还可以跑一次相关性检查看看哪些特征和目标列的线性关系强这个数字写进报告很有说服力corr df[features_all [tmax]].corr()[tmax] print(corr.sort_values())相关性不为零不代表特征有用但相关性接近零且本来就是冗余的字段可以考虑从特征列表移除减少模型过拟合风险。2.4 样本量不够怎么办聚合、补数据与客观预期写这种课程设计经常遇到一个现实约束数据集只有 365 行一年。365 行做日级别预测模型容易过拟合测试集 20% 只剩 73 个样本误差波动很大。应对方法有三条。第一条降低任务粒度把日预测改成月均预测或周均预测用groupby聚合后重新切分样本数虽然更少但噪声也小。第二条拉长数据年限很多公共气象历史数据能导出十年以上拿到后按城市筛选即可。第三条接受现实一年数据不是不能做只是报告里要写明数据规模限制然后重点展示特征工程和可视化能力老师通常不会因为客观数据量而扣分。3. 机器学习建模别急着上深度模型先把 sklearn 的三板斧跑通3.1 回归还是分类先定题再选模型做天气预测拿到数据后第一件事是回看目标列。预测「明天最高温多少度」是回归预测「明天下不下雨」是分类预测「晴、阴、雨、雪」则是多分类。三者评价指标不同报告写法也完全不同。任务例子模型出口报告里最常放的指标回归明天 32℃回归RMSE / MAE二分类明天是否降雨分类概率accuracy / F1多分类晴阴雨雪多个类别概率混淆矩阵大多数天气预测课程设计都是回归任务。回归任务分数最直观模型偏 1 度还是 2 度老师一眼能看懂如果数据里的目标列是「天气现象」这种离散值再切回分类路线也不难只是可视化部分要换成混淆矩阵热力图。课程设计阶段完全不需要上深度学习。天气预测这种低维表格数据sklearn 的模型在性能、训练速度和可解释性上都更合适神经网络对环境配置要求高答辩现场还容易因为依赖装不上而翻车。3.2 先用线性回归打 baseline分数不是越高越好先知道底线建模不要一上来就上随机森林。线性回归是天气预测这类低维表格数据的合理起点它快、可解释能告诉你哪些特征没有用。先跑通 baseline再换集成模型你才知道提升到底来自哪里。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, mean_absolute_error # 按时间切分前 80% 训练后 20% 测试 split_date df[date].quantile(0.8) train_mask df[date] split_date model LinearRegression() model.fit(X[train_mask], y[train_mask]) pred model.predict(X[~train_mask]) print(RMSE: %.2f ℃ % mean_squared_error( y[~train_mask], pred, squaredFalse)) print(MAE: %.2f ℃ % mean_absolute_error(y[~train_mask], pred))这里的要点是不能直接用train_test_split的默认参数因为它默认shuffleTrue会把时间序列随机打乱。天气数据是强时间序列跨越时间的随机抽样等于用 6 月的规律去考 1 月分数虚高到没意义。这也是天气预测大作业里最隐蔽的翻车点后文避坑清单还会再讲一次。split_date df[date].quantile(0.8)的意思是取时间维度上的 80% 分位点作为切分边界之前训练、之后测试。用 quantile 而不是写死年份换数据集时不用改代码。RMSE 和 MAE 的区别也要心里有数MAE 是平均绝对误差直观RMSE 因为有平方项对「大幅偏错」的样本更敏感。同一个模型RMSE 通常大于 MAE两个都报老师会认为你理解评价指标的含义。3.3 随机森林三个参数足够撑起成绩线性回归跑通后换随机森林通常能把 RMSE 再压下去一截代码改动也不大。天气数据特征少、样本量不大随机森林完全够用不一定要上更重的方案。from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators200, # 树的数量100~300 之间收益最大 max_depth8, # 限制树深防止背下个别日期 min_samples_leaf3, # 叶子至少 3 个样本给预测做平滑 random_state42, # 固定随机种子保证答辩现场可复现 n_jobs-1, # 用满 CPU 内核提高训练速度 ) rf.fit(X[train_mask], y[train_mask]) pred_rf rf.predict(X[~train_mask]) print(RF RMSE: %.2f ℃ % mean_squared_error( y[~train_mask], pred_rf, squaredFalse))max_depth8在特征只有 5 到 8 个的天气任务上树深 8 已经足够表达非线性关系再深就容易把特殊日期的噪声记下来训练集误差很低、测试集误差反而升高。min_samples_leaf3等价于对叶子节点的输出做一个小平滑能明显压低预测方差。random_state42不是随手写的树模型初始化有随机性不固定的话你实验报告里写的 RMSE 和答辩现场跑出来的 RMSE 可能差一截老师会直接质疑报告真实性。如果时间和机器允许还可以换GradientBoostingRegressor通常比随机森林略强但它对学习率敏感训练也慢。课程设计阶段我的意见是先让随机森林达到一个稳定数字有余力再试提升树不要一开始就在两个模型间反复横跳。3.4 有时间再调参网格搜索只扫训练集如果随机森林的分数已经稳定下一步可以做一个轻量级网格搜索把参数选择从「拍脑袋」变成「有依据」。from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200], max_depth: [6, 8, 10], min_samples_leaf: [2, 3, 5], } gs GridSearchCV( RandomForestRegressor(random_state42), param_grid, scoringneg_mean_squared_error, cv3, n_jobs-1, ) gs.fit(X[train_mask], y[train_mask]) print(gs.best_params_)注意这里有个时序数据的细节GridSearchCV默认会用随机交叉验证对时间序列并不合适。课程设计阶段更安全的做法是只把网格搜索作用在已经切好的训练集内部比如cv3但保持shuffleFalse的自定义切分或者干脆手动列出几个参数组合跑完对比就行。网格搜索的价值在于证明你调过参而不是追求绝对最优参数。4. 天气可视化三张图让老师一眼看出你「真的做过」4.1 真实值 vs 预测值曲线用颜色标注偏差方向可视化不是堆图而是给模型可信度做证据。第一张图永远是测试集上的真实温度与预测温度对比曲线。import matplotlib.pyplot as plt # 中文字体设置一套 rcParams 同时解决中文和负号 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, WenQuanYi Zen Hei] plt.rcParams[axes.unicode_minus] False test_df df[~train_mask].copy() test_df[预测] pred_rf plt.figure(figsize(12, 5)) plt.plot(test_df[date], test_df[tmax], label真实最高温, linewidth1.2) plt.plot(test_df[date], test_df[预测], label模型预测, linewidth1.2) # 预测偏高的区域标浅红偏低的区间自然留白 plt.fill_between( test_df[date], test_df[tmax], test_df[预测], where(test_df[预测] test_df[tmax]), colorred, alpha0.15, label预测偏高 ) plt.legend() plt.ylabel(温度℃) plt.tight_layout() plt.savefig(temperature_fit.png, dpi200)fill_between的where参数接收一个布尔数组把预测高于真实值的区域涂出来。这张图的意义在于如果红色区域集中在冬天说明模型对冷空气过程把握不住如果集中在夏天则可能是对极端高温的低估。图不在于多在于能推动你下一步分析。4.2 误差直方图看看模型平均偏几度折线图看走势直方图看分布。误差直方图能快速回答「模型到底是偶尔大错还是稳定小错」。residual test_df[tmax] - test_df[预测] # 正值 预测偏低 plt.figure(figsize(8, 4)) plt.hist(residual, bins30, edgecolorwhite, color#4C72B0) plt.axvline(0, colorred, linestyle--, linewidth1.5) plt.xlabel(真实值 - 预测值℃) plt.ylabel(天数) plt.tight_layout() plt.savefig(residual_hist.png, dpi200)如果直方图中心明显右移说明模型系统性地低估了温度如果直方图两端拖出长尾说明极端天气样本是主要失分来源。绝大多数天气预测报告里这一页是老师最愿意看的它在证明你在看模型的失败模式而不只是报一个漂亮数字。4.3 特征重要性条形图给特征选择一个实锤树模型自带feature_importances_画成水平条形图能一眼看出模型主要依赖哪些特征。importance pd.Series( rf.feature_importances_, indexfeatures ).sort_values() plt.figure(figsize(8, 4)) importance.plot.barh() plt.xlabel(重要性) plt.tight_layout() plt.savefig(feature_importance.png, dpi200)如果dayofyear排第一说明季节因素主导温度变化这个结果符合常识报告里很好解释如果pressure排在后面也不用紧张说明该数据集中气压对最高温的直接解释力弱。这张图还有一个隐藏用途如果某个你本以为很重要的特征贡献接近 0它往往提示你数据里有字段质量或编码问题回去检查编码方式。4.4 年度温度热力图一眼看全全年规律如果数据有多个年份可以做一张按「年份 × 月份」聚合的月均最高温热力图把季节性和年际差异摊在一张图里。heat_data df.pivot_table( indexdf[date].dt.year, columnsdf[date].dt.month, valuestmax, aggfuncmean ) plt.figure(figsize(10, 4)) plt.imshow(heat_data.values, aspectauto, cmapYlOrRd) plt.colorbar(label月均最高温℃) plt.xticks(range(12), [f{i}月 for i in range(1, 13)]) plt.yticks(range(len(heat_data.index)), heat_data.index) plt.tight_layout() plt.savefig(temperature_heatmap.png, dpi200)pivot_table在这里把数据重排成二维矩阵imshow直接渲染成热力图。图本身没有直接展示模型效果但它放在报告「数据探索」部分能证明你对数据做过结构化的观察这也是高分报告里常见的细节。5. 避坑清单天气预测大作业最常见的 5 个翻车现场5.1 时间序列被随机切分成绩虚高不是真本事现象训练时 RMSE 很漂亮报告图表也好看但换一段新日期数据预测立刻崩。原因用了train_test_split(X, y, test_size0.2)且没关shuffle测试集日期被打乱相当于模型在训练时已经见过测试集「邻居日期」的气温规律成绩虚高。解决按日期边界手工切分——训练集 2018 到 2022 年测试集 2023 年整年或者用下一章的滚动验证。5.2 天气现象文本直接塞进模型现象fit时报could not convert string to float或者转成整数后线性模型系数乱到没法解释。原因sklearn 不接受字符串特征转成整数后线性模型会把「晴天1、雨天2」理解为数值大小关系产生错误推断。解决树模型用astype(category).cat.codes线性模型用pd.get_dummies(..., drop_firstTrue)编码方式与模型匹配。5.3 用同一年数据「预测」同一年现象报告写测试集是 2023 年但训练集也是从 2023 年 1 月到 12 月随机抽的模型成绩看起来很高逻辑上完全站不住。原因本质上和 5.1 同源都是时序泄漏只是表现形态不同。解决严格按时间截止做切分例如训练 2018 到 2022测试只用 2023 全年每次切分前先看一眼日期范围确认训练集时间末尾早于测试集开头。5.4 中文标签全部变方块现象图里所有中文标签都是□□□负号显示成小方块。原因matplotlib 默认字体不含中文字形Windows 上最常见。解决绘图脚本一开头就设置plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, WenQuanYi Zen Hei]并加plt.rcParams[axes.unicode_minus] False保存成图片时用dpi200分辨率够放进报告里。5.5 没固定随机种子导致答辩现场翻车现象报告里 RMSE 是 1.80现场演示跑出 2.31老师开始质疑数据来源。原因随机森林、GBDT 内部有随机初始化模型每次运行结果不同train_test_split的随机打乱也会改变结果。解决所有涉及随机性的模型、切分步骤都固定同一个random_state42。这是「可复现」最直观的考核点也是实验报告和演示不一致最常见的幕后黑手。提示避坑清单里的 5.1 和 5.3 是同一类问题很多高分源码包里其实已经写好了按时间切分的逻辑但你没有读懂就直接跑最容易中招。拿到别人的源码第一件事是看懂它的训练集和测试集是怎么切的再谈模型。6. 进阶技巧滚动验证与「人话化」指标——最后 5 分从这里抠6.1 滚动验证让评估结果更接近真实业务固定一次切分只有一个分数运气成分没被排除。滚动验证是这类大作业里成本最低又最能体现工程感的升级对多个年份逐一做「用之前年份训练、预测该年份」的循环。for test_year in [2023, 2024]: train df[df[date].dt.year test_year] test df[df[date].dt.year test_year] model_rv RandomForestRegressor(n_estimators100, random_state42) model_rv.fit(train[features], train[tmax]) pred_year model_rv.predict(test[features]) rmse mean_squared_error(test[tmax], pred_year, squaredFalse) print(f{test_year}: RMSE {rmse:.2f} ℃)这段代码的关键在于每年的训练集都只使用该年份之前的数据逐年推进模拟真实部署中「今天预测明天」的感觉。如果 2024 的误差比 2023 大出一截说明模型在你没覆盖的气候事件上泛化不足而不是模型坏了——这个结论写进结课报告比任何调参记录都有说服力。6.2 把指标讲成人话回归任务的报告里写「准确率 95%」是常见错误准确率是分类指标回归应该写误差。把 RMSE 翻译成人话报告会立刻上一个档次RMSE 1.8℃意思是大约 68% 的测试日预测偏差落在 1.8℃ 以内MAE 1.4℃直接说「平均偏 1.4 度」再补一句「80% 的天数误差在 2℃ 以内」老师不用换算就能感受到模型质量。我个人的习惯是交一份实验报告前把代码从头到尾重跑一遍确认图片、RMSE、特征重要性图和报告里写的一致再把random_state固定住。曾见过同学因为没固定随机种子答辩现场跑出的分数和报告差了一倍老师当场质疑模型造假那场面真的很尴尬。固定随机种子这种几十秒就能做完的事是我在这类项目上最想提醒你的血泪经验。希望帮到你。本文还有配套的精品资源点击获取