简介基于Python的天气预测与可视化课程设计项目已获导师指导并通过成绩97分适合计算机相关专业学生作为课程设计或期末大作业参考。项目覆盖天气数据抓取、数据清洗、模型训练与结果可视化完整流程包含爬虫脚本、特征处理、预测模型与图表展示能帮助学习者快速掌握从数据获取到应用落地的工程思路。压缩包共24个文件大小约1.43MB主要有4个Python源代码文件、4个CSV格式的训练与测试数据集、12张运行效果截图、1个训练好的PKL模型、1个HTML天气网页面以及readme说明文档和gitignore配置文件结构清晰模块划分明确。目前已有654人学习下载。借助该项目读者可以下载即用直接复现结果也可以参考其中数据预处理流程、模型构建方式与可视化思路进行二次开发或在其基础上替换数据源、调整算法完成其他气象分析任务是兼具完整性与扩展性的高分课程设计模板。1. 一份“高分课程设计”值在哪天气预测与可视化项目的完整套路看到“基于Python的天气预测与可视化完整源码数据”这串描述多数人的第一反应是先解压、找代码、点运行。但从我替别人排查这类课程设计的经验看真正让一份作业拿到高分的从来不是LSTM有多复杂而是三件事是否完整数据管线是否干净、预测结果是否可验证、可视化是不是“答辩友好”——最后一项在评分表里的权重往往比模型还高。这个方向适合三类人正在赶课程设计的学生、刚接触时间序列处理的入门开发者、以及想快速验证“Python 天气预测可视化”技术栈能不能用在自己数据集上的工程师。这套项目的本质是一个完整闭环读取一份带时间字段的天气数据清洗并重采样成稳定序列用统计基线或深度模型预测未来温度再把真实值、预测值、误差分布变成能写进报告的图表。它的“高分”不在某一个环节而在于每个环节都有可展示的产出。下文我会按这个闭环拆开讲数据怎么体检、模型怎么选怎么调、图表怎么配、哪些坑必然遇到以及最后怎么把结果整理成答辩和报告都站得住的版本。2. 打开数据先体检读懂天气数据集的字段、编码与时间粒度2.1 编码与日期解析不走弯路的第一项检查课程设计里的天气数据绝大多数是CSV常见字段有日期时间、温度、湿度、气压、风速、风向、天气现象、降水量。拿到手第一件事不是建模而是确认三件事文件编码、日期字段是否被正确解析、缺失值分布。很多翻车现场都始于一步pd.read_csv()读进来日期还是字符串后面resample()直接报错。import pandas as pd df pd.read_csv( weather.csv, encodingutf-8-sig, # 带BOM的CSV用utf-8-sig否则第一列名会变成 \ufeffdate parse_dates[date], # 指定日期列读进来直接是 datetime64 index_coldate, # 设为索引为后面重采样铺路 ) print(df.head()) print(df.dtypes) # 确认温度等列为数值不是 object print(df.isna().sum()) # 看每列缺失数量 print(df.describe()) # 看温度/湿度/气压的分布揪出异常值encoding参数是第一个隐形坑很多网上下载的数据带 UTF-8 BOM直接用utf-8读会让列名多一个看不见的\ufeff后面按列名取值全部 KeyError。parse_dates把日期字符串转成 datetime64这一步做了才能用.dt.hour、resample()这类时间操作。index_col不是必须的但放进索引后画图、重采样、滞后特征都会顺手很多。读进来之后还要做一个快速体检时间索引是否有序、有没有重复。print(df.index.is_monotonic_increasing) # 是否按时间递增False 说明乱序 print(df.index.duplicated().sum()) # 重复时间戳数量常见于多个站点数据拼在一起乱序或重复会直接污染后续的滞后特征——shift()本质是“上一条记录”如果时间乱序上一条根本不是上一个时刻。遇到这两种情况先排序再去重df df.sort_index() df df[~df.index.duplicated(keepfirst)]2.2 缺失值填充与重采样构造干净的时序管线天气数据的缺失通常是传感器空档或某几天没采集量级不大但位置随机。时间序列的填充逻辑和普通表格不一样全局均值填充会抹掉昼夜和季节周期让模型学到错误的偏置直接删除整行又可能把时间轴弄断。我的做法是短空洞用线性插值长空洞用前向填充极端情况下才允许看到连续空洞之后用周期匹配。# 短空洞线性插值最多连续插5个点避免过度外推 df[temperature] df[temperature].interpolate( methodlinear, limit5, limit_directionboth ) # 剩余缺失用前向填充兜底 df df.ffill() # 统一重采样到日粒度方便后续做“明天温度预测” daily df.resample(D).agg({ temperature: mean, humidity: mean, pressure: mean, wind_speed: mean, wind_direction: last, # 风向是类别取当天最后一次观测 condition: last, # 天气现象取末班记录即可 }) daily daily.dropna(subset[temperature])插值的limit5是经验值5 个点以内的空洞用线性插值不会破坏日内曲线超过 5 个点线性插值的可信度急剧下降不如用ffill()保持前一段状态。resample(D)把小时或分钟数据聚合到天agg()里不同列用不同函数——连续变量温湿度、气压用均值离散变量风向、天气现象用最后一条记录。这里要注意如果原始数据本身就是日粒度不需要重采样但至少要做一次时间连续性检查确认没有中间断档。注意重采样前必须先确认索引是 datetime64。若df.index是 object 类型resample会直接抛错常见做法是df.index pd.to_datetime(df.index)补救。完成这一步后面所有模型和图表都在daily这个干净的 DataFrame 上进行。3. 天气预测建模从移动平均基线到LSTM的替换路径3.1 先跑基线移动平均和滞后特征线性回归很多初学者一上来就训练 LSTM忽略了一个事实天气序列尤其是温度短期预测的强基线可能是“过去 7 天均值”或者“昨天温度”。课程设计评分里“有模型对比”是实打实的加分项——先跑基线再让深度学习赢过基线论证才完整。先装环境pip install pandas numpy scikit-learn tensorflow pyecharts matplotlib依赖比较多tensorflow安装体积也大如果只是做温度预测这种单变量序列CPU 版本就够用不需要 GPU。import numpy as np from sklearn.metrics import mean_absolute_error, mean_squared_error series daily[temperature].dropna() # 移动平均基线用过去 7 天均值预测明天 ma7 series.rolling(7).mean().shift(1) # shift(1) 是为了只用历史数据 valid pd.concat([series, ma7], axis1).dropna() valid.columns [actual, ma7] mae mean_absolute_error(valid[actual], valid[ma7]) rmse np.sqrt(mean_squared_error(valid[actual], valid[ma7])) print(fMAE{mae:.2f}℃ RMSE{rmse:.2f}℃)rolling(7)表示滑动窗口大小为 7 天mean()算窗口均值shift(1)把第 t 天算出的均值移动到第 t1 天让预测值只依赖历史。这一步做错会得到一种“看起来完美”的假模型用当天数据预测当天误差接近零但毫无实战意义。移动平均能让分数基准立刻可见。如果它在你的数据集上 MAE 已经很低比如只有 0.8℃说明温度本身有强短期惯性后续模型的目标就是逼近这个下界而不是追求“肉眼看起来特别贴近”的曲线。接着用滞后特征做线性回归这是从统计基线到机器学习之间的过渡台阶from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline def make_lag_features(series, lags(1, 3, 7, 14)): df pd.DataFrame({y: series}) for lag in lags: df[flag_{lag}] series.shift(lag) # 第 t-lag 天的温度作为特征 return df.dropna() lag_df make_lag_features(daily[temperature]) train lag_df.iloc[:-30] # 留出最后30天当测试 test lag_df.iloc[-30:] model make_pipeline(StandardScaler(), LinearRegression()) model.fit(train.drop(columnsy), train[y]) pred model.predict(test.drop(columnsy)) print(fLinearRegression MAE{mean_absolute_error(test[y], pred):.2f}℃)滞后特征的选择有讲究1、3、7、14分别对应惯性、短周期波动、一周周期、两周趋势。对温度这类强周期序列7 天滞后尤其重要因为“上周同一天的温度”往往比“昨天温度”更有参考价值。StandardScaler()在管线里虽然不是必须量纲一致但加上它之后如果换用带正则的模型代码不需要再改。3.2 LSTM 的核心参数与窗口切分让模型真正学会趋势LSTM 在课程设计里的角色是“进阶模型”。它不一定比线性回归好但它让你有得写序列建模、滑窗、归一化、早停、多步预测每一个都是评分点。温度预测用单变量就够了把其他字段加入特征属于加分动作。先把代码跑通再谈调参。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.callbacks import EarlyStopping from sklearn.preprocessing import MinMaxScaler # 1. 划分训练/测试集注意按时间顺序 data_all daily[temperature].dropna() train_data data_all.iloc[:-30] test_data data_all.iloc[-30:] # 2. 归一化只 fit 训练集防止测试集信息进入训练流程 scaler MinMaxScaler() train_scaled scaler.fit_transform(train_data.values.reshape(-1, 1)) test_scaled scaler.transform(test_data.values.reshape(-1, 1)) # 3. 构造滑窗样本 def make_windows(data, lookback7): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) # 过去 lookback 天 y.append(data[i lookback]) # 下一天 return np.array(X), np.array(y) lookback 7 X_train, y_train make_windows(train_scaled, lookback) X_test, y_test make_windows(test_scaled, lookback) # 4. 构建模型 model Sequential([ LSTM(32, activationtanh, input_shape(lookback, 1)), Dense(1), ]) model.compile(optimizeradam, lossmse, metrics[mae]) # 5. 训练 早停 model.fit( X_train, y_train, epochs50, batch_size16, validation_split0.2, callbacks[EarlyStopping(patience5, restore_best_weightsTrue)], verbose0, ) # 6. 预测并回转到原始量纲 pred_scaled model.predict(X_test, verbose0) pred scaler.inverse_transform(pred_scaled) y_test_orig scaler.inverse_transform(y_test.reshape(-1, 1)) print(fLSTM MAE{mean_absolute_error(y_test_orig, pred):.2f}℃)关键参数的作用有必要逐个说清。lookback7是滑窗长度对应“用过去一周预测明天”这个值要跟数据周期匹配天气日数据用 7 合理如果做小时级数据可以用 24。LSTM(32)的 32 是隐藏单元数单变量序列没必要用到 128单元越多越容易过拟合训练时间也成倍增长。epochs50是上限真正能跑满的次数很少EarlyStopping(patience5)监控验证集 loss连续 5 个 epoch 不下降就停并恢复最优权重。validation_split0.2是从训练集内部再切 20% 当验证集和后面那 30 天测试集没关系不要混。提示restore_best_weightsTrue一定要加否则早停后模型权重可能停在最后一个 epoch而不是验证集最优的 epoch回测指标会变差。窗口切分是另一个容易写错的地方。上面make_windows()里data[i:ilookback]和data[ilookback]在原始序列上的下标是错开的标签永远不在输入窗口内。如果图省事用i当标签等于让模型偷看了正确答案测试 MAE 会低得不真实。实践里温度预测的 LSTM 效果通常只比线性回归好一点甚至持平这很正常。真正拉开差距的是多变量特征把湿度、气压、风速拼进输入矩阵让最后两维变成(lookback, 特征数)。这一步不需要改网络结构只需要在构造窗口前把多列一起归一化并把input_shape的第二维改成特征数。不过对课程设计先把单变量版本跑通、把上面这段代码吃透性价比最高。4. 可视化定“高分”四张报告级图表怎么配4.1 选型逻辑pyecharts 与 matplotlib 的分工选可视化工具有一个简单标准答辩和查重看哪个哪个就打主力。我的经验是pyecharts 生成 HTML可以在浏览器里缩放、悬停看数值答辩现场演示起来明显更生动matplotlib 生成静态 PNG适合塞进实验报告、打印到纸质文档。对比项pyechartsmatplotlib输出形式HTML可交互PNG/SVG静态中文支持默认较好无需配字体需手动设置中文字体否则乱码代码风格链式调用配置项集中偏命令式细碎适合场景答辩演示、电子版报告纸质报告、论文插图数据量大配合 DataZoom 缩放容易糊成一片结论不是二选一而是分工趋势对比、热力图这类需要“现场讲解”的图用 pyecharts误差直方图、相关性这类偏统计的图用 matplotlib。两者代码都会在下面给出。4.2 四张图表落地趋势对比、误差分布、热力图、风向玫瑰图第一张是全局趋势对比图也是整个项目的主图横轴时间两条线分别是实际温度和模型预测值。这张图的作用是让评委在 10 秒内知道你做了什么。用 pyecharts 做from pyecharts import options as opts from pyecharts.charts import Line # 只取测试集区间日期转成字符串避免坐标轴渲染异常 dates [d.strftime(%Y-%m-%d) for d in test_data.index] actual y_test_orig.flatten().tolist() pred_list pred.flatten().tolist() line ( Line(init_optsopts.InitOpts(width900px, height420px)) .add_xaxis(dates) .add_yaxis(实际温度, actual, is_smoothTrue, symbolnone) .add_yaxis(预测温度, pred_list, is_smoothTrue, symbolnone) .set_global_opts( title_optsopts.TitleOpts(titleLSTM 温度预测对比测试集), tooltip_optsopts.TooltipOpts(triggeraxis), datazoom_opts[opts.DataZoomOpts(range_start0, range_end100)], yaxis_optsopts.AxisOpts(name温度(℃)), ) ) line.render(temperature_forecast.html)is_smoothTrue让折线更接近真实曲线形态symbolnone去掉数据点圆点在样本多时避免图面变成一串珠子。datazoom_opts加滚动条答辩时可以框选某段看细节这个交互对“现场演示感”贡献很大。日期必须先strftime转成字符串否则 pyecharts 对 datetime 类型的坐标轴支持有时会渲染异常。第二张图是误差分布用来快速判断模型有没有系统性偏差。用 matplotlib 画即可import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams[font.sans-serif] [YaHei, SimHei] # 中文字体 matplotlib.rcParams[axes.unicode_minus] False # 负号不乱码 errors y_test_orig.flatten() - pred.flatten() plt.hist(errors, bins20, edgecolorwhite) plt.axvline(0, colorred, linestyle--, linewidth1) plt.xlabel(预测误差 / ℃) plt.ylabel(天数) plt.title(LSTM 预测误差分布) plt.tight_layout() plt.savefig(error_hist.png, dpi200)误差直方图如果近似正态且在 0 附近集中说明模型基本无偏。如果直方图整体右偏说明系统性地低估温度左偏则相反。报告里写“模型在测试集上平均偏低 0.3℃”这句话时就是看这张图得出的结论。第三张热力图用来展示温度的周期结构适合小时级数据横轴月份纵轴小时颜色深浅表示温度高低。如果只有日粒度数据可以把纵轴换成星期几表达“一周内的温度波动规律”。from pyecharts.charts import HeatMap # 需要小时粒度数据若是日粒度把 hour 换成 weekday pivot daily.pivot_table( valuestemperature, indexdaily.index.hour, # 小时 columnsdaily.index.month, # 月份 aggfuncmean, ) heat_data [ [hour, month, round(float(pivot.loc[hour, month]), 1)] for hour in pivot.index for month in pivot.columns ] heatmap ( HeatMap() .add_xaxis([f{m}月 for m in pivot.columns]) .add_yaxis( 小时, [f{h}:00 for h in pivot.index], heat_data, label_optsopts.LabelOpts(is_showFalse), ) .set_global_opts( visualmap_optsopts.VisualMapOpts(max_35, orienthorizontal), title_optsopts.TitleOpts(title温度-月份-小时热力图), ) ) heatmap.render(temperature_heatmap.html)visualmap_opts的颜色映射要设一个合理的max_比如 35℃否则夏季温度过高会把整个色带拉偏春秋的差异就看不出来。label_opts(is_showFalse)去掉每个格子的数值图形更干净。第四张是风向玫瑰图用Polar实现适合有风向文本字段的数据集from pyecharts.charts import Polar wind_freq daily[wind_direction].value_counts().sort_index() polar ( Polar() .add_schema( angleaxis_optsopts.AngleAxisOpts( datawind_freq.index.tolist(), type_category, ) ) .add(风向频次, wind_freq.values.tolist(), type_bar) .set_global_opts(title_optsopts.TitleOpts(title风向玫瑰图)) ) polar.render(wind_rose.html)风向是分类变量value_counts()统计各方位出现次数type_category让方位名均匀分布在圆周上。如果数据里的风向是 0–360 度的数值则改type_value。这张图不需要作为主图但放在“数据探索”小节里能体现你处理了非数值字段在课程设计的完整性评分里很加分。四张图的搭配逻辑是趋势对比证明模型有效误差分布证明评估可信热力图证明数据理解到位风向玫瑰图证明数据覆盖全面。评分表里要的“工作量”一半藏在这里。5. 踩坑与排查天气预测项目最容易翻车的五个细节5.1 时间索引与数据泄露两条最容易静默翻车的坑坑一日期没解析成时间索引重采样直接报错。现象df.resample(D)抛出 “Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex”或者画折线图时 x 轴文字乱序、完全看不出时间先后。原因read_csv没写parse_dates日期列以字符串形式存在pandas 不知道它是时间。解决回到读取步骤给read_csv加parse_dates[date]然后df.index pd.to_datetime(df.index)兜底再检查df.index.is_monotonic_increasing若是 False 就sort_index()。养成这个习惯后后续所有时间操作都不会再碰这个错。坑二窗口切分时把“未来值”编进了输入。现象训练 loss 降到很低测试集 MAE 只有 0.1℃ 甚至接近 0看起来模型“完美”换一段新数据立刻崩。原因构造 X 和 y 时下标没错开或者用了shift(-1)构造特征让标签日期的真实值进入了输入窗口。这是典型的数据泄露预测变成了“查答案”。解决严格按“X 取[i:ilookback]y 取[ilookback]”的错位方式构造窗口并在写完后做一次验证打印X_train[0, -1, 0]和y_train[0]确认前者是第 lookback-1 天的值后者是第 lookback 天的值两者在时间上必须是一前一后。5.2 归一化、中文乱码与文本特征细节决定分数坑三MinMaxScaler 在整段数据上 fit测试集信息泄露。现象回测指标好到不真实但部署到新地区的天气数据后误差翻倍。原因scaler.fit_transform(全部数据)用了测试集的 min/max 来归一化训练集等价于让模型提前知道了测试集的取值范围。解决先切分再归一化只对训练集调用fit()测试集只调用transform()train_scaled scaler.fit_transform(train_data.values.reshape(-1, 1)) test_scaled scaler.transform(test_data.values.reshape(-1, 1))坑四matplotlib 中文全变成方块。现象图标题、坐标轴标签里的中文显示为一排方框英文数字正常。原因matplotlib 默认字体不包含中文字形。解决在绘图代码顶部配置字体。找不到具体字体名时用plt.rcParams[font.family] sans-serif配合[YaHei, SimHei]兜底如果 Linux 环境没有这些字体安装中文字体包或干脆把中文标题改成英文。pyecharts 默认处理了中文字体所以这类项目我通常把中文图表都交给它。坑五天气现象文本被粗暴编码成 1、2、3。现象模型把“晴”“暴雨”“雾”编码成数值预测结果里晴和暴雨只差 1 个单位完全不合理。原因LabelEncoder给文本强加了不存在的顺序关系模型以为“数值越大天气越极端”。解决对离散天气字段用OneHotEncoder或多标签二元化如果只是做温度预测更稳妥的做法是不把天气文本送进模型只把它留在可视化里当原始描述。课程设计没必要为了“用了所有字段”而牺牲模型的合理性。提示踩坑诊断有个通用口诀——“先看索引再看括号”。时间序列问题先确认DatetimeIndex排序所有指标异常先画一张真实值 vs 预测值的散点图肉眼比数字更快发现问题。6. 从能跑到高分预测验证与报告呈现的三个技巧6.1 用 TimeSeriesSplit 替代随机 K 折让回测可信课程设计报告里写“我用 4 折交叉验证评估模型”之前先想清楚一个问题train_test_split默认随机打乱数据对于天气序列就是灾难——训练集里混着未来数据测试集里混着过去数据指标再好看也是假象。正确做法是TimeSeriesSplit它按时间顺序切分保证每一折的训练集时间永远早于测试集。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits4) scores [] for train_idx, test_idx in tscv.split(daily[temperature].dropna()): train_part daily.iloc[train_idx] test_part daily.iloc[test_idx] # 在这里面重复“训练→预测→计算MAE”的流程 # scores.append(mae_score) print(平均MAE:, np.mean(scores))n_splits4是稳妥的默认值切出的测试集各占 20% 左右。真实报告里不用把每一折的细节都贴出来但要写清楚“采用时间序列交叉验证测试集严格晚于训练集”这一句话就能和那些随机切分的作业拉开差距。6.2 多步递归预测误差累积下的七天预报怎么写课程设计里如果要求“预测未来 7 天”单步模型不能直接输出 7 个值。常见做法是递归预测把第一步的预测值滚动回窗口末尾继续预测下一天。代价是误差会累积越往后越不准这是正常的要在报告里如实说明。def recursive_forecast(model, last_window, steps7, scalerNone): predictions [] current last_window.copy() for _ in range(steps): next_val model.predict( current.reshape(1, current.shape[0], current.shape[1]), verbose0, )[0, 0] predictions.append(next_val) current np.roll(current, -1, axis0).copy() current[-1, 0] next_val # 用预测值替代真实值滚进窗口 return scaler.inverse_transform(np.array(predictions).reshape(-1, 1)) # last_window 是测试集最后 lookback 天的归一化数据 forecast_7 recursive_forecast(model, X_test[-1], steps7, scalerscaler)这段代码写得像一个小零件但它是评分表里“模型应用能力”的重要证明。报告结论里应该有一句类似“七步预测的前三天 MAE 与单步接近后四天上升约 45%误差随时间累积”——这句话比任何调参记录都更能体现你理解序列预测的本质。6.3 报告呈现三件套对着图表把结论说清楚最后一步是组织呈现材料。我会让你把报告里预测相关的部分收敛成三件套一张对比图、一张误差统计表、一段结论。对比图用第 4 章的Line图展示测试集整体效果误差统计表用describe()生成列出 MAE、RMSE、最大低估、最大高估结论控制在 80 字内包含“模型对中长期趋势捕捉较好对极端温度日预测偏差较大”这类具体描述而不是“效果很好”这种空话。result_df pd.DataFrame({ actual: y_test_orig.flatten(), pred: pred.flatten(), }) result_df[error] result_df[actual] - result_df[pred] print(result_df.describe())describe()的输出里有error的均值、标准差、min/max直接改写进报告即可。注意误差的均值如果是 -0.3说明整体低估如果是 0.8说明整体高估要结合误差直方图一起解读不能让表格和文字自相矛盾。我现在拿到任何天气数据第一件事永远是画一条完整的时间序列折线而不是先选模型——先把数据的粒度、缺失、周期看明白再谈 LSTM 还是回归。这个习惯帮我避开了至少一半的返工。希望帮到你。本文还有配套的精品资源点击获取