☰
天气预测Python课程设计:从数据清洗到ARIMA模型与可视化实现
2026/9/28 23:18:19 网站建设 项目流程

简介:这是一份面向高校数据科学课程设计的完整实战项目,基于Python实现天气数据的采集、清洗、建模预测与可视化展示,适合作为课程设计、期末大作业或多学分的综合实训参考。项目已通过导师指导并获97分,整体质量经过验证,解压后按说明即可直接运行,无需额外改动。

压缩包共24个文件,大小仅1.43MB,结构清晰:4个.py脚本分别负责数据获取、处理、建模与主流程,4个csv文件提供训练、验证、测试及当日实况数据,还包含Model.pkl预训练模型、天气网HTML可视化页面、readme说明文档以及运行截图,便于对照效果和快速理解设计思路。

目前已有653人学习使用,特别适合初学Python数据分析与机器学习的学生参考,可从中学习完整的数据处理管线、模型保存与加载、前端可视化衔接等方法,也能直接作为自己的课程设计材料提交。

1. 天气预测课程设计:一张数据表到一条可展示的预测曲线

拿天气数据做预测和可视化,是Python课程设计里出场率最高的题目之一。它看起来简单——无非是读取数据、跑个模型、画张图——但真正动手时,你会发现数据清洗、日期对齐、模型选型和出图样式每一个环节都能卡住人。市面上流传的“完整源码包”大多只给一个能跑的脚本,数据一换、Python版本一换就翻车。这篇笔记照着课程设计的真实评分点来拆:数据从哪里来、特征怎么做、模型怎么选、图怎么画、坑在哪里,最后给一个能让答辩老师点头的验证思路。适合正在做课设的学生,也适合想快速搭一个天气数据demo的开发者照着复现。

2. 从城市天气数据到训练集:清洗与特征工程的三个关键动作

2.1 先决定数据来源:爬虫接口还是本地CSV

课程设计里最常见的数据来源有两种。第一种是用爬虫抓天气网站的历史数据,优点是能拿到任意城市、任意时间段的数据,缺点是网站改版、反爬策略、字段名不统一都会打断你的节奏,而且答辩时现场联网经常出意外。第二种是直接用现成的CSV数据包,稳、可复现、离线可跑,数据包里一般包含日期、最高气温、最低气温、天气现象、风力风向这几个字段,对课程设计完全够用。

我的做法是:本地CSV为主,爬虫脚本作为加分项放在项目里。这样既保证主流程稳定,又能向老师展示你“会爬数据”。如果选择爬虫,优先找提供JSON接口的天气服务商,解析比解析HTML省事得多。

下面是一段读取并检查CSV数据的代码:

import pandas as pd df = pd.read_csv('weather_data.csv', encoding='utf-8') df['date'] = pd.to_datetime(df['date']) df = df.sort_values('date').reset_index(drop=True) print(df.shape) print(df.head()) print(df.isnull().sum())

逻辑说明:先用pd.to_datetime把日期字符串转成时间类型,这是一切时间序列分析的前提——如果日期是字符串,后面切分训练集和测试集时顺序会错。然后按日期排序并重置索引,避免原始数据乱序导致训练集和测试集互相污染。isnull().sum()用来快速定位哪些列有空值,这一步看似基础,但相当一部分课设翻车就翻在缺失值没处理。

参数说明:encoding='utf-8'必须和你数据文件实际编码一致。有些数据包是GBK编码,读进来直接乱码,需要改成encoding='gbk'。判断方法很简单:读出来打印df.head(),看到中文正常就不是编码问题;看到一堆乱码或者直接报错,换编码再试。

2.2 缺失值与异常值处理:不要用均值填一切

天气数据里的缺失通常集中在极端天气或站点维护时段。处理时先看缺失比例:比例低于5%,可以直接删除;比例在5%到20%之间,用插值法补齐;超过20%,这个字段直接丢弃,别硬留。

异常值的判断逻辑更有讲究。气温数据里出现40℃或-40℃不一定是错的,得结合城市和季节判断。一般做法是用滚动窗口算均值和标准差,偏离均值超过3倍标准差的数据点标记为异常,再用相邻日期的均值替换。

# 缺失值处理 df['temp_high'] = df['temp_high'].interpolate(method='linear') # 滚动窗口异常值检测 rolling_mean = df['temp_high'].rolling(window=7, center=True).mean() rolling_std = df['temp_high'].rolling(window=7, center=True).std() upper = rolling_mean + 3 * rolling_std lower = rolling_mean - 3 * rolling_std df['temp_high'] = df['temp_high'].clip(lower=lower, upper=upper)

逻辑说明:interpolate用的是线性插值,拿缺失值前后两个有效数据的平均值补位,比用整列均值填充要合理得多——气温有季节性趋势,前后日期的值才更有参考意义。异常值检测先用7天滚动窗口算出局部均值和标准差,然后通过clip把超出3倍标准差的值压回合理范围。

参数说明:rolling(window=7)表示以7天为窗口,center=True让窗口以当前日期为中心。窗口宽度可以调:窗口越小,对短期波动的反应越敏感,但也越容易把正常天气变化误判为异常;窗口越大,检测越平滑,但可能漏掉真实的剧烈降温。课程设计里7到14天是比较稳的选择。

2.3 特征工程:让模型看到“昨天和去年的今天”

如果直接把日期字符串丢给模型,模型学不到任何时间规律。特征工程的核心是把时间维度拆成模型能消化的数值。常用的做法有三个:滞后特征、滑动窗口统计、周期性编码。

滞后特征是最直接有效的。预测明天的最高气温时,把昨天、前天的最高气温作为输入特征,模型就有了“气温惯性”这个概念。滑动窗口统计则是取过去7天平均气温、最高最低温差等。周期性编码时,把月份转成sin和cos,防止12月和1月因为是“相邻月份”却被当作风马牛不相及的类别。

# 滞后特征 for lag in [1, 2, 3, 7]: df[f'temp_high_lag{lag}'] = df['temp_high'].shift(lag) # 滑动窗口统计 df['temp_high_avg7'] = df['temp_high'].rolling(window=7).mean() # 周期性编码 df['month_sin'] = np.sin(2 * np.pi * df['date'].dt.month / 12) df['month_cos'] = np.cos(2 * np.pi * df['date'].dt.month / 12) # 删除含NaN的行(滞后和滚动窗口会产生前几行的空值) df = df.dropna().reset_index(drop=True)

逻辑说明:shift(lag)把目标列向上移动lag行,让模型用前1天、前2天、前3天、前7天的真实气温来预测当天。这里的核心是删除含NaN的行:做了滞后和滚动窗口之后,数据集前几行必然缺失,直接训练会报错或产生无意义的预测。

参数说明:滞后天数选[1, 2, 3, 7]是有讲究的。1和2捕捉短期惯性,3捕捉一般天气过程周期,7对应一周前的温度,能帮模型感知“和上周同一天比是冷是热”。如果你手头数据跨了好几年,可以再加一个365的滞后特征,让模型知道“去年今天的气温”。周期性编码里sin和cos配合使用,能把月份映射到一个连续的圆环上,模型才学得到“冬天和夏天不是线性两端”这种常识。

3. 选 ARIMA 还是线性回归:课程设计里预测模型的落地选择

3.1 两个常用方案的能力边界对比

课程设计里预测部分最常看到两个方向:线性回归和ARIMA。还有一部分人会硬上LSTM,但对课设来说性价比很低。

线性回归的优势在于特征透明、训练快、容易解释。你可以清楚地说出“滞后1天的气温对预测贡献最大”,答辩时老师问“为什么这个特征重要”,你能从容回答。缺点是它对时间序列的自相关结构建模能力有限,趋势和季节项需要靠外部特征补齐。

ARIMA全称是差分自回归移动平均模型,专门为时间序列设计。它能自动捕捉序列自身的自相关结构,对平稳序列的预测效果通常比普通线性回归更好。缺点是需要人工判断差分阶数、看自相关图和偏自相关图,参数选择有门槛,而且不太方便加入外部特征。

课程设计的评分逻辑里,“代码能跑出合理曲线”和“你能讲清楚为什么这么做”各占一半。我的建议是:数据量超过两年、有明显的季节性,用ARIMA;数据量只有几个月或者你想做多特征融合,用线性回归。如果时间充裕,两个都做然后对比RMSE,反而是最好的答辩素材。

3.2 用 statsmodels 跑通 ARIMA 的最小流程

ARIMA需要三个参数:p(自回归阶数)、d(差分阶数)、q(移动平均阶数)。课程设计里不需要用复杂的ACF/PACF判断法,一个自动定阶的循环就够了。

import itertools import warnings import statsmodels.api as sm warnings.filterwarnings('ignore') # 也只取最高气温作为预测目标 ts = df.set_index('date')['temp_high'].astype(float) # 遍历参数组合,用AIC选最优模型 best_aic = float('inf') best_order = None for p in range(0, 4): for d in range(0, 2): for q in range(0, 4): try: model = sm.tsa.ARIMA(ts, order=(p, d, q)).fit() if model.aic < best_aic: best_aic = model.aic best_order = (p, d, q) except Exception: continue print(f'最优ARIMA参数: {best_order}, AIC: {best_aic:.2f}')

逻辑说明:这段代码把所有p在0到3、d在0到1、q在0到3的共32种组合都训练一遍,选取AIC信息准则最小的那个作为最终模型。AIC不是预测准确率,它衡量的是模型拟合质量和复杂度之间的平衡——AIC越低,说明在拟合得好的同时参数尽量少,不容易过拟合。

参数说明:p和q的搜索范围设在0到3是合理的,天气数据的气温序列自相关通常不会太复杂,超过3的阶数很容易过拟合。d的搜索范围0到1,因为气温序列有年周期但整体平稳,二阶差分会把序列内部的季节关系破坏掉。如果数据量特别大,可以把p和q范围扩大到0到5,但训练时间会明显增长,课设数据量下没必要。

3.3 训练测试切分:时间序列不能随机打乱

这个错误几乎每个课设都有:用train_test_split默认的随机切分来切时间序列。随机打乱意味着模型可能用7月的数据去预测3月的数据,测试集里混着训练集的时间段,得到的准确率完全是假的。

train_size = int(len(ts) * 0.8) train, test = ts[:train_size], ts[train_size:] # 在训练集上重新拟合最优模型 model = sm.tsa.ARIMA(train, order=best_order).fit() forecast = model.forecast(steps=len(test)) # 计算评估指标 from sklearn.metrics import mean_absolute_error, mean_squared_error mae = mean_absolute_error(test.values, forecast) rmse = mean_squared_error(test.values, forecast, squared=False) print(f'MAE: {mae:.2f}°C, RMSE: {rmse:.2f}°C')

逻辑说明:这段代码严格按时间顺序切分,前80%做训练、后20%做测试,模拟“用过去预测未来”的真实场景。model.forecast(steps=len(test))直接预测未来N个时间点,N等于测试集的长度。MAE和RMSE都是温度预测里常用的误差指标,RMSE对偏大的误差更敏感。

参数说明:train_size=0.8是时间序列切分的常见比例。数据量偏少时可以调到0.7,让测试集有更多样本去评估稳定性;数据量充裕时可以0.85。用RMSE时注意sklearn不同版本里mean_squared_error的squared参数,新版本默认是True(返回MSE),要拿到RMSE必须设squared=False。

4. 可视化实现:让预测结果在答辩屏幕上站得住脚

4.1 用 Matplotlib 画“实际对比预测”图:三个默认设置必须改

Matplotlib的默认样式放在论文里尚可,放在课程设计答辩上就显得单薄。重点改三个方面:中文字体、坐标轴密度、图例样式。尤其是中文字体,Windows下默认不显示中文,画出来的图全是方块,一下就能扣分。

import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei'] plt.rcParams['axes.unicode_minus'] = False plt.figure(figsize=(12, 5)) plt.plot(train.index, train, label='训练集实际值', color='#2E86AB') plt.plot(test.index, test, label='测试集实际值', color='#A23B72') plt.plot(test.index, forecast, label='预测值', color='#F18F01', linestyle='--', linewidth=2) plt.xlabel('日期') plt.ylabel('最高气温 (°C)') plt.title('ARIMA 最高气温预测效果对比') plt.legend() plt.grid(alpha=0.3) plt.tight_layout() plt.savefig('arima_forecast.png', dpi=300) plt.show()

逻辑说明:训练集实际值用蓝色实线,测试集实际值用红色实线,预测值用橙色虚线。三条线的关系一眼就能看出模型的预测是滞后还是超前。linestyle='--'和linewidth=2让预测线在视觉上更突出,答辩时站在两米外也能看清。

参数说明:font.sans-serif必须放在绘图代码最前面,且要放在plt.figure()之前才生效。SimHei是Windows自带黑体,Microsoft YaHei是微软雅黑,两个都写上是为了兼容不同环境。axes.unicode_minus=False解决的是坐标轴负号显示为方块的问题,这个不设的话,冬天零下温度的图会多一处硬伤。dpi=300导出的图片足够清晰,放进答辩PPT不会糊。

4.2 用 Pyecharts 做交互式大屏:从静态图到可缩放页面

如果课程设计要求里有“可视化大屏”或“交互式界面”字样,Matplotlib画完静态图之后,还需要一个交互式页面。Pyecharts是首选,它能生成HTML文件,双击就能在浏览器打开,比Flask搭网站省事得多。

贴合天气主题,主要用两个图表:一个是折线图展示预测曲线,另一个是温度计图或仪表盘展示当天实时温度。

from pyecharts.charts import Line from pyecharts import options as opts line = ( Line() .add_xaxis([d.strftime('%Y-%m-%d') for d in ts.index]) .add_yaxis('实际气温', ts.values.tolist(), is_smooth=True) .add_yaxis('预测气温', forecast.values.tolist(), is_smooth=True, linestyle_opts=opts.LineStyleOpts(width=2, type_='dashed')) .set_global_opts( title_opts=opts.TitleOpts(title='城市最高气温预测趋势'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=45)), datazoom_opts=[opts.DataZoomOpts(range_start=60)], ) ) line.render('weather_forecast.html')

逻辑说明:Pyecharts的链式调用把数据源、坐标轴、缩放组件依次配置。add_xaxis的日期列表必须和温度值列表一一对应,常见错误是两个列表长度不一致,页面渲染时报错或曲线错乱。datazoom_opts加了一个缩放条,初始显示后40%的数据区间,方便直观看到测试集和预测值的对比细节。

参数说明:is_smooth=True让折线变得平滑,天气数据本身连续,折线图带棱角反而显得不自然。range_start=60表示缩放条初始位置在60%处,也就是一打开页面直接看后半段预测部分。轴标签旋转45度是因为日期字符串较长,默认横排会互相重叠。

4.3 数据可视化里“产地”字段缺失时怎么办

做可视化时一个常见尴尬是:数据包里的天气数据没有经纬度或城市字段,而做可视化大屏时你想加一个地图组件。遇到这种情况,不要硬改数据,更不要在代码里写死城市名。数据包里有什么字段就用什么可视化:如果没有地理信息,就把重点放在时间维度的可视化上,比如叠加多条曲线对比不同月份的温差分布。

如果确实要地图效果,常见做法是在数据预处理阶段把城市名称映射到Google或高德的行政区划码,然后用Pyecharts的Map组件和opts.ItemStyleOpts配合颜色区间来呈现。不过对这个课设而言,一条清晰的预测曲线远比一张花哨但数据生硬的地图更能说明问题。

5. 天气预测项目避坑:5 个让人翻车的细节和对应解法

5.1 日期索引丢失,预测结果整体错位

现象:预测曲线画出来,看起来形状和实际气温一致,但对不上日期,整体偏移了几天。

原因:做特征工程时dropna()删除了前几行,但索引重置不彻底;或者把date列单独拿出来训练,模型不知道日期顺序,预测结果被随机排序。

解决:所有涉及时间顺序的操作之后,都检查一遍数据顺序。一个稳的做法是在切分训练集和测试集之前显式排序并重置索引:

df = df.sort_values('date').reset_index(drop=True)

然后每次做dropna()之后再加一次reset_index(drop=True)。如果模型输出后需要重新关联日期,用pd.Series的索引对齐,而不是按位置对齐。

5.2 归一化泄漏:用全量数据的均值去缩放训练集

现象:训练时模型误差很小,测试时预测值明显偏低或偏高。

原因:做标准化或归一化时用了整个数据集的均值和标准差,包括测试集的信息。这在机器学习里叫数据泄漏,测试集的信息跑到了训练过程里,导致评估结果虚高。

解决:先切分训练集和测试集,再分别做标准化。

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() train_scaled = scaler.fit_transform(train.reshape(-1, 1)) test_scaled = scaler.transform(test.reshape(-1, 1))

fit_transform只在训练集上计算均值和标准差,测试集只用transform继承同一套参数。如果模型最后要输出真实温度值,需要inverse_transform还原。

5.3 Matplotlib 中文是方块,负号也是方块

现象:图标题和图例里的中文全是空心方块,温度图上的负号也变成方块。

原因:Matplotlib默认字体不带中文字形,axes.unicode_minus默认True会用Unicode负号,但默认字体不支持。

解决:在任何绘图代码之前加上两行配置:

plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False

如果是在Mac或Linux上跑,SimHei不存在,换成'PingFang SC'或'WenQuanYi Micro Hei'。最省事的方法:把自己环境的可用中文字体名打印出来看一遍。

5.4 做多步预测时把预测值当成真实值滚进去

现象:预测未来7天温度,前两天准,第三天以后越来越偏,最后偏差好几度。

原因:用“滚动预测”方式把上一步的预测值作为下一步的输入特征。在线性回归模型里,如果特征包含滞后特征,预测第2天用的是第1天的预测值而非真实值,误差一步步累积。

解决:要么只做单步预测(预测一天,然后重新训练或滑动窗口推进),要么在代码里明确区分训练阶段和预测阶段,预测时只允许使用已有的历史值。

这个问题不是bug,是方法论选择。课设答辩时主动说出这一点,反而是加分项——说明你理解误差累积的机制。

5.5 Pyecharts 图表渲染空白,只有标题没有数据

现象:HTML页面打开,标题和坐标轴都在,折线区域却是空的。

原因:常见的是数据类型问题。传入给add_yaxis的数值列表里混入了None值、NaN或者numpy的float64类型,Pyecharts的序列化器无法处理。另一个原因是日期索引和数值列表长度不匹配,页面渲染时静默地丢弃了这段数据。

解决:传值之前统一转换类型。

x_data = [d.strftime('%Y-%m-%d') for d in df['date']] y_data = [float(v) if pd.notnull(v) else None for v in df['temp_high']]

用float()把numpy类型转成Python原生类型,用pd.notnull过滤掉NaN,None值要手动处理或删除对应日期。这行代码能解决大部分Pyecharts渲染空白问题。

5.6 文件路径与编码:换一台电脑就跑不起来

现象:项目在你自己电脑上运行正常,拷给老师或换台电脑后直接报错FileNotFoundError或UnicodeDecodeError。

原因:源码里用了绝对路径,或者文件编码在不同系统间不一致。Windows默认GBK,Mac和Linux默认UTF-8,CSV文件从Windows拷到Mac上用UTF-8读取就会乱码。

解决:项目里所有文件路径统一用相对路径,并放到同一个目录结构下。读取CSV时先用try尝试UTF-8,失败再回退到GBK:

try: df = pd.read_csv('weather_data.csv', encoding='utf-8') except UnicodeDecodeError: df = pd.read_csv('weather_data.csv', encoding='gbk')

这是一个很小的防御性写法,但能让你的项目在别人电脑上少一次“跑不起来”的尴尬。

6. 让模型更可信:残差检验与滚动预测验证

课程设计做完预测和可视化,能得高分的关键一步是验证模型到底靠不靠谱。除了RMSE和MAE,还有两个验证手段值得加进项目里,能让答辩时更有底气。

第一个是残差检验。残差是真实值减预测值的差值序列。好的预测模型,残差应该围绕0随机波动,不应该有明显的趋势或周期性。画一张残差图,再用statsmodels里的acf函数算一下残差的自相关系数,如果前几个滞后的相关系数都落在置信区间内,说明模型已经把时间序列里的规律学干净了——剩下的都是随机噪声。

import statsmodels.api as sm residual = test.values - forecast sm.graphics.tsa.plot_acf(residual, lags=20) plt.title('预测残差自相关图') plt.show() norm_residual = (residual - residual.mean()) / residual.std()

残差自相关图的理想形态是除0之外没有明显超出蓝色置信带的柱状线。如果某个滞后处显著超出置信带,说明模型漏掉了对应天数的周期性规律,这时返回去检查是否需要提高ARIMA的p或q阶数,或者补上更多滞后特征。

第二个是滚动预测验证。固定训练集起点,每次只预测未来一天,然后把真实值加入训练集,再预测下一天。这个过程模拟的是实际使用场景——每天更新数据、每天产出新的预测。课程设计里可以用一个循环实现,把每天预测的MAE单独打印出来,你能直观看到模型在不同天气条件下的表现差异。比如降温天误差普遍偏大,就说明模型对突变天气的学习不充分,需要增加气温变化率这个特征。

这两个验证加进去之后,你的课设从“能出图”提升到了“能解释”。老师问误差来源、问你模型改进方向时,你可以指着残差图说哪个滞后项还有相关性,而不是含糊其辞。我自己做这类项目时,固定下来的习惯是:预测结果必须配一张残差图才敢拿出去展示,否则一条曲线看不出模型内部的问题。这套思路同样适用于数据量更大的项目。

希望这份拆解能帮你的天气预测课程设计少走几步弯路,把源码跑通、把图做清楚、把原理讲明白。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询