☰
Python天气预测与数据可视化课程设计:从数据清洗到随机森林模型实战
2026/10/1 23:00:13 网站建设 项目流程

简介:面向高校期末大作业或课程设计,这份Python项目实现了天气预报与数据可视化功能,下载后无需修改即可运行,适合作为高分参考,涵盖天气预测与可视化两大模块,利用线性回归、时间序列分析等模型处理气象数据,并通过Matplotlib、Seaborn等工具生成折线图、柱状图与热力图。压缩包共包含二十四个文件,主要涉及四个Python源码(对应数据获取、处理、建模与主程序逻辑)、四份CSV数据集、一个已训练模型、一个HTML页面及一份使用说明文档,另附十二张效果预览图,整体仅一点四二MB,结构清晰,便于按模块对照学习,目前已有六十二人学习下载。借助模型文件与示例图表,读者可快速理解预测流程,也可依据源码和文档进行二次开发,是巩固Python数据分析与可视化技能的实用案例,项目还附带了天气网HTML和图片,为作业答辩或成果展示提供直观支撑。

1. 从天气预测到数据可视化:一套能直接跑完的 Python 课程设计源码

期末周最怕的不是写代码,而是写完发现跑不通。这套基于 Python 的天气预报与数据可视化项目,是我拆过最省心的课程设计源码:下载解压、装好依赖、直接运行 main.py,就能输出未来几天气温走势和可视化图表,预测部分由 Model.pkl 支撑,无需再碰训练细节。

它的定位很清楚:给 Python 期末大作业、课程设计提供一份高分参考。源码按 GetData→ProcessData→GetModel→main 拆成四个模块,每个文件干一件事,附带 readme 和网页版可视化结果,既能交作业,也能当答辩素材讲清楚整条数据链路。

下面把文件分工、执行顺序、依赖环境和我实际运行中踩过的坑全部展开。新手照着步骤能复现,熟手可以直接看第五节排查记录和第六节的图表验收技巧。

2. 数据获取链路:GetData.py 怎么拿数据,四份 CSV 各自管什么

2.1 先看懂文件分工:四份 CSV 的角色差异

进入代码之前,我建议先把压缩包里这几份 CSV 的用途对清楚。很多同学拿到项目第一反应是直接运行 main.py,跑完截图就交差,结果答辩被问「date_train.csv 和 china_today.csv 到底谁喂给模型」就卡住了。这个问题不难,但现场卡住很伤。

文件角色典型内容使用阶段
date_train.csv训练集历史日期对应的气温、湿度、气压等特征和真实标签GetModel.py 训练
date_valid.csv验证集与训练集同口径的样本,调参时只做评估不参与训练GetModel.py 调参
date_test.csv测试集最近的待预测样本,交给 main.py 生成预报结果main.py 预测
china_today.csv城市当日快照城市维度的实时天气,给网页地图坐标点展示可视化阶段

这三份 date 开头的 CSV,实际上对应训练、验证、预测三个时间窗口。date_train 覆盖的历史周期最长,date_valid 是中间一段,date_test 是最末尾的一段。做过时间序列预测的人都知道,这种切法比随机切分更接近真实场景:模型学到的是趋势,而不是记住某几天的答案。顺序切分带来的泛化性,答辩时讲出来是加分项。

china_today.csv 则完全是另一个用途。它是城市维度的当日天气快照,给网页端地图展示用的,跟模型没有直接关系。把这条线分清之后,再看 GetData.py 就明白它至少承担两件事:拉取实时天气写入 china_today.csv,以及整理历史数据生成 train/valid/test。

2.2 GetData.py 的采集脚本:requests 拉数据的写法

源码里的 GetData.py 是数据链路的起点。它的核心逻辑就是向天气接口发请求、解析返回值、落盘成 CSV。我拆的时候顺手写了一个最小可用版本,方便你看清每个参数在干嘛:

import requests import pandas as pd from datetime import datetime # 城市列表,按自己的需要增删 CITIES = ["北京", "上海", "广州", "深圳", "成都"] def fetch_city_weather(city: str) -> dict: """拉取单个城市当天天气,返回结构化字段""" url = "https://xxx/api/weather" params = { "city": city, "key": "your_api_key", # 换成自己的 API 密钥 "unit": "metric", # 摄氏度而不是华氏度 } # timeout 设 10 秒,避免某个城市拖垮整个采集任务 resp = requests.get(url, params=params, timeout=10) data = resp.json() return { "city": city, "date": datetime.now().strftime("%Y-%m-%d"), "temp": data["now"]["temp"], # 当前气温,单位摄氏度 "humidity": data["now"]["humidity"], # 相对湿度,单位 % "pressure": data["now"]["pressure"], # 气压,单位 hPa "wind_dir": data["now"]["wind_dir"], # 风向,文本字段 } rows = [fetch_city_weather(c) for c in CITIES] df = pd.DataFrame(rows) df.to_csv("china_today.csv", index=False, encoding="utf-8-sig") print(df.head())

这段代码的要点不在请求本身,而在三个容易被忽略的参数。第一个是 timeout,网络请求最忌讳不设超时,接口假死会卡住整个采集任务;第二个是 encoding,写 CSV 用 utf-8-sig,Windows 上 Excel 打开才不乱码;第三个是 unit=metric,不同平台默认单位不一样,不统一的话后面建模全是错值。

如果要补历史数据,通常是把同一个接口按日期参数循环请求,每天响应追加进同一个 DataFrame,最后按日期去重。这类脚本运行时间是分钟级,我一般会在循环里加 time.sleep 限速,请求太频繁会被服务端封 IP。

2.3 数据源选型:公开 API 和本地 CSV 怎么取舍

如果 GetData.py 内置的是免费天气接口,那它通常有几个限制:日请求配额、历史数据不完整、返回字段不统一。课程设计阶段我建议分两步走。

第一步,确认接口能稳定返回当前天气和近期预报,用上面的脚本把 china_today.csv 跑通,保证可视化部分有真实数据。第二步,核对训练用历史数据的覆盖时长。免费接口往往只给最近几十天,训练集太小,模型和作业展示效果都会打折。备选方案是自己在本地整理历史 CSV,从一个可靠平台导出近两年的天气记录,整理成 date_train.csv 需要的列,这比纠结接口稳不稳定更实际。不少高分大作业的训练数据就是这么来的——评分看的是数据链路是否完整,不是数据集有多大。

这里还有一个常见误解:认为每次运行 main.py 前都必须跑 GetData.py。实际不是这样,main.py 读的是已经躺在目录里的 CSV,只有你想更新数据时才需要重新采集。源码把数据文件和采集脚本拆开,目的就是让你把训练和预测跑在稳定数据上。

拿到 CSV 后别急着跑模型,先做一次粗检:用 pandas 读进来,看行数、列数、缺失值比例,再按日期 groupby 看有没有重复。我见过历史数据源在节假日或极端天气时出现整段缺失,比如某城市少了三十天记录,训练阶段就会变成异常的误差尖峰。

3. 数据清洗与特征构造:ProcessData.py 的预处理链路

3.1 清洗环节:缺失值、类型转换和异常值

ProcessData.py 处理的是模型喂进去之前的最后一公里,也是最容易被轻视的一公里。爬下来的天气数据通常带着三个毛病:字段类型不对、日期格式不统一、个别异常值像钉子一样扎在序列里。不处理直接训练,模型会把噪音当成规律。

清洗的第一步是统一类型。date 列要转成 datetime,温度、湿度、气压要转成数值型,风向这种文本列要决定保留还是编码。下面这个函数展示了最常见的清洗操作:

import pandas as pd def clean_weather_data(df: pd.DataFrame) -> pd.DataFrame: """标准清洗流程:类型转换、缺失值、异常值""" # 日期统一成 pandas 时间戳,之后才能按时间窗口切分 df["date"] = pd.to_datetime(df["date"], errors="coerce") # 关键数值列统一成 float,爬下来是字符串的情况很常见 for col in ["temp", "humidity", "pressure"]: df[col] = pd.to_numeric(df[col], errors="coerce") # 按时间排序后做前向填充,比直接填均值合理 df = df.sort_values("date").reset_index(drop=True) df[["temp", "humidity", "pressure"]] = ( df[["temp", "humidity", "pressure"]].ffill() ) # 异常值:气温低于 -50 或高于 60 直接裁剪 df.loc[df["temp"] < -50, "temp"] = -50 df.loc[df["temp"] > 60, "temp"] = 60 return df

这段代码有三处值得展开。ffill() 是按时间顺序用前一个有效值填充缺失,对气象序列来说,前一天的观测值比全局均值更接近当天状态;errors="coerce" 把无法解析的脏值变成 NaN,交给后续步骤处理;异常值用硬边界裁剪而不是删除整行,因为删除行会在时间序列里挖出空洞,破坏连续性。

很多教程喜欢用 df["temp"].mean() 填缺失,那适合独立样本,不适合天气这种强自相关的时序数据。这是我在实际项目里改用前向填充后,模型误差明显下降的原因。填充方法一改,验证集 MAE 可能差出 0.5 度以上,这个量级足以影响作业评分。

3.2 特征构造:从日期里拆出模型真正关心的信息

原始数据只有日期、温度、湿度、气压,直接丢给回归模型也能跑,但特征太单薄。日期本身是有信息的:是几月、星期几、一年第几天,气温走势和这些强相关。所以特征构造的重头戏是从 date 列拆出一组新特征。

def build_features(df: pd.DataFrame) -> pd.DataFrame: """从日期列构造时间特征,并加入滞后期特征""" df = df.copy() dt = df["date"] # 时间基础特征:月份、星期几、一年中的第几天 df["month"] = dt.dt.month df["dayofweek"] = dt.dt.dayofweek df["dayofyear"] = dt.dt.dayofyear # 滞后特征:昨天的气温对今天有直接参考价值 df["temp_lag1"] = df["temp"].shift(1) df["temp_lag2"] = df["temp"].shift(2) # 滑动平均:近三天平均气温,平滑短期波动 df["temp_ma3"] = df["temp"].rolling(window=3).mean() # 预测目标:明天的气温,也就是监督学习的标签 df["target"] = df["temp"].shift(-1) # 去掉没有目标值和滞后特征的头部记录 df = df.dropna().reset_index(drop=True) return df

shift(1) 生成滞后特征,让模型知道昨天的温度;rolling(3).mean() 生成滑动平均,相当于给模型一个近期趋势基线;target 用 shift(-1) 取明天的气温,是监督学习的标签。dropna 会丢掉序列头尾几行,这是边缘损耗,不用心疼。

这里有个容易犯的错误:把 target 本身也当特征喂进模型,这叫数据泄漏。检查方式很简单,训练特征矩阵里不允许出现未来时刻的值,否则评估阶段的精度虚高,答辩时被追问两句就露馅。泄漏问题在课程设计里是最常见的高级错误,主动在 readme 里写一句「特征中不包含任何未来信息」,老师会觉得你真正理解了监督学习的边界。

3.3 三份数据统一口径:train/valid/test 的切分纪律

ProcessData.py 除了清洗和构造特征,还要让 date_train、date_valid、date_test 三份数据的特征口径完全一致。同样是 month 字段,训练集覆盖 1 到 12 月,测试集只落在最近两周,这没问题;但如果训练集做了边缘裁剪、测试集没做,模型加载时直接报维度错误。

处理多份文件时,最稳的做法是让三份数据走同一个函数:

def process_all(): """三份数据走同一套清洗和特征逻辑,并校验特征列一致""" paths = { "date_train": "date_train.csv", "date_valid": "date_valid.csv", "date_test": "date_test.csv", } result = {} for name, path in paths.items(): df = pd.read_csv(path) df = clean_weather_data(df) df = build_features(df) result[name] = df print(f"{name}: {df.shape}") # 特征列必须完全一致,顺序也不能差 cols = result["date_train"].columns assert result["date_valid"].columns.equals(cols) assert result["date_test"].columns.equals(cols) print("columns check passed") return result

这个设计的核心就一句话:特征逻辑只维护一份代码,三份数据都调同一个函数。shape 不同是正常的,训练集样本多、测试集样本少,但列名和列顺序必须一致。加了 assert 之后,如果后续训练阶段报维度错误,可以先回来查这里。

切分还得强调一点:必须按时间顺序,不能随机切分。随机切分会让验证集里混入训练窗口之前或之后的数据,模型等于偷偷看过答案,验证分数的参考意义直接归零。date_train 年份靠前、date_valid 居中、date_test 最近,这样的安排是站得住脚的。

注意:assert 检查只能拦住列完全对不上的情况,如果某列只是取值区间变了,assert 不会报错。跑模型前最好把两边的 describe 打印出来人工过一眼。

4. 训练模型与持久化:GetModel.py 怎么把随机森林装进 Model.pkl

4.1 模型选型:为什么是随机森林而不是深度学习

课程设计阶段的数据量通常在几百到几千行之间,深度学习在这个量级上没有优势,反而容易过拟合,调参成本也高。随机森林这类树模型是更稳妥的起点:对异常值和缺失值容忍度高、不需要特征缩放、还能输出特征重要性,答辩时能讲出东西。

线性回归也要提一下,它可以作为对照基线。如果线性回归和随机森林在验证集上差得不多,说明目标变量和特征基本是线性关系,这在气温预测里是常见现象。时间序列分析里的回归方法也是可选方向,但对多维特征的处理不如树模型灵活,所以多数方案会把随机森林作为主力。

Model.pkl 就是这个主力模型的持久化产物。训练完序列化到磁盘,main.py 运行时直接加载,不需要重训。这里用 joblib 而不是 pickle,是因为 joblib 对大数组和 sklearn 模型对象的序列化更稳,默认支持内存映射,加载速度也更快。

4.2 训练脚本与模型保存:关键参数怎么设

GetModel.py 的完整流程是读入已经处理好的 train/valid,指定特征列,训练,评估,保存模型。下面这段代码我按通用实现整理了一遍,源码里算法可能不同,但职责是一样的:

import pandas as pd import joblib from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error def train_and_save(): # 读入已经过 ProcessData 处理的数据 train = pd.read_csv("date_train.csv") valid = pd.read_csv("date_valid.csv") # 特征列排除 date 和 target,按顺序固定下来 feature_cols = [c for c in train.columns if c not in ["date", "target"]] X_train = train[feature_cols] y_train = train["target"] X_valid = valid[feature_cols] y_valid = valid["target"] # 随机森林:树偏多、深度限制,防过拟合 model = RandomForestRegressor( n_estimators=200, max_depth=8, min_samples_leaf=3, random_state=42, ) model.fit(X_train, y_train) # 训练集和验证集各评估一次,看是否过拟合 mae_train = mean_absolute_error(y_train, model.predict(X_train)) mae_valid = mean_absolute_error(y_valid, model.predict(X_valid)) print(f"train mae: {mae_train:.2f}, valid mae: {mae_valid:.2f}") # 序列化到 Model.pkl,main.py 直接加载 joblib.dump(model, "Model.pkl") if __name__ == "__main__": train_and_save()

参数方面,n_estimators=200 控制树的棵数,树越多训练越慢,精度不一定线性提升,超过 300 之后收益通常很小;max_depth=8 限制单棵树深度,防止过拟合;min_samples_leaf=3 保证每个叶子至少有 3 个样本,减少噪声分支。random_state=42 固定随机种子,确保每次运行结果可复现,这一点在作业说明里写一句会很加分。

训练完看两个 MAE。如果训练 MAE 远低于验证 MAE,说明过拟合,调低 max_depth 或加大 min_samples_leaf;如果两个都很高,说明特征不够或数据质量有问题,回头查 ProcessData.py。气温预测的 MAE 落在 2 到 3 度内是合理的,超过 5 度就要怀疑是数据的问题而不是模型的问题了。

4.3 验证集的价值:调参不能只看训练集

很多初学者盯着训练集精度高就觉得大功告成,这是危险的错觉。模型在训练集上表现好,只能说明它记住了训练样本,不能说明它对未来的天气有预测力。评分老师真正关心的是验证集和测试集上的表现,调参决策也应该只看验证集。

手动试几个档位就足够。比如把 max_depth 从 6 调到 12、n_estimators 从 100 调到 300,每次记录验证集 MAE,画一张小表,就能看出趋势。这个表放进大作业报告里,比任何理论叙述都有说服力,因为它展示了真实的调参过程。

Model.pkl 里保存的是训练好的模型结构和参数,不包含训练数据。所以 main.py 加载它做预测时,并不需要重新读入 date_train.csv。这解释了为什么压缩包里可以同时放下 CSV 和 pkl,也能说明为什么 main.py 的运行速度和训练速度完全不是一个量级——加载一个序列化的模型是秒级,训练一次模型可能到分钟级。

还要提一个边界:如果你换了新数据或改了特征,旧 Model.pkl 必须重新训练生成,否则模型拿到的特征分布是陌生的,预测结果不可信。每个版本的数据对应一个版本的模型,这是工程上的建模纪律。

5. 运行与排查:main.py 启动顺序、依赖环境与常见翻车

5.1 main.py 的执行流程设计

main.py 是这个项目交付时的门面。它的典型流程是:加载 Model.pkl → 读入 date_test.csv → 按同一套特征逻辑构造预测特征 → 调用 model.predict 得到未来几天气温 → 用 matplotlib 出图并以天气网.html 的方式展示结果。跑通之后,终端能看到预测值,目录里多出图表文件。

如果你打开 main.py 发现它比你预期的短,不用奇怪。模型训练已经在 GetModel.py 里做完了,main.py 只负责推理和展示,这是合理的分工。如果运行时报错,先按流程一环一环定位:报错在加载模型,就检查 Model.pkl 是否存在;报错在特征构造,就回头看测试集有没有经过 ProcessData.py 的处理。大部分报错信息会精确到文件和行号,照着拆就行。

5.2 依赖环境:Python 版本、sklearn 安装、vscode 配置

这个项目依赖的第三方库不多,核心是 pandas、numpy、scikit-learn、matplotlib。先做一件事:检查当前 Python 能不能导入这些库。

python -c "import pandas, numpy, sklearn, matplotlib; print('ok')"

如果报 ModuleNotFoundError,说明依赖没装齐。常见做法是一次装完:

python -m pip install pandas numpy scikit-learn matplotlib

注意这里刻意用了 python -m pip,而不是直接 pip。原因是 Windows 上同时存在多个 Python 版本时,pip 可能指向另一个解释器,装完还是 import 不到。python -m pip 能保证安装目标就是当前终端里那个 python。

vscode 里跑项目还要确认解释器路径。最经典的翻车现场是:在系统终端里 pip install 装到了 Python 3.11,vscode 右下角却选中了 Python 3.9,于是 import pandas 直接报错。我一般在 vscode 终端里先跑一遍上面的 python -c 检查,过了再打开 main.py。这一步能过滤掉八成环境问题。

提示:如果项目文件里有 readme.md,里面写了当前环境建议的 Python 版本,尽量对齐。sklearn 新版对旧模型的兼容性整体不错,但有些老模型文件用新版库加载时会提示版本不一致,处理办法是重新运行 GetModel.py 生成新的 Model.pkl。

5.3 常见问题排查记录

下面是这个项目最容易翻车的几个点,每一条都按现象、原因、解决问题的顺序写。

现象 1:运行 main.py 报 ModuleNotFoundError: No module named 'sklearn'原因:库没有安装在当前解释器里,或 pip 装到了另一个 Python。 解决:先跑 python -m pip install scikit-learn,再跑 python -c 验证。如果还报错,把终端里的 python 路径打印出来,和 vscode 选择的解释器路径对比,必须一致。这个坑在 Windows 上出现频率最高,因为系统里往往有多个 Python。

现象 2:提示维度错误,说 predict 传进去的特征数量不对原因:测试集的特征列和训练模型时的特征列不一致,常见于重新生成了 CSV 但没有走 ProcessData 的统一处理。 解决:把 date_train.csv 和 date_test.csv 的列名列表打印出来对比,先确认 date 列没有被当成特征——我见过最高频的场景就是处理测试集时忘了排除 date,导致多了一列。再确认特征列顺序和数量完全一致,建议直接用第 3 章的 process_all 函数统一处理。

现象 3:CSV 里的中文乱码原因:文件写入时用了默认编码,或读取时没有指定 utf-8-sig。 解决:统一用 encoding="utf-8-sig" 读写。如果乱码已经存在,用 pandas 带编码参数重新读一遍再另存。图表里的中文乱码是另一回事,那是 matplotlib 缺中文字体,需要手动指定字体路径,这也是很常见的坑。

现象 4:Pandas 版本升级后出现 FutureWarning 或运行报错原因:老代码用了 fillna(method="ffill") 这类在 pandas 2.0 里已被标记废弃的写法,新版本仍然会警告甚至报错。 解决:把 fillna(method="ffill") 改成 df.ffill(),然后看输出,确认没有其它废弃写法。这里提醒一下,拿到老源码不要急着升级 pandas 到最新版,先在当前环境跑通再说,别给自己加戏。

6. 可视化与验收:把预测结果做成能拿高分的图表

6.1 两条出图路径:静态图和网页交互图

项目的可视化分两支。main.py 里通常用 matplotlib 出静态图,适合贴进作业文档;天气网.html 是网页端交互展示,打开浏览器就能看到城市地图和温度趋势,答辩演示时很加分。网页端的实现常见做法是 ECharts,把数据导成 JSON,页面脚本读取后绘制折线图和地图散点。这部分的核心不是代码量,而是结果让老师能一眼看懂。

静态图建议先画两条曲线:预测值和真实值叠在一起。对测试集来说,如果 target 列保留着,直接对比一目了然。

6.2 验证模型的一个技巧:先看残差再看曲线

图表不只是给别人看的,也是给你自己看模型的。先把预测残差按日期画出来,如果残差围绕 0 上下无规律波动,说明模型没有系统性偏差;如果从某一天开始误差持续为正或持续为负,说明时间窗口切分有问题,或者数据在那个区间发生了分布漂移。

import pandas as pd import joblib import matplotlib.pyplot as plt # 加载模型并对测试集预测 model = joblib.load("Model.pkl") test = pd.read_csv("date_test.csv") feature_cols = [c for c in test.columns if c not in ["date", "target"]] pred = model.predict(test[feature_cols]) # 如果测试集里保留真实值,可以直接对比 if "target" in test.columns: resid = test["target"] - pred plt.figure(figsize=(10, 3)) plt.plot(test["date"], resid, marker="o", linewidth=1) plt.axhline(0, color="red", linewidth=1, alpha=0.6) plt.title("Prediction Residual") plt.tight_layout() plt.savefig("residual.png", dpi=150) plt.show()

这个检查花的时间不到一分钟,但在答辩时能回答「你的模型预测准吗」这个问题:直接给出验证集 MAE 和残差图,比任何口头描述都硬气。从那以后我每次拿到这类预测项目,都会强制自己先画残差再画结果图,确认残差稳定在零轴附近才开始调可视化样式。希望帮到你。

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

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

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

立即咨询