简介:基于Python机器学习(ML)的天气预测与可视化完整项目,面向计算机相关专业做课程设计或期末大作业的学生,也适合需要项目实战练习的入门学习者。项目围绕真实天气数据,覆盖数据获取、预处理、特征处理、模型训练、预测评估和可视化展示等环节,可直接作为高分课设模板参考。资源共24个文件,核心包含4个Python源码、4个CSV数据集、1个训练好的pkl模型、1个HTML可视化页面及12张效果截图,压缩包仅1.42MB;Python脚本分别承担数据抓取、数据处理、模型训练与主程序调用,CSV提供训练与测试数据,HTML页面用于呈现可视化结果。当前已有248人学习下载,项目经严格调试,下载即可运行,并附带readme说明文档,便于快速了解运行流程。无论用于课程提交还是巩固ML实战能力,这份结构清晰的项目包都能大幅节省从零搭建时间。
1. 天气预测大作业想拿95分,先把“预测什么”想清楚
如果你手里刚拿到一份“基于Python机器学习的天气预测和天气可视化源码+全部数据”,第一件事不是急着跑代码,而是先想清楚:这份作业到底要预测什么。常见的目标变量有三类——最高气温、最低气温、或者次日的平均气温。多数高分大作业选的是“最高气温”,因为它的周期性最强、噪声最小,机器学习模型最容易拟合出漂亮的曲线,评分老师一眼就能看到预测值和真实值高度重合,分数自然就上去了。
另一个反直觉的结论是:这个项目的得分上限不取决于你用了多先进的模型,而取决于三个细节——特征工程做没做、时间序列有没有正确划分、可视化图表有没有让老师看懂你的工作量。神经网络在这个任务上往往打不过堆了滞后特征的随机森林,而这两者的代码量差距是十倍。这篇文章会把这条路径完整走一遍:从数据清洗、特征构造、模型选型,到可视化出图,再到那些能让成绩从85掉到70的隐藏坑。适合正在做课程设计、毕业设计,或者想用一份完整项目练手Python机器学习的读者。
2. 先拆数据和特征工程:天气预测的本质是回归,不是分类
2.1 为什么天气预测必须用回归而不是分类
很多新手拿到天气数据后,第一反应是问“要不要把天气分成晴、阴、雨,然后做分类”。这是一个方向性错误。天气预报在大作业语境下,几乎总是预测一个连续数值——明天的最高气温是多少度,或者后天的湿度是多少。这是典型的回归问题,评估指标用平均绝对误差(MAE)或均方根误差(RMSE),而不是分类准确率。
回归和分类的选择直接影响损失函数、模型接口和评分标准。比如你用sklearn的RandomForestRegressor可以输出“明天的最高温度是23.4摄氏度”,而如果用RandomForestClassifier,它只能告诉你“明天属于‘高温’这个类别的概率是0.65”,这个结果在课程报告里很难展开——你没法画一条“预测值 vs 真实值”的折线图。所以拿到源码后,先看fit()函数里的标签(y)是浮点数还是整数类别。如果是浮点数,方向就对了;如果是类别,说明这份源码本身就有问题,需要你自己重写目标变量。
2.2 核心特征构造:拆日期、补滞后、建滑动窗口
原始天气数据集通常只有几列:日期、最高气温、最低气温、湿度、气压、风速等。如果直接把日期丢给模型,模型只能把它当成一个从1到几千的整数,学到的规律是“时间越靠后温度越高”——这在有季节趋势的数据集上会造成假象。正确的做法是把日期拆成多个循环特征。
import pandas as pd import numpy as np # 假设原始数据只有三列: date, high_temp, low_temp df = pd.read_csv('weather_data.csv', parse_dates=['date']) df['year'] = df['date'].dt.year df['month'] = df['date'].dt.month df['day'] = df['date'].dt.day df['dayofweek'] = df['date'].dt.dayofweek # 周一=0, 周日=6 # 构造滞后特征: 用前1天、前3天、前7天的最高气温作为特征 df['temp_lag1'] = df['high_temp'].shift(1) df['temp_lag3'] = df['high_temp'].shift(3) df['temp_lag7'] = df['high_temp'].shift(7) # 构造滑动窗口均值: 过去7天的平均气温 df['temp_rolling_mean7'] = df['high_temp'].rolling(window=7).mean() # 删除包含NaN的行, 因为前7天没有足够的历史数据 df = df.dropna().reset_index(drop=True) print(df.head())这里的逻辑是:shift()是把过去几天的真实气温“搬”到今天这一行,让模型能看到昨天、三天前、一周前的温度。rolling().mean()则是给模型一个“近期气温水平”的参考。滞后特征和时间窗口是时间序列预测的两大支柱——对天气这种强周期数据,昨天和一周前的温度对明天温度的影响极大,这两个特征能直接解释数据中大部分方差。
参数上需要注意两个点。第一,shift(7)配合dropna()会让你的数据集损失前7行,这在几千条数据里可以忽略不计,但如果你用的是只有30天的微型数据集,就要考虑用fillna(method='bfill')回填而不是直接丢弃。第二,滑动窗口的大小选7而不是3或30,是因为天气的周期性以周为单位最明显——工作日和周末的气温模式差异很小,但冷空气活动通常以7到10天为一个周期。这是天气数据和股票数据的本质区别:股票里你用7日均线,天气里你用7日滑动平均,理由完全不同。
2.3 训练集和测试集划分:这套案例的得分关键
普通机器学习项目里,train_test_split的默认参数是shuffle=True,也就是把数据打乱后再随机划分。这个默认设置在天气预测里是致命的。假设你的数据是2015年到2022年的逐日记录,随机划分会让模型“看到”2022年夏天某几天的数据,然后用它们去“预测”2019年冬天的一个日子——这在作业报告里完全说不通,因为实际的天气预报只有历史数据可用。
from sklearn.model_selection import train_test_split # 正确做法: 按时间顺序划分, 前80%训练, 后20%测试 train_size = int(len(df) * 0.8) train_df = df.iloc[:train_size] test_df = df.iloc[train_size:] # 拆分特征和标签 feature_cols = ['month', 'day', 'dayofweek', 'temp_lag1', 'temp_lag3', 'temp_lag7', 'temp_rolling_mean7'] X_train = train_df[feature_cols] y_train = train_df['high_temp'] X_test = test_df[feature_cols] y_test = test_df['high_temp'] # 对比: 如果用了shuffle=True, 分数会虚高, 但老师只要看图就能识破 # X_train, X_test, y_train, y_test = train_test_split( # df[feature_cols], df['high_temp'], test_size=0.2, random_state=42 # )这里有一个很微妙的问题:如果你在报告里写“因为随机划分在时序数据中是数据泄漏,所以我用了按时间顺序的划分”,导师立刻会认定你理解了这个项目,而不是只会调包。反过来,如果你用了默认的shuffle=True,表面上看MAE更小、图表更漂亮,但只要你把预测曲线画出来就会发现——训练集和测试集的预测值交替出现,毫无时间连续性。这是最容易被一眼识破的作弊行为,除非你想赌老师不仔细看图,否则不要在时间序列上用随机划分。
3. 模型选型和训练:从线性回归到随机森林的梯度
3.1 三个候选模型:线性回归、决策树、随机森林
天气预测任务在大作业场景下,候选模型就三个:线性回归、决策树(或随机森林)、以及如果数据量够大可以试试XGBoost。神经网络(LSTM)在这个任务里表现常常不如随机森林,原因是逐日气温数据的样本量通常只有几千条,而LSTM对这种小样本的时间序列并不能发挥优势。相反,随机森林对特征交互的捕捉能力很强,尤其是“月份”和“滞后温度”之间的交互——它不需要你手动去告诉模型“夏天和冬天对滞后温度的依赖程度不同”。
模型选择还有一个隐藏的考量:报告好写。线性回归的系数表能直接说明“滞后1天温度每升高1度,预测温度上升0.6度”,随机森林的feature_importances_能给出特征重要性条形图。这些都可以直接放进答辩PPT。而LSTM的“不可解释性”会让老师在答辩时问出一连串你答不上来的问题。大作业的原则是:用你讲得清楚的模型,而不是用看起来很高级但讲不清楚的模型。
3.2 跑通训练流程并打印评估指标
下面是完整的最小可运行代码。先加载清洗后的数据,然后同时训练三个模型,输出各自的MAE和R²分数。这一步你不需要调参,先用默认参数跑一遍,目的是建立一个性能基线。
from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from xgboost import XGBRegressor from sklearn.metrics import mean_absolute_error, r2_score # 三个模型, 固定随机种子保证可复现 models = { 'LinearRegression': LinearRegression(), 'RandomForest': RandomForestRegressor( n_estimators=200, max_depth=10, random_state=42, n_jobs=-1 ), 'XGBoost': XGBRegressor( n_estimators=300, learning_rate=0.05, max_depth=6, random_state=42 ) } # 训练并评估 for name, model in models.items(): model.fit(X_train, y_train) y_pred = model.predict(X_test) mae = mean_absolute_error(y_test, y_pred) r2 = r2_score(y_test, y_pred) print(f"{name}: MAE = {mae:.3f}°C, R² = {r2:.4f}")第一次跑出来的结果通常是这样的规律:线性回归的MAE在2.5到3.5摄氏度之间,随机森林的MAE会立刻降到2.0以下,XGBoost和随机森林接近但通常略好一点点。R²方面,线性回归可能在0.75左右,而树模型能到0.90以上。这个差距完全来自特征中的“月份”和“滞后温度”之间存在非线性交互——线性回归无法自行捕捉“1月份滞后7天温度对预测的影响方式不同于7月份”。
参数选型的理由是:n_estimators=200对于几千条样本已经足够,再大只会增加训练时间而不会明显降低误差;max_depth=10防止树在单条样本上过拟合,因为叶子节点过多时模型会开始记忆训练集中的噪声;learning_rate=0.05配合n_estimators=300是XGBoost比较稳妥的组合——学习率小一点,树多一点,不容易过拟合。这些参数不需要光头调,默认参数已经能把大作业做到85分,下面这节才是冲95分的关键。
3.3 特征重要性分析:报告里的加分段落
随机森林和XGBoost都自带特征重要性属性,这一行代码值得单独拿出来说,因为它是你报告里“模型解释”章节的核心素材。
import matplotlib.pyplot as plt # 以随机森林为例, 输出特征重要性 importance = models['RandomForest'].feature_importances_ feat_imp_df = pd.DataFrame({ 'feature': feature_cols, 'importance': importance }).sort_values('importance', ascending=False) # 画横向条形图 plt.figure(figsize=(8, 4)) plt.barh(feat_imp_df['feature'], feat_imp_df['importance'], color='skyblue') plt.xlabel('Importance') plt.ylabel('Feature') plt.gca().invert_yaxis() plt.tight_layout() plt.savefig('feature_importance.png', dpi=150) print(feat_imp_df)特征重要性的结论在绝大多数数据集上是稳定的:temp_lag1(滞后1天温度)排名第一,重要性占比通常在40%以上;temp_rolling_mean7(7日滑动均值)排名第二;month(月份)排在第三。这意味着天气预测的本质是“今天的温度强烈影响明天的温度,而季节提供了长期趋势”。你可以把这个结果写进报告的结论段——它比任何模型原理的堆砌都更有说服力。
4. 可视化出图:让评分老师三秒看懂你的工作量
4.1 三张图定江山:折线图、散点图、误差分布图
天气可视化的核心原则是:老师不会盯着你的print()输出看指标,但会一眼扫过你放的每一张图。我建议至少出三张图。第一张是“测试集真实值 vs 预测值”的折线图,时间作为横轴,这证明你的模型能捕捉趋势;第二张是预测值对真实值的散点图,附上y=x斜线,这证明预测没有系统性偏移;第三张是预测误差的直方图,这证明误差集中在0附近而不是有大尾巴。
import matplotlib.pyplot as plt # 用已经训练好的随机森林来预测测试集 y_pred_rf = models['RandomForest'].predict(X_test) # 图1: 时间序列对比折线图 —— 只画最后60天, 太挤了看不清楚 plt.figure(figsize=(12, 4)) test_dates = df['date'].iloc[train_size:].reset_index(drop=True) plt.plot(test_dates[:60], y_test[:60], label='True', color='black', linewidth=1.5) plt.plot(test_dates[:60], y_pred_rf[:60], label='Prediction', color='red', linestyle='--', linewidth=1.5) plt.legend() plt.ylabel('High Temp (°C)') plt.title('True vs Predicted High Temperature (Last 60 Days)') plt.xticks(rotation=45) plt.tight_layout() plt.savefig('pred_vs_true.png', dpi=150) # 图2: 散点图 + y=x 参考线 plt.figure(figsize=(5, 5)) plt.scatter(y_test, y_pred_rf, s=8, alpha=0.5, color='steelblue') plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', label='y = x') plt.xlabel('True Value (°C)') plt.ylabel('Predicted Value (°C)') plt.legend() plt.tight_layout() plt.savefig('scatter_true_pred.png', dpi=150) # 图3: 误差分布直方图 errors = y_pred_rf - y_test plt.figure(figsize=(6, 4)) plt.hist(errors, bins=30, color='gray', edgecolor='white') plt.xlabel('Prediction Error (°C)') plt.ylabel('Count') plt.axvline(x=0, color='red', linestyle='--') plt.tight_layout() plt.savefig('error_hist.png', dpi=150)画图这段代码里有三个容易被忽视的细节。第一,折线图只画最后60天而不是全部测试集——如果画全量数据,预测和真实曲线会因为太密集而糊成一团,老师截图放进报告里根本看不清,分数就会打折。第二,散点图的红虚线是y = x,这条线段的作用是让预测值和真实值的偏离程度一目了然;如果点都集中在对角线附近,哪怕你说MAE只有1.5度,老师也能从图上直接验证。第三,误差直方图如果分布明显偏向正数,说明模型系统性低估了温度,也就是某个关键特征没有被用上——比如没有加入“当天气压”作为特征。
4.2 特征相关性热力图:答辩时的防守依据
如果项目要求提交“天气可视化”,那么相关性热力图是你必须放进报告里的那张图。它展示的是所有数值特征两两之间的皮尔逊相关系数,老师拿这张图来对照你的特征工程是否合理。
import seaborn as sns # 选择数值特征列 plot_cols = ['high_temp', 'low_temp', 'month', 'dayofweek', 'temp_lag1', 'temp_lag3', 'temp_lag7', 'temp_rolling_mean7'] corr_df = df[plot_cols].copy() # 计算相关系数矩阵 corr_matrix = corr_df.corr() # 用热力图展示 plt.figure(figsize=(8, 6)) sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm', linewidths=0.5) plt.tight_layout() plt.savefig('correlation_heatmap.png', dpi=150)这里有一个值得在报告里写的发现:high_temp和temp_lag1的相关系数通常在0.85以上,而high_temp和low_temp的相关系数可能高达0.95。这个“共线性”特征意味着,如果你在特征里同时放high_temp的滞后值和low_temp的滞后值,两个特征会互相打架,部分模型(尤其是线性回归)的参数估计会变得不稳定。我见过有同学的源码里保留了所有原始特征加上六个滞后特征,结果线性回归的系数正负号都反了,报告里根本解释不通。
5. 避坑排查:四个能让成绩从95掉到70的隐藏问题
5.1 坑一:训练集和测试集之间发生了数据泄漏
现象:测试集上的MAE低得离谱,几乎接近0.5摄氏度,R²跑到0.98以上。散点图上的点完美贴合对角线。但你再仔细看,预测曲线的走势和真实曲线几乎逐日重合,连转折点都对得上,误差始终不超过1度。
原因:在构造滞后特征之前就划分了训练集和测试集,导致测试集里的滞后特征使用了训练集的数据。或者更直接——你把整个数据集的均值或标准化参数(scaler.fit_transform)在划分之前就拟合了,让测试集的信息提前混进了训练流程。
解决:标准化必须放在划分之后,滞后特征必须在划分之后单独构造。最稳妥的做法是写一个make_features(data, start_index)函数,对训练集和测试集分别调用。如果你发现源码里有StandardScaler().fit_transform(df[cols])这样的代码出现在train_test_split之前,那这就是泄漏源。
5.2 坑二:日期解析失败导致全是NaN
现象:代码跑起来不报错,但模型预测出来的温度是同一个常数,折线图是一条水平线。检查特征后发现month和day列全是NaN,或者所有值等于1。
原因:pd.read_csv('weather_data.csv', parse_dates=['date'])没生效,日期列实际被读成了字符串;用df['date'].dt.month访问时返回了NaN。还有一种情况是数据集里的日期格式包含了时间戳或者中文年月日,pandas默认解析不了。
解决:先用print(df.dtypes)确认date列的类型是datetime64。如果不是,用pd.to_datetime(df['date'], format='%Y/%m/%d')按实际格式强制转换,这才是最稳妥的做法。不要相信parse_dates总是聪明到能自动识别你的格式。
5.3 坑三:中文标签在图表里乱码,这是大忌
现象:图是画出来了,但标题、坐标轴标签、图例上的中文全部变成了方框和乱码。报告里放这样一张图,老师的第一印象就是“这个作业不够认真”,哪怕你的模型分数再高也会被打折扣。
原因:matplotlib的默认字体不支持中文,需要手动指定中文字体。
解决:在绘图代码最前面加上font setting。
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统 # 或者 Linux: plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei'] plt.rcParams['axes.unicode_minus'] = False # 让负号正常显示这个坑几乎是每年大作业翻车的重灾区。如果你用的是macOS,需要把SimHei换成PingFang SC或者Arial Unicode MS。反正记住:画图前先设字体,而不是画完发现乱码再去猜是哪里的问题。顺带说一句,如果你给老师的源文件里没有这一行字体设置,他本人在自己电脑上一跑就是乱码,这会直接影响评分观感。
5.4 坑四:把年份当作线性特征,模型学到奇怪的东西
现象:模型在训练集上表现很好,但测试集的MAE突然飙升到5度以上,尤其是测试集覆盖的时间段和训练集相隔半年以上时,预测值完全偏离真实值。
原因:年份被当作普通数值特征丢进了模型。如果数据是2015到2022年,模型会学到一个“年份越大温度越高”的趋势——哪怕这个趋势在数据里只有0.1度/年的幅度,模型也会把它放大。当测试集是2023年时,模型会外推出一个毫无依据的偏移量。
解决:如果你不需要跨年预测,直接把year特征从feature_cols里去掉。如果你的数据只有一年,year这个特征根本就是常数,留着只会让模型徒增困惑。我一般只在做跨年度预测时使用dayofyear(一年中的第几天)来捕捉季节性,而不会用实际的公元年份。
6. 用模型持久化收尾:把训练好的模型拿去交作业
最后一章教你一个实用技巧:把训练好的模型和预测流程打包成一个干净的predict.py,老师拿到之后只需要输入“第几天的特征”,就能得到预测温度。这比交一堆.ipynb文件更能体现工程能力。
import joblib # 保存模型和特征列名 joblib.dump(models['RandomForest'], 'weather_model.pkl') joblib.dump(feature_cols, 'feature_columns.pkl') # 使用: 在新环境中加载并预测 loaded_model = joblib.load('weather_model.pkl') loaded_features = joblib.load('feature_columns.pkl') # 假设明天的特征是: month=8, day=20, dayofweek=5, # temp_lag1=31.2, temp_lag3=29.8, temp_lag7=28.5, temp_rolling_mean7=30.1 tomorrow_features = [8, 20, 5, 31.2, 29.8, 28.5, 30.1] tomorrow_temp = loaded_model.predict([tomorrow_features])[0] print(f"明天预测最高气温: {tomorrow_temp:.1f}°C")这份代码虽然短,但把你的项目从“一个能跑的notebook”提升到了“一个能交付的预测工具”。我在最后一步一定会做两件事:把模型文件和数据文件放在同一个目录下,并在README.md里写清楚“运行predict.py之前需要先安装pandas、scikit-learn、joblib这些依赖”。这个习惯帮我挡掉了无数“在我的电脑上跑不起来”的追问。
如果学有余力,还可以给这个项目加一个“多步预测”的demo:用预测出的明天的温度作为新的temp_lag1,连续预测未来3天。这会展示你对时间序列递归预测的理解,也是答辩时最容易被提问的部分。我的经验是把它作为一个可选功能放在代码末尾的注释块里,而不是放在主线流程中,免得完整流程跑起来因为误差累积而被老师质疑。
天气预测这个方向,拿高分靠的不是模型有多新,而是特征工程有没有讲清楚、评估流程有没有避开水泄漏、图有没有让外行看明白。你把这些细节打磨到位,哪怕用的只是随机森林默认参数,也能稳稳站上90分。希望帮到你。
本文还有配套的精品资源,点击获取