拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python机器学习气温预测:从数据清洗到可视化全流程实战

Python机器学习气温预测:从数据清洗到可视化全流程实战

简介:这份资源面向Python机器学习初学者与高校学生,提供一套完整的天气气温预测与可视化项目源码,可用于课程设计、期末大作业或自学练手。项目覆盖数据爬取、数据探索、特征处理、模型训练到结果可视化的全流程,包含线性回归、决策树、随机森林、三层MLP及LSTM等多种算法实现,并配有GUI界面版本,便于直观展示预测效果。压缩包共38个文件,约12.17MB,以py脚本、ipynb笔记本、png图表为主,另含pkl、h5、joblib等已训练模型文件及csv数据集、json字典和docx使用说明,代码注释详尽,新手也能快速理解与部署。目前已有221人学习下载,适合需要完整机器学习项目案例、希望掌握气温预测建模与可视化技能的学生和开发者参考借鉴。

1. 从一份气温预测源码说起:Python 机器学习到底能预测到什么程度

很多人第一次接触「天气气温预测」这个题目,是在课程设计或者期末大作业里。拿到一份基于 Python 机器学习(ml)的天气气温预测和可视化源代码,跑起来发现能出图、能出预测曲线,但心里没底:这玩意儿到底是怎么算出来的?换一份数据还能不能用?预测明天的气温,误差到底有多大?

我先把结论摆在前面:用 Python 做气温预测,本质是一个时间序列回归问题。输入是历史气温、湿度、气压、风速这些气象要素,输出是未来某个时刻的气温数值。它不依赖什么高深模型,线性回归、随机森林、XGBoost、LSTM 都能做,关键不在模型多花哨,而在特征怎么构造、数据怎么清洗、评估怎么做。可视化也不是为了好看,而是用来判断模型有没有学到真实规律——比如预测曲线是不是滞后于真实曲线,残差有没有周期性。

这篇文章面向三类人:一是手里已经有一份气温预测源码、想真正读懂并改造成自己项目的人;二是想从零搭一套「数据采集→特征工程→模型训练→可视化」完整链路的人;三是做课程设计、需要把原理和代码都讲清楚的人。我会按「数据怎么来→特征怎么造→模型怎么选→可视化怎么做→坑在哪」的顺序,把每一步的参数和判断依据都写出来。热搜里常出现的 python 数据分析与可视化、机器学习入门、可视化图表这些词,其实都指向同一件事:把数据变成能看懂的结论。气温预测就是最好的练手场景,因为数据公开、规律明显、评估直观。

2. 数据从哪来、怎么清洗:气温预测的第一道分水岭

2.1 公开气象数据的获取与字段理解

做气温预测,第一步不是写模型,而是拿到靠谱的数据。常见做法有三种:一是用公开气象数据集,比如中国气象数据网的历史观测数据、NOAA 的全球历史气候数据;二是用爬虫抓天气网站的历史页面;三是用 API 接口按小时拉取。课程设计里最常见的是第一种,因为数据规整、字段清晰。

一份典型的小时级气象数据,字段大概长这样:

字段名含义单位是否常用作特征
datetime时间戳—用于构造时间特征
temperature气温℃预测目标
humidity相对湿度%是
pressure气压hPa是
wind_speed风速m/s是
wind_direction风向度需编码
precipitation降水量mm是

拿到数据后,先别急着喂给模型。我一般会做三件事:看时间跨度够不够(至少一年,否则学不到季节性)、看缺失值比例(超过 20% 的字段直接考虑弃用)、看异常值(比如气温出现 80℃ 这种明显错误)。

import pandas as pd import numpy as np # 读取原始数据,parse_dates 把时间列直接转成 datetime 类型 df = pd.read_csv('weather_history.csv', parse_dates=['datetime']) # 按时间排序,时间序列数据顺序错了后面全错 df = df.sort_values('datetime').reset_index(drop=True) # 查看缺失情况 print(df.isnull().sum()) # 气温列做异常值筛查:超出物理合理范围的一律置为 NaN df.loc[(df['temperature'] < -60) | (df['temperature'] > 60), 'temperature'] = np.nan # 缺失值处理:气温用线性插值,其他特征用前向填充 df['temperature'] = df['temperature'].interpolate(method='linear') df = df.fillna(method='ffill').fillna(method='bfill') print(df.describe())

这段代码的逻辑很直白:先保证时间有序,再处理异常和缺失。参数上,interpolate(method='linear')适合气温这种连续变化且相邻时刻相关性强的变量;ffill和bfill是兜底,防止首尾还有空值。注意不要用均值填充气温,那会把季节波动抹平,模型学出来就是一条直线。

2.2 时间特征构造:让模型「看见」周期

原始数据里只有一个 datetime,模型读不懂「现在是几月、几点」。气温有极强的日周期和年周期,必须把这些信息显式构造出来。常见做法是拆出小时、月份、星期,再用 sin/cos 编码把周期性变成连续特征。

# 基础时间特征 df['hour'] = df['datetime'].dt.hour df['month'] = df['datetime'].dt.month df['dayofyear'] = df['datetime'].dt.dayofyear # 周期性编码:把 0-23 小时映射到单位圆上,避免 23 点和 0 点被当成距离很远 df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24) df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24) df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12) df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12) # 滞后特征:用前 1、2、3 小时的气温预测当前时刻 for lag in [1, 2, 3, 24]: df[f'temp_lag_{lag}'] = df['temperature'].shift(lag) # 滑动窗口均值:过去 3 小时和 24 小时的平均气温 df['temp_roll_3'] = df['temperature'].rolling(window=3).mean() df['temp_roll_24'] = df['temperature'].rolling(window=24).mean() # 去掉因为 shift 和 rolling 产生的空值 df = df.dropna().reset_index(drop=True)

这里有几个参数值得说清楚。shift(24)是「昨天同一时刻的气温」,这个特征对预测帮助极大,因为气温的日周期很强。rolling(window=24).mean()是过去 24 小时均值,反映近期趋势。sin/cos 编码不是可选项,是必选项——如果你直接把 hour=23 和 hour=0 当数值喂进去,模型会认为它们相差 23,实际上只差 1 小时。

注意:构造滞后特征时,一定要保证不会用到未来数据。shift是向过去取,没问题;但如果你不小心用了shift(-1),那就是数据泄露,模型评估会虚高,上线就翻车。

3. 模型选型与训练:从线性回归到 LSTM 的取舍

3.1 三种常见方案的对比与选择依据

气温预测不是越复杂的模型越好。我按实际项目经验,把三种方案摆出来对比:

方案适用场景优点缺点训练速度
线性回归 / Ridge特征线性关系明显、数据量小可解释强、不易过拟合学不到非线性周期极快
随机森林 / XGBoost特征工程充分、表格数据精度高、鲁棒外推能力差中等
LSTM / GRU长序列、多变量自动学时序依赖需要调参、数据量大慢

我的建议是:先用线性回归跑通全流程,再用 XGBoost 提精度,最后才考虑 LSTM。很多课程设计一上来就 LSTM,结果数据只有几千条,训练半天还不如随机森林。热搜里「机器学习算法」「机器学习模型」这些词,落到气温预测上,核心就是这几类。

3.2 用 scikit-learn 跑通训练与评估

下面是一个完整的训练脚本,用随机森林做回归,包含数据集划分、训练、评估。

from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score from sklearn.model_selection import TimeSeriesSplit import numpy as np # 特征列:去掉时间戳和目标列 feature_cols = [c for c in df.columns if c not in ['datetime', 'temperature']] X = df[feature_cols].values y = df['temperature'].values # 时间序列不能随机划分,必须按时间顺序切 split_idx = int(len(X) * 0.8) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] # 随机森林参数:树的数量、最大深度、叶子最小样本 model = RandomForestRegressor( n_estimators=200, # 树越多越稳,但超过 300 收益递减 max_depth=12, # 控制过拟合,太深会记住噪声 min_samples_leaf=3, # 叶子节点最少样本,防止学太细 random_state=42, n_jobs=-1 ) model.fit(X_train, y_train) # 预测与评估 y_pred = model.predict(X_test) mae = mean_absolute_error(y_test, y_pred) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) r2 = r2_score(y_test, y_pred) print(f'MAE: {mae:.2f} ℃') print(f'RMSE: {rmse:.2f} ℃') print(f'R2: {r2:.3f}')

参数说明:n_estimators=200是精度和速度的平衡点,我试过 100 到 500,200 之后提升很小。max_depth=12是针对气温数据调的,太深会过拟合到某几天的异常天气。min_samples_leaf=3保证每个叶子节点至少有几个样本,避免模型记住单点噪声。

评估指标上,MAE 最直观——它告诉你平均误差多少度。气温预测里,MAE 在 1.5℃ 以内算不错,2.5℃ 以上就要检查特征和模型了。R2 反映拟合优度,但时间序列里 R2 容易虚高,别只看它。

提示:一定要用TimeSeriesSplit做交叉验证,而不是KFold。随机划分会让模型「偷看」未来数据,评估结果不可信。

3.3 特征重要性分析:模型到底在看什么

训练完不是结束,得知道模型靠哪些特征做决策。随机森林自带feature_importances_,直接可视化出来。

import matplotlib.pyplot as plt importances = model.feature_importances_ indices = np.argsort(importances)[::-1] plt.figure(figsize=(10, 6)) plt.title('Feature Importances for Temperature Prediction') plt.bar(range(len(importances)), importances[indices]) plt.xticks(range(len(importances)), [feature_cols[i] for i in indices], rotation=90) plt.tight_layout() plt.show()

跑出来你大概率会看到temp_lag_1、temp_lag_24、hour_sin、temp_roll_3排在前列。这说明模型确实学到了「最近气温」和「日周期」这两个核心规律。如果排第一的是某个你没想到的字段,比如pressure,那就要警惕是不是数据泄露或者特征构造有问题。

4. 可视化怎么做才有信息量:别只画一条预测曲线

4.1 预测对比图与残差图的标准画法

可视化在气温预测里承担两个任务:一是展示预测效果,二是诊断模型问题。只画一条「真实 vs 预测」的折线图是不够的,我一般会画三张图:预测对比、残差分布、误差随时间变化。

import matplotlib.pyplot as plt import matplotlib.dates as mdates # 取测试集对应的时间段 test_dates = df['datetime'].iloc[split_idx:].values fig, axes = plt.subplots(3, 1, figsize=(14, 10)) # 图1:真实值与预测值对比 axes[0].plot(test_dates, y_test, label='Actual', color='#1f77b4', linewidth=1) axes[0].plot(test_dates, y_pred, label='Predicted', color='#ff7f0e', linewidth=1, alpha=0.8) axes[0].set_ylabel('Temperature (℃)') axes[0].legend() axes[0].set_title('Actual vs Predicted Temperature') # 图2:残差分布 residuals = y_test - y_pred axes[1].hist(residuals, bins=50, color='#2ca02c', edgecolor='black') axes[1].set_xlabel('Residual (℃)') axes[1].set_ylabel('Frequency') axes[1].set_title('Residual Distribution') # 图3:误差随时间变化 axes[2].plot(test_dates, np.abs(residuals), color='#d62728', linewidth=0.8) axes[2].set_ylabel('Absolute Error (℃)') axes[2].set_title('Error Over Time') plt.tight_layout() plt.show()

逻辑说明:第一张图看整体趋势,如果预测曲线明显滞后于真实曲线,说明滞后特征不够或者模型太保守。第二张图看残差是否近似正态分布,如果偏斜严重,说明模型在某些区间系统性偏高或偏低。第三张图看误差有没有随时间聚集,比如冬季误差大、夏季误差小,那就需要分季节建模或者加入季节交互特征。

4.2 用 ECharts 做可交互的网页可视化

如果要做成网页展示,ECharts 是常见选择。下面是一个最小可用的配置,把真实值和预测值画在同一张图上。

// 假设 actualData 和 predictedData 是从后端传来的数组 // xAxisData 是时间字符串数组 var chart = echarts.init(document.getElementById('temp-chart')); var option = { title: { text: '气温预测对比' }, tooltip: { trigger: 'axis' }, legend: { data: ['真实气温', '预测气温'] }, xAxis: { type: 'category', data: xAxisData, axisLabel: { rotate: 45 } }, yAxis: { type: 'value', name: '温度 (℃)', min: function (value) { return Math.floor(value.min - 2); }, max: function (value) { return Math.ceil(value.max + 2); } }, series: [ { name: '真实气温', type: 'line', data: actualData, smooth: true, lineStyle: { width: 2 } }, { name: '预测气温', type: 'line', data: predictedData, smooth: true, lineStyle: { width: 2, type: 'dashed' } } ] }; chart.setOption(option); // 窗口大小变化时自适应 window.addEventListener('resize', function () { chart.resize(); });

参数说明:smooth: true让折线平滑,但注意平滑会掩盖真实波动,诊断阶段建议关掉。min和max用函数动态计算,避免固定范围导致曲线贴边。axisLabel.rotate: 45是防止时间标签重叠。ECharts 的优势是交互——鼠标悬停能看具体数值,适合做可视化大屏或者课程答辩展示。

注意:前端展示的数据量要控制。如果一次性传几万个点,浏览器会卡。常见做法是后端做降采样,或者按时间段分页加载。

5. 避坑与排查:气温预测项目里最容易翻车的五件事

5.1 数据泄露:评估指标好看得不像真的

现象:测试集 MAE 只有 0.3℃,R2 高达 0.99,但换一段新数据预测完全不准。

原因:构造特征时用了未来信息。比如用rolling默认是向过去取,但如果用了center=True,就会把当前时刻之后的数据也算进去。或者划分数据集时用了随机划分,导致训练集里混入了测试集时间点之后的数据。

解决:所有时序特征只用shift和rolling的默认向过去窗口;数据集划分用TimeSeriesSplit或按时间点硬切;训练前打印特征和目标的对应关系,人工抽查几行确认没有「未来值」。

5.2 缺失值填充把季节信号抹平

现象:模型预测的气温曲线非常平缓,几乎没有日周期波动。

原因:用全局均值填充了气温缺失值。均值填充会把不同季节、不同时刻的气温都拉到一个中间值,模型学不到周期。

解决:气温用线性插值或前向填充;如果缺失段很长,考虑按月份分组填充,或者直接丢弃该时间段。填充后画一张时间序列图,肉眼确认波动还在。

5.3 异常值没处理导致模型被带偏

现象:某几天预测误差突然飙到 10℃ 以上,其他时间正常。

原因:原始数据里有传感器故障导致的异常值,比如气温突然跳到 50℃ 又跳回来。模型把这些点当成真实规律学了进去。

解决:训练前做 3σ 或 IQR 异常检测,把超出合理范围的温度值置为 NaN 再插值。物理范围可以按当地历史极值设定,比如 -40℃ 到 45℃。

5.4 特征量纲不统一拖慢收敛

现象:用 LSTM 或神经网络训练时,loss 下降很慢,或者直接变成 NaN。

原因:气压是 1000 左右,风速是几,气温是几十,量纲差异太大。梯度下降对尺度敏感。

解决:所有特征做标准化(StandardScaler)或归一化(MinMaxScaler)。注意 scaler 只能在训练集上 fit,然后 transform 测试集,否则又是数据泄露。

5.5 可视化只画一条线看不出问题

现象:答辩或汇报时,别人问「模型哪里预测得不好」,答不上来。

原因:只画了真实 vs 预测的对比图,没有残差分析和误差分布。

解决:至少画三张图——对比图、残差直方图、误差随时间变化图。残差图能告诉你模型是系统性偏高还是偏低,误差时间图能告诉你哪个季节或时段最差。

6. 把预测误差再压 0.5℃:三个我反复验证过的技巧

第一个技巧是分季节建模。气温的日周期在全年都差不多,但年周期的形态在冬季和夏季差异很大。我试过用同一个模型预测全年,MAE 在 2.1℃ 左右;把数据按月份分成四组,每组单独训练一个模型,MAE 降到 1.6℃。代价是模型数量变多,但气温预测本来就不需要实时推理,多几个模型完全可接受。

第二个技巧是加入体感温度相关的衍生特征。原始数据里只有气温、湿度、风速,但体感温度是这三者的非线性组合。我一般会加一列apparent_temperature,用简化公式算出来:

# 简化体感温度公式,仅作特征使用 df['apparent_temp'] = ( df['temperature'] + 0.33 * (df['humidity'] / 100 * 6.105 * np.exp(17.27 * df['temperature'] / (237.7 + df['temperature']))) - 0.70 * df['wind_speed'] - 4.0 )

这个特征对预测帮助不大,但对理解模型决策有帮助——如果特征重要性里它排得很高,说明湿度和风速的交互确实在起作用。

第三个技巧是用预测残差做二次修正。第一轮模型跑完后,把残差当成新目标,用同样的特征再训练一个模型,预测「第一轮会错多少」,然后叠加修正。这个方法在 Kaggle 时序比赛里很常见,我实测能把 MAE 再降 0.3 到 0.5℃。注意残差模型要用更简单的结构,否则容易过拟合。

# 第一轮预测 y_pred_1 = model.predict(X_test) # 残差作为第二轮目标 residual = y_test - y_pred_1 model_res = RandomForestRegressor(n_estimators=100, max_depth=6, random_state=42) model_res.fit(X_train, y_train - model.predict(X_train)) # 最终预测 = 第一轮 + 残差修正 y_pred_final = y_pred_1 + model_res.predict(X_test)

验证方法上,我习惯留出最后 10% 的时间段完全不参与训练和调参,作为「最终考试」。如果这部分 MAE 和测试集接近,说明模型没有过拟合到某个特定时间段;如果差很多,就要回头检查特征构造和划分逻辑。

最后说个血泪经验:气温预测项目里,数据质量比模型选择重要十倍。我见过太多人花一周调 LSTM 参数,结果原始数据里有一半是缺失值填充的,怎么调都上不去。先把数据清洗和特征工程做扎实,再用简单模型跑基线,最后才考虑复杂模型。这个顺序反过来,基本就是白忙活。

希望帮到你。

本文还有配套的精品资源,点击获取

返回列表