☰
Python气象数据分析:从数据清洗到可视化报告全流程实践
2026/10/10 14:09:50 网站建设 项目流程

简介:一份围绕气象数据分析的实验型资源包,面向数据分析初学者、选修课学生及需要完成课程设计的人群,完整演示了从中国天气网爬取指定城市天气数据,到清洗整理、绘制雷达图与条形图、并结合实际给出分析说明的全流程。内容包含可直接在Jupyter Notebook中运行的源代码、整理后的实验报告及全部可视化图表,既可用于课程作业模板,也可作为爬虫入门和数据分析可视化的练习素材。

资源共39个文件,压缩包仅1.69MB,其中20张PNG图片是最终生成的雷达图、条形图等分析图表,15个XML文件与4个rels文件共同构成实验报告的Word源文档结构,方便查看报告排版和图片引用关系,整体十分轻量,下载后即可快速运行。

目前已有1853人学习下载。借助这套材料,读者可以掌握天气数据采集、解析、分析与可视化的完整方法,同时学习如何组织一份规范的实验报告,并将这套流程迁移应用到其他行业数据分析任务中。

1. 气象分析:用一份气象数据把数据分析全流程走通

气象分析这个项目,名字听起来像气象专业的事,实际上是一套用 Python 做的数据分析项目实践。核心就一句话:拿一份真实气象站点的逐日观测数据,把数据清洗、探索性分析、相关性检验、可视化报告这条链路完整走一遍。为什么用它来练手?因为气象数据的字段语义非常明确,气温、降水、湿度、风速这些列每一列都看得懂物理含义;缺失值和异常值模式清晰;而且季节周期天然存在,分析完对不对一眼就能验证。新手能在这里把 pandas、matplotlib 的基本功练扎实,熟手可以拿它做插值试验、季节分解和残差诊断。这份资源我按 Python 脚本、气象样本数据、报告实验模板三块整理,照着跑一遍,就能产出一份像样的数据分析实验报告。

2. 数据获取与清洗:拿到气象数据先处理三件事

气象数据在公开渠道并不难找,但真正决定报告质量的是三大前提:数据来源可靠、字段语义明确、缺失和异常处理得当。这一章把资源里数据的常用来源、字段含义和清洗流程说清楚,顺序对了后面基本不返工。

2.1 气象数据的常见来源与字段选择

写数据分析报告的第一步不是写代码,是先搞清楚数据从哪来、每个字段代表什么。资源里用的是气象站点的逐日观测表,常见公开来源有国家级气象站历史观测数据共享服务、气象数据网站,以及 NASA POWER 这类再分析数据集。不管从哪个渠道拿,落到 csv 或 excel 之后字段大同小异,下表是这套资源里保留的核心字段:

字段含义单位连续性
date观测日期-离散
tmax日最高气温℃连续
tmin日最低气温℃连续
temp日平均气温℃连续
precip日降水量mm连续,大量 0 值
humidity日均相对湿度%连续
wind日均风速m/s连续
sun日照时数h连续
pressure日均气压hPa连续

字段选择上有个常见做法:初版分析只保留数值连续性好的列,天气现象、云量这类文本或分类字段留到后面做专项分析,不要一上来全塞进统计里。我一般会把 date 设成唯一索引,其余列统一转成 float,先跑一遍 dtypes 检查,省得后面在类型问题上翻车。

2.2 用 pandas 加载数据并做缺失值画像

先用 pandas 把数据读进来,同时把日期拆成年、月两列,这两列是所有分组统计的前提。

import pandas as pd df = pd.read_csv( "weather_daily.csv", parse_dates=["date"], # 把 date 解析成 datetime date_format="%Y-%m-%d", # 显式指定格式,避免解析失败 ) df = df.set_index("date") # 日期设为行索引 df["year"] = df.index.year # 拆出年份 df["month"] = df.index.month # 拆出月份 missing = df.isna().sum() print("各列缺失值数量:") print(missing[missing > 0]) print(f"总行数:{len(df)}")

这段代码做了三件事:parse_dates 把日期解析成 pandas 的 datetime 类型,配合 date_format 显式指定格式,可以避免默认解析器遇到混合格式时整列回退成字符串;set_index 让日期成为行索引,后面按年按月切片都依赖它;isna().sum() 输出每列缺失值数量,先给数据画个像。有一点要注意:date_format 是 pandas 2.0 以后的写法,老版本里这个参数叫 format,写脚本前先确认环境版本。

拿到缺失画像之后,要区分三类缺失:单点缺失、连续缺失、整月缺失,处理策略完全不同。单点缺失可以用邻近值插补;连续缺失超过一周就不要再插值了,插出来的平滑曲线是假数据;整月缺失只能标记,任何填充都是在编造。下面是一个按年按月分组填充气温缺失值的写法:

# 按年月分组,组内均值填充缺失 df["tmax"] = df.groupby(["year", "month"])["tmax"].transform( lambda x: x.fillna(x.mean()) )

groupby 按年和月分组后,transform 会把填充结果按原索引对齐回每一行,表结构不变。lambda 里的 fillna(x.mean()) 只填充当前组内部的均值,避免用全年均值把冬季缺口填成夏季水平。这里有个容易忽略的细节:如果整组缺失,组内均值本来就是 NaN,transform 之后仍然返回 NaN。我的习惯是填充完再跑一次 isna().sum(),把残余 NaN 数量写进报告的数据质量说明里。

2.3 异常值识别:物理边界与分位数双保险

清洗的第三件事是处理异常值。气象数据有天然优势:物理边界非常清楚。比如某站历史极端最高温 42℃,突然出现一条 58℃,基本可以判定是传感器或录入错误。我一般用两道检查:物理边界直接标记,分位数检查看分布尾部。

cols = ["tmax", "tmin", "temp", "precip"] for col in cols: q01 = df[col].quantile(0.01) q99 = df[col].quantile(0.99) print(f"{col}: 1%分位数={q01:.1f}, 99%分位数={q99:.1f}")

quantile(0.01) 和 quantile(0.99) 给出分布两端的参考值,超过 99% 分位数的记录并不一定错误,极端高温本身就是研究对象的一部分,所以策略是只标记、不删除。我会把可疑行导出成单独文件,在报告实验里写清楚人工核验结果。记住一条:在气象分析里,删除是最后手段,先标记、再核验、再决定,这个顺序能帮你躲过大部分数据质量事故。

提示:所有统计输出在写报告时都要保留一份原始导出文件,数据质量说明应该是报告的第一节,别跳过。

3. 探索性分析:分布、周期与相关检验怎么选方法

数据洗干净之后,进入探索性分析阶段。这一章对应数据分析的方法里最核心的三板斧:描述分布、拆解周期、检验相关。气象分析报告实验的主体内容也全集中在这块。

3.1 描述性统计与分布形态检查

先看整体统计量。pandas 的 describe 是第一个必跑的检查,但只看默认输出不够,要补偏度。

cols = ["tmax", "tmin", "temp", "precip", "humidity", "wind", "sun"] desc = df[cols].describe().T # 转置成变量行为读数 desc["skew"] = df[cols].skew() # 追加偏度列 print(desc.round(2))

.T 把变量转成行、统计量转成列,这样逐行可读;skew() 计算偏度。降水列的偏度会远大于 0,因为大多数日子降水为 0 或很小,偶尔几天暴雨把分布拉成右偏;风速一般轻微右偏;气温的偏度接近 0。看到偏度大的列,后续相关分析要优先选 Spearman 而不是 Pearson,这个判断在 3.3 会用到。

分布检查还可以配合直方图看形态:

import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 3, figsize=(14, 4)) for ax, col in zip(axes, ["temp", "precip", "wind"]): ax.hist(df[col].dropna(), bins=40) # 40 组直方图 ax.set_title(col) plt.tight_layout() plt.show()

bins=40 是经验参数,几千行数据用 40 组能把分布形状显示清楚,太少会丢掉细节,太多会抖动。观察重点:气温接近单峰对称分布,降水是右偏尖峰,风速中度右偏。这些形态判断会直接决定后面选什么统计方法、怎么解读相关系数,别把直方图当成凑数的配图。

3.2 季节周期分析:按月聚合与箱线图

气象数据季节周期天然存在,不拆开看等于浪费数据里最重要的信息。先按月份聚合看均值曲线,再看箱线图判断每月离散度。

monthly = df.groupby("month")["temp"].agg(["mean", "std", "count"]) print(monthly.round(2)) # 按月份绘制箱线图,观察季节分布 df["temp"].plot(kind="box", by=df["month"], figsize=(10, 5))

agg 一次算出月度均值、标准差和样本量,count 顺便检查每月样本量是否稳定。箱线图横轴按月份排列,夏季箱体整体上移、冬季下移,季节规律一目了然。如果某个月份箱体异常宽,说明该月方差偏大,站点可能经历过搬迁或仪器更换,这些背景信息要在报告里交代,否则读者会对离群月份产生困惑。

如果数据跨多个年份,我还建议做一次季节分解,把趋势项、季节项、残差项拆开看。statsmodels 的 seasonal_decompose 是现成工具:

from statsmodels.tsa.seasonal import seasonal_decompose # 逐日数据聚合到月度均值 series = df["temp"].resample("ME").mean().dropna() result = seasonal_decompose(series, model="additive", period=12) result.trend.plot() # 趋势项 result.seasonal.plot() # 季节项

resample("ME") 把逐日数据聚合到月度均值,注意新版 pandas 里 "ME" 是标准写法,老版本写 "M" 也认。period=12 代表 12 个月一个循环;model 参数选 additive 还是 multiplicative,取决于季节波动的幅度是否随水平变化:气温一般加性够用,降水用乘法更贴近实际。分解出来的 trend 项就是做长期趋势判断的干净素材,直接看原始年均值曲线反而会被季节波动干扰。

3.3 变量相关分析:为什么优先选 Spearman

气象分析报告里最有价值的结论往往是变量之间的关系:气温和湿度、气温和降水、风速和气压。计算相关矩阵时,方法选择直接影响结论方向。

import scipy.stats as stats cols = ["tmax", "precip", "humidity", "wind"] pearson = df[cols].corr(method="pearson") spearman = df[cols].corr(method="spearman") print("Pearson 相关:") print(pearson.round(2)) print("Spearman 相关:") print(spearman.round(2))

method 参数是关键:Pearson 衡量线性相关,对离群点和偏态分布敏感;Spearman 基于秩次,只关心单调关系,对降水这种右偏分布稳健得多。实践里我两个都算,如果两者差异明显,比如 Pearson 接近 0 而 Spearman 不为 0,说明变量之间是非线性关系,报告里要专门讨论,不能只挑好看的那个写。

单条相关系数的显著性检验同样要走:

r, p = stats.spearmanr(df["temp"], df["humidity"]) print(f"温度-湿度: r={r:.3f}, p={p:.4f}")

spearmanr 返回相关系数和 p 值,p 小于 0.05 才算显著相关。但气象数据样本量动不动几千条,微小的相关系数也会被检验成显著,所以报告里必须强调:r 的绝对值小于 0.2 时,统计显著不等于实际有意义,解释力非常有限。这一条是新手写分析报告最容易犯的过度解读错误,宁可写得保守也不要吹过头。

4. 可视化与报告实验:Python 出图与结果落地的配合

python数据分析与可视化是这套实践包里观感最直接的部分。统计数字很难让读者直接信服,一张趋势图加一张相关热力图,报告的说服力立刻上一个台阶。资源里所有出图脚本都集中在 analysis_plots.py,改参数就可以复用到别的站点数据。

4.1 温度趋势图:年际曲线与月度季节曲线

先画两幅最有代表性的图:年均气温曲线和月度平均曲线,分别回答长期怎么变、年内怎么波动。

import matplotlib.pyplot as plt yearly = df.groupby("year")["temp"].mean() # 年均气温 monthly = df.groupby("month")["temp"].mean() # 月均气温 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(11, 7)) ax1.plot(yearly.index, yearly.values, color="#2c7fb8", linewidth=1.5) ax1.set_ylabel("年均气温 (℃)") ax1.set_title("站点年均气温年际变化") ax2.plot(monthly.index, monthly.values, marker="o", color="#f03b20") ax2.set_ylabel("月均气温 (℃)") ax2.set_xticks(range(1, 13)) # 强制显示 1 到 12 月 ax2.set_title("年内月均气温季节曲线") plt.tight_layout() plt.savefig("temperature_trend.png", dpi=150)

subplots(2, 1) 生成上下两张子图,返回的 axes 对象按顺序给 ax1、ax2 使用。savefig 的 dpi=150 是报告贴图的最低要求,要提交印刷版再提到 300。ax2.set_xticks(range(1, 13)) 这行容易被漏掉,不设置的话 pandas 会按数据自动挑刻度,可能跳过 11 月、12 月,横轴看起来就是缺的。

以降水量为例,再画一张年累计降水柱状图,考察降水的年际波动:

yearly_precip = df.groupby("year")["precip"].sum() # 每年降水总量 yearly_precip.plot(kind="bar", figsize=(11, 4), color="#3182bd") plt.ylabel("年降水量 (mm)") plt.savefig("precip_yearly.png", dpi=150)

groupby("year")["precip"].sum() 汇总每年降水总量,bar 图适合展示离散年份的对比。注意降水柱状图容易出现个别年份明显偏高或偏低,这可能是真实的旱涝差异,也可能是缺测导致的系统性低估,所以画完要回头对照第 2 章的数据质量说明核对一遍,别直接把图扔进报告。

4.2 相关矩阵热力图:一眼看清变量关系

相关矩阵用 seaborn 热力图展示,是报告里信息密度最高的一张图,通常放在分析章节的开头位置。

import seaborn as sns corr = df[["tmax", "tmin", "precip", "humidity", "wind", "sun"]].corr( method="spearman" # 用 Spearman,原因见 3.3 ) plt.figure(figsize=(8, 6)) sns.heatmap( corr, annot=True, # 格子里显示数值 fmt=".2f", # 保留两位小数 cmap="RdBu_r", # 红正蓝负 vmin=-1, vmax=1, center=0, # 0 值显示为白色 ) plt.tight_layout() plt.savefig("corr_heatmap.png", dpi=150)

annot=True 把相关系数直接写在格子里,fmt=".2f" 控制显示格式;cmap="RdBu_r" 让正相关呈红色、负相关呈蓝色,这是气象报告里的惯用配色,红蓝相反不会引起歧义;center=0 保证 0 值是白色,冷暖对称,看起来不会误解方向。注意这里的相关矩阵用的是 Spearman,原因上一章说过——降水偏态严重,Pearson 会给出误导性数值,出图前别改回默认。

4.3 报告实验的输出组织:数据、图表、结论三件套

报告实验要的不只是一张图,而是一套能复现的记录。我习惯把所有实验结果导出成 csv 和图片,再按模板写数据分析报告,每个结论都能对应到具体输出文件。

summary = df.groupby("month").agg( temp_mean=("temp", "mean"), # 月度均温 temp_std=("temp", "std"), # 月度标准差 precip_sum=("precip", "sum"), # 月度降水总量 ).round(2) summary.to_csv("analysis_results.csv", encoding="utf-8-sig") final_corr = df[["tmax", "precip", "humidity"]].corr( method="spearman" ).round(3) final_corr.to_csv("correlation_results.csv", encoding="utf-8-sig")

agg 用元组语法给每个输出列指定聚合函数,输出列名直接叫 temp_mean、temp_std,比 pandas 默认的列名可读性好很多。to_csv 里的 encoding="utf-8-sig" 是关键参数:Windows 上 Excel 打开带 BOM 的 utf-8 文件才不会中文乱码,这是报告交付环节最常见的坑。实验记录的思路是每个输出对应报告的一节:描述统计支撑数据概况,相关矩阵支撑变量关系讨论,趋势图支撑长期变化结论,三件套齐了,报告实验才算闭环。

5. 避坑与排查:气象数据分析的五个翻车现场

这一章把气象数据分析项目实践里最容易踩的坑集中列出来。每一条都是真实发生过的翻车现场,按现象、原因、解决的顺序写,照着排查能省下大量返工时间。

5.1 日期解析失败:读进来全是字符串

现象:print(df.dtypes) 显示 date 列是 object 而不是 datetime,按年份分组时报错,或者分组结果完全对不上。

原因:csv 文件里的日期格式不标准,2023/1/5 和 2023-01-05 混着写,或者月份用了英文缩写,pandas 默认解析器遇到无法解析的格式,整列回退成字符串。

解决:读取时显式指定格式,用 date_format 参数强制解析规则。不确定文件里是什么格式时,先读前几行看原始字符串再下手,别靠猜。

df = pd.read_csv( "weather_daily.csv", parse_dates=["date"], date_format="%Y-%m-%d", # 格式与文件实际保持一致 )

这个坑几乎每个做气象数据分析的人都会碰一次,排查成本其实很低,就是多写一个参数的事,但漏了它后面所有时间序列操作都白搭。

5.2 缺失值填充把冬季均值拉高

现象:补完缺失之后,冬季平均气温比历史记录明显偏高,1 月均值异常离谱。

原因:用了全局均值填充所有月份的缺失值。冬季本来偏冷,夏季均值混进去之后,冬季的缺口被填成偏高数值,季节特征被彻底破坏。

解决:按年按月分组填充,或者用相邻日期线性插值。分组填充 2.2 里写过,这里补插值写法:

df["tmin"] = df["tmin"].interpolate( method="linear", limit_direction="both", # 首尾缺失也向外推 )

interpolate 对连续几天缺失效果好,limit_direction="both" 表示序列首尾的缺失也向前后外推。但注意:如果某个月整月缺失,插值会把边界连成直线,看着平滑,实际是编造的假数据,必须识别出来并在报告里声明,不能当真实观测用。

5.3 降水字段的 0 与 NaN 混为一谈

现象:降水量为 0 的天数统计出来特别少,或者年降水总量算出来明显偏低。

原因:气象记录里无降水记 0,缺测记 NaN,两者含义完全不同。有人清洗时用 dropna() 把 NaN 行整行删掉,0 值保留,结果样本缺失集中在无降水日上,年降水总量被系统性低估。

解决:先把 0 和 NaN 分开统计,再决定策略。降水缺失不做均值填充,用前向填充或者直接标记,因为均值填充会凭空造出毛毛雨记录。

rain_na = df["precip"].isna().sum() rain_zero = (df["precip"] == 0).sum() print(f"缺测:{rain_na} 天,无降水:{rain_zero} 天")

这个统计必须写进报告实验的数据质量说明里,它有实际业务含义:缺测比例高说明站点维护差,后续趋势分析的可信度要打折,读者一眼就能看出数据的底线在哪。

5.4 相关矩阵出现违背常识的正负号

现象:气温与降水的相关系数算出来是正的,数值还不小,和夏季多雨、冬季干燥的常识完全对不上。

原因:计算相关时没有去掉季节效应。把 1 月到 12 月混在一起算,温度高的月份降水也多,整体看就是正相关,但这种相关其实是季节混叠造成的假象,不是气象学意义上的真实关系。

解决:分季节计算,写进报告更直观:

for season, months in [("春", [3, 4, 5]), ("夏", [6, 7, 8]), ("秋", [9, 10, 11]), ("冬", [12, 1, 2])]: sub = df[df["month"].isin(months)] r, p = stats.spearmanr(sub["temp"], sub["precip"]) print(f"{season}: r={r:.2f}, p={p:.3f}")

这个翻车点特别适合写进报告实验的讨论部分:先说明整体相关受季节干扰,再展示分季节结果,分析深度立刻不一样,评审一眼就能看出你真正理解数据。

5.5 图表中文全部显示成方块

现象:图表标题和图例里的中文变成空心方块,英文和数字正常。

原因:matplotlib 默认字体 DejaVu Sans 不包含中文字形,Windows 和 Linux 上默认配置都不带中文字体支持。

解决:绘图前统一设置中文字体,Windows 常用 SimHei,Linux 用 Noto Sans CJK SC。

import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "Noto Sans CJK SC"] plt.rcParams["axes.unicode_minus"] = False # 修负号方块

axes.unicode_minus 设为 False 解决坐标轴负号显示成方块的问题。这段配置建议放在脚本最开头统一设置,不要每张图调一次,也别指望每台机器字体都一样,多列几个候选字体名是稳妥做法。

6. 进阶用法:滑动平均与线性回归交叉验证趋势

前面把探索分析和出图都跑通了,最后这一步是把结论验证做扎实:年均气温到底有没有上升趋势?只用均值曲线肉眼判断不够客观,我的做法是两条腿走路,先做 12 个月滑动平均去除季节波动,再用线性回归估计趋势斜率。

# 月度序列 + 12 个月滑动平均 monthly = df["temp"].resample("ME").mean().to_frame() monthly["temp_ma"] = monthly["temp"].rolling( window=12, # 12 个月窗口 center=True # 窗口对齐到中间点 ).mean() print(monthly["temp_ma"].tail())

rolling(window=12) 对月度序列做 12 个月滑动平均,center=True 让窗口对齐到中心,曲线比原始数据平滑且没有相位偏移。滑动平均把季节周期抹掉之后,剩下的主要是年际波动,看趋势比直接看逐年线干净得多,这也是气象分析里最常用的平滑方式。

回归部分只取近 20 年数据,这个窗口长度在气象分析里比较常用:太短容易被单一年份极端天气带偏,太长又混进多年代际震荡。

yearly = df.groupby("year")["temp"].mean() slope, intercept, r_value, p_value, std_err = stats.linregress( yearly.index.astype(int), yearly.values, ) print(f"趋势斜率={slope:.4f} ℃/年, R²={r_value**2:.3f}, p={p:.3f}")

linregress 返回斜率、截距、相关系数、p 值和标准误五个值,这套输出足够支撑报告里的趋势判断。两个细节必须强调:第一,滑动平均后的序列存在自相关,回归得到的 p 值会偏乐观,严谨一点需要做块自举或 HAC 标准误校正;第二,斜率单位是 ℃/年,写报告时要换算成每十年变化量,读者才直观。气象分析报告实验做到这一步,已经比大多数课程作业多了一个层次,不满足于画出趋势,而是对趋势做显著性检验。

从那以后我每次跑气象数据分析,都会强制走一遍交叉验证流程:先画原始图,再滑动平均,最后回归出斜率,三个结果对不上就回头查数据。做数据分析最怕结论只来自一张图、一个数字,没有第二重验证兜底。希望帮到你。

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

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

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

立即咨询