简介:这是一份面向数据分析初学者的气象数据实战项目,适合希望掌握爬虫采集、数据清洗与可视化完整流程的读者。项目以中国天气网某城市天气数据为对象,通过爬虫获取原始信息,再借助Jupyter Notebook完成分析,并输出雷达图、条形图等多种可视化图表,附带相应分析结论,可帮助巩固数据报告撰写能力。压缩包共39个文件、约1.69MB,主要包含20张png分析图表、15个xml配置/文档结构文件及4个rels关系文件,另有可直接运行的源代码与整理完毕的实验报告,结构清晰,便于对照学习。已有1852人学习下载,适合课程实验、毕业设计或自学练手场景。资源中代码与报告配套呈现,读者既能参考爬虫写法,也能借鉴图表绘制和分析表述思路,对快速上手完整的数据分析流程有直接帮助。
1. 气象数据分析:从一个完整项目看懂数据分析和报告实验的全流程
这个资源不是教学课件,而是一套能直接跑通的气象数据分析项目样本:里面带气象观测数据集、Python 清洗与分析脚本,还有一份可以改写成实验报告的结果模板。换句话说,你拿到的不是零散代码,而是从原始气象数据到分析结论的完整落地方案。它主要解决三类人的问题:要做数据分析报告实验却不知道从哪下手的入门者、刚接触气象数据想参考分析方法论的从业者,以及想找一个真实数据分析案例练手的求职者。气象数据的优点是规律性强、结论容易验证,非常适合把数据清洗、分析方法、可视化、报告撰写这条链路完整走一遍。
2. 数据准备阶段:气象数据清洗与字段对齐的三个关键环节
做过数据分析项目的人都清楚,最耗时间的往往不是分析本身,而是数据准备。这份资源里的气象数据集是原始观测记录,站号、观测时间、气温、湿度、气压、降水这些字段混在一起,字段命名不统一、缺失值分布不均、单位五花八门,直接跑分析必翻车。我自己拆这个资源的第一天,70% 的精力都花在把数据洗干净、把字段对齐这件事上。气象数据尤其如此,它来自自动站设备,设备故障、传输中断都会在数据里留下痕迹,清洗阶段不处理好,后面所有分析都建立在沙子上。
2.1 先看数据字典和字段类型,别急着跑分析
打开资源包,我先找到随附的数据字典,确认每个字段的含义。气象站数据一般包含这些字段:站号、观测时间、气温、相对湿度、气压、风向、风速、降水量。不同来源的数据字段命名差异很大,有的叫 temp,有的叫 temperature;降水有的叫 prep,有的叫 rain。字段名不统一就动手写分析,后面每写一步都要回头查字段,非常低效。我的习惯是先做一次字段标准化,把分析要用的字段统一改名,再进入后续步骤。
import pandas as pd df = pd.read_csv('weather_data.csv', encoding='utf-8') print('数据规模:', df.shape) print('字段类型:') print(df.dtypes) print('前5行:') print(df.head())这段代码干的活有三件。df.shape 看数据有多少行多少列,判断数据量级是否和资源说明一致,防止拿到的是残缺样本;df.dtypes 看每个字段的类型,这一步能提前暴露大量问题,比如时间列被读成 object、站号列被读成 int 导致前导 0 丢失;head() 看前几行,直观感受数据长什么样,对后续清洗心里有数。读进来之后,我还会顺手检查有没有完全重复的行,气象数据里偶尔会出现相同时间点的重复观测记录,这种重复行不删掉,后面按时间聚合时会双倍计入。
提示:气象数据文件常见的编码是 utf-8 或 gbk,如果读取后中文乱码,把 encoding 换成 'gbk' 再试。我一般会在读取时写一个两行容错逻辑,先尝试 utf-8,报 UnicodeDecodeError 再回退 gbk,省得每次手动试。
2.2 缺失值处理:先算缺失比例,再决定插补方式
气象数据的缺失值非常常见,自动站设备故障、传输丢包、人工站缺测都会造成空值。处理缺失值的第一步不是直接补,而是先算缺失比例,缺多少决定采取什么策略,这个顺序不能反。一上来就 fillna,等于对数据质量状况完全没有概念,补出来的结果也没法在报告里解释。
missing_ratio = df.isnull().sum() / len(df) print('各字段缺失比例:') print(missing_ratio[missing_ratio > 0])我的经验是分三档处理。缺失比例低于 5% 的字段,用线性插补或前向填充,对连续变量影响很小;5% 到 20% 之间的,结合前后日值和同站历史同期均值综合补,单一方法都不够稳;超过 20% 的字段直接建议从分析里剔除,硬补出来的数据写进报告反而经不起评审追问。气温这类连续变量适合线性插补,而降水是事件型变量,0 代表无降水,不能按普通缺测处理。
df['temp'] = df['temp'].interpolate(method='linear', limit_direction='both') df['precipitation'] = df['precipitation'].fillna(0)这里的关键差异在最后一行:降水缺测统一填 0,而不是插补。原因很简单,插补会把缺测值变成一个小数,报告里出现 0.3mm 的降水数据,懂行的人一眼就能看出是补出来的。fillna(0) 配合插补,既保留了连续变量的平滑性,又避免降水变量出现伪数据。另外,interpolate 的 limit_direction='both' 表示序列开头和结尾的缺失也要补,如果不加,首尾的缺失值会原样保留为 NaN,后面计算均值时会带上空值,结果表里 count 和 mean 对不上。
2.3 时间字段规范化与单位统一
气象数据最麻烦的是时间格式不统一。有的文件里是 2023-01-01 08:00:00,有的是 2023/1/1 8:00,还有的是 20230101080000 这种纯数字串。不统一时间格式,后面所有按时间聚合的操作都会出问题,比如按月份 groupby 时把年份也混进去,月度曲线出现锯齿。
df['datetime'] = pd.to_datetime(df['datetime'], format='%Y-%m-%d %H:%M:%S') df = df.sort_values('datetime').reset_index(drop=True)pd.to_datetime 是 pandas 里最值得熟悉的函数之一,format 参数明确指定输入格式,解析速度快而且不会猜错。sort_values 按时间排序后重置索引,保证后面按时间滑动的操作都有序。单位也需要检查:气压有的文件给 hPa,有的给 kPa;风速有 m/s 和 km/h 两种。统一单位的做法是先确认原始单位,再乘换算系数,我一般会把这步写成独立函数,避免换算散落在各处。
| 字段 | 常见单位 | 标准单位 | 换算系数 |
|---|---|---|---|
| 气压 | hPa | hPa | 1 |
| 气压 | kPa | hPa | 10 |
| 风速 | m/s | m/s | 1 |
| 风速 | km/h | m/s | 3.6 |
另外要注意时区口径。国内气象数据习惯用北京时间,但部分自动站原始文件可能记录的是 UTC 时间,两者相差 8 小时。如果不做转换,逐时数据和日界对齐会出现偏差,日最高气温也可能落在错误的日期上。拿到数据先确认元信息里标注的时区,再决定要不要加 8 小时。
提示:如果 format 解析报错,说明数据里有脏格式,比如混入了空字符串或别名。这时候先 astype(str) 再逐个排查,不要直接硬删行,先确认脏数据占比再决定清洗方式。
3. 核心分析方法:从统计指标到气象趋势判断
数据清洗完之后进入分析方法部分。做数据分析实验,最忌讳的是堆图,把能画的图全画一遍,报告里一个解释都没有。正确做法是每个分析方法都有明确目的,每个输出都能对应一条结论。气象数据的分析方法其实不复杂,常用的就是描述性统计、相关性分析、时间序列聚合和可视化这几板斧,关键是每板斧要用在正确的场景里。
3.1 描述性统计与相关性分析定位变量关系
第一步永远是从描述性统计开始。它回答的是“数据大概是什么情况”这个问题,也是报告实验里必放的基础表。描述性统计的价值在于快速建立对整体分布的感知,比如是否存在极端值、量级是否合理,同时给后面的异常筛查提供基准。
features = ['temp', 'humidity', 'pressure', 'wind_speed'] print(df[features].describe()) corr = df[features].corr() print('相关系数矩阵:') print(corr.round(3))describe() 输出每个字段的 count、mean、std、min、25%、50%、75%、max,这些是报告实验里的基础统计表。corr() 计算两两变量的相关系数,气象数据里最常见的两个关系是:温度和湿度负相关,温度和气压负相关。把相关系数矩阵放进报告时,不要只贴一张数字表,至少要挑相关性最强的两对展开解释。比如温度与湿度相关系数 -0.62,说明湿度随温度升高而下降,这个现象在夏季表现得尤其明显,可以在后续小节里用月度曲线验证。相关系数在报告里写到两位小数即可,不用追求高精度,重点是解释变量之间的物理联系。
3.2 时间序列聚合:把逐时数据变成可读趋势
逐时数据波动大,直接画出来全是锯齿,很难看出趋势。做趋势分析前,先把数据聚合到日、月两个颗粒度,分别用在不同场景。日粒度适合看短期波动和极端天气,月粒度适合看季节规律。
df['date'] = df['datetime'].dt.date daily = df.groupby('date')[['temp', 'precipitation']].mean() df['month'] = df['datetime'].dt.month monthly_mean = df.groupby('month')['temp'].mean() monthly_precip = df.groupby('month')['precipitation'].sum()这里有个常被忽略的细节:温度和降水用的聚合函数不同。温度取均值,因为气温是连续状态量;降水取求和,因为降水量是累计量,月降水量等于当月每天降水之和。如果把降水也取均值,月降水曲线会完全失真,7 月雨季和 1 月旱季的差异会被抹平。这个差异我在多个数据分析案例里都见过,属于典型的方法选型错误。聚合完成后,我建议再计算一个逐日气温的 7 天滑动平均,用来平滑短期冷空气扰动,滑动平均序列比原始日值更能反映趋势方向。
daily['temp_smooth'] = daily['temp'].rolling(7, center=True).mean()rolling 窗口取 7 天,center=True 让滑动值对齐窗口中心,避免整体右移一截。滑动平均曲线和原始曲线叠在一张图里,报告里用一条平滑曲线说明趋势,用原始浅色曲线展示波动,层次很清楚。
3.3 极端值筛查:找出报告里最值得写的现象
除了均值趋势,报告实验里最出彩的部分往往是极端值。气象数据里的最高温、最大降水量、最大风速都是结论的有力支撑。用分位数筛查可以避免被个别设备故障数据带偏。
q99 = df['temp'].quantile(0.99) hot_days = df[df['temp'] > q99]['date'].nunique() print(f'超过99%分位气温的天数: {hot_days}')分位数方法比固定阈值好用,因为不同地区的气温基准完全不同,固定 35°C 在北方是极端高温,在南方可能只是日常。分位数是相对标准,贴合具体数据集的分布特征。筛选出的极端事件记录,配合日期定位到具体时段,就是报告里“异常分析”小节的素材。筛查极端值的同时,反过来看低于 1% 分位的低温记录,两侧都查一遍,报告内容会更完整。
3.4 可视化出图:matplotlib 中文显示与保存参数
数据分析与可视化不分家,图是报告的门面。气象数据出图常用 matplotlib,这也是 Python 数据分析生态里最基础的绘图库。先把基础图例、标签、保存流程用熟,再谈花哨的交互图表。
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False fig, ax = plt.subplots(2, 1, figsize=(10, 7)) ax[0].plot(monthly_mean.index, monthly_mean.values, marker='o', label='月平均气温') ax[0].set_ylabel('气温(°C)') ax[0].legend() ax[1].bar(monthly_precip.index, monthly_precip.values, label='月降水量') ax[1].set_ylabel('降水量(mm)') ax[1].legend() plt.tight_layout() plt.savefig('月度趋势图.png', dpi=150)两行 rcParams 是中文显示的关键:SimHei 指定中文字体,axes.unicode_minus 解决负号显示成方块的问题。这两个参数不设置,图里全是方框。figsize 控制画布尺寸,dpi=150 保证保存的图在报告里放大也不模糊。子图用 2 行 1 列,上面温度折线,下面降水柱状,一张图同时展示两个变量的季节变化。保存图片时用 savefig,不要截图,截图分辨率不可控。
提示:每个图表都要有完整的图标题、轴标签和图例,这是报告实验评分时最容易扣分的地方。图里的中文字体在同一台机器上配置一次即可全局生效,换电脑跑脚本时要重新确认字体存在。
4. 报告实验落地:一份气象数据分析报告是怎么组装出来的
这个资源名字里带着「报告实验」,核心就是教你如何把分析结果组织成一份能交差的实验报告。报告不是写作文,它的结构是固定的逻辑链:数据来源和数据说明、清洗过程、分析方法、结果图表、结论。每一部分都有对应的产出物,缺一环都不完整。我拆这个资源时发现,最好的使用方式是把它当成模板,保留结构,替换成自己的数据和结论,比从空白文档开始写快很多。
4.1 分析目标拆解与方法选型
拿到一份气象数据,先别急着写代码,把分析目标拆成具体问题,再给每个问题选一个方法。目标不清晰,后面的分析会越做越散,最后报告里堆了一堆不相关的图。拆目标的原则是:一个问题对应一个图表或一个统计表,不重复、不遗漏。
| 分析问题 | 对应方法 | 报告输出形式 |
|---|---|---|
| 全年气温和降水的总体情况 | 描述性统计 | 统计表 |
| 气温和降水的季节变化规律 | 时间序列按月聚合 | 折线图与柱状图 |
| 各气象要素之间的关系 | 相关性分析 | 相关系数矩阵 |
| 极端天气出现的时段 | 分位数筛查与逐日统计 | 极值表加标注图 |
这个表格本身就是报告的骨架,后面所有工作都是往这个骨架里填内容。方法选型的原则是:能用简单统计说清楚的,不引入复杂模型。气象数据的规律性很强,描述性统计加时间序列聚合足够支撑一份合格的报告实验,硬上机器学习反而显得刻意,而且模型的可解释性在气象场景里很难讲清楚。
4.2 核心指标计算:均值、极值与距平
报告结论不能只靠图表,必须有一组可引用的具体数字。核心指标就那几项:平均值、最高最低值、极差、总量。这些数字是所有结论的底座,报告里每一句定性描述背后都应该有至少一个数字支撑。
summary = { '年平均气温': df['temp'].mean(), '最高气温': df['temp'].max(), '最低气温': df['temp'].min(), '年降水总量': df['precipitation'].sum(), '气温极差': df['temp'].max() - df['temp'].min(), } for k, v in summary.items(): print(f'{k}: {v:.2f}')这些数字就是报告结论的直接支撑。比如“年平均气温 16.8°C,最高气温 38.5°C,最低气温 -4.2°C”,一句结论同时包含三个指标,比单纯贴图有信息量得多。距平值在气象分析里也很常用,表示某个值偏离平均值的程度:
df['temp_anomaly'] = df['temp'] - df['temp'].mean()正距平表示偏暖,负距平表示偏冷。把距平按月份聚合,能看出一年里哪些月份明显偏离常年状态,这适合写进“异常分析”小节。距平计算的前提是平均值本身可靠,如果之前数据清洗阶段没做干净,均值偏了,整个距平序列都会跟着偏。
分析完还要把结果导出成表,我习惯用 to_csv 出中间结果,方便在报告里引用数字而不依赖代码运行环境:
df.groupby('month')[['temp', 'precipitation']].agg(['mean', 'sum']).to_csv('月度汇总.csv', encoding='utf-8-sig')编码用 utf-8-sig 而不是 utf-8,是因为 Windows 上的 Excel 打开带 utf-8 的 CSV 会乱码,utf-8-sig 带 BOM 头,Excel 能正确识别。导出文件名里带上日期后缀,比如月度汇总_20240115.csv,避免多版本覆盖后找不到历史结果。
4.3 图表编号与结论对齐
报告实验最常见的翻车方式,是图和结论对不上。我的做法是:先定图表清单,再写每个图的结论句,结论句必须包含具体数值。这样报告内部是自洽的,评审从任何一个图出发都能找到对应的文字解释。
| 图表编号 | 图表内容 | 对应结论 |
|---|---|---|
| 图1 | 月平均气温折线图 | 气温呈单峰型,7 月平均最高约 28.3°C |
| 图2 | 月降水量柱状图 | 降水集中在 6—8 月,占全年 60% 以上 |
| 表1 | 气象要素相关系数矩阵 | 温度与湿度负相关,相关系数 -0.62 |
写结论的通用句式是:先描述现象,再引用数值,最后给一句解释。比如“从图 1 可以看出,气温呈单峰型变化,7 月平均气温最高,约 28.3°C,这与该地夏季受副热带高压控制的背景一致。”这种写法把现象、数字、原因串成一句话,图表和结论严丝合缝。反过来,最忌讳的写法是“如图所示,气温变化明显”——图是哪个图、明显在哪里、数字是多少,全部缺失,等于没写。
5. 避坑指南:气象数据分析高频踩坑记录
这部分内容全部来自我实际跑这个资源时踩过的坑。每条都是现象、原因、解决三段,直接对号入座。气象数据分析项目踩坑有个特点:很多错误不报错,程序正常跑完,但结果是错的。这种静默错误最危险,所以排查时要从结果反推,先问“这个数字合理吗”,再查代码。我把高频问题分成三类:类型错误、语义错误、流程错误,下面五条基本覆盖了最容易翻车的场景。
5.1 日期被读成字符串,聚合结果全乱
现象:按月份做 groupby 时报错,或者按年聚合后 2023 和 2024 的数据混在一起,月度曲线出现周期性锯齿,但程序本身没有任何异常提示。 原因:read_csv 默认把时间列读成 object 类型,没有转换成 datetime。字符串参与聚合时,pandas 按字典序排列,2023-11 会排在 2023-02 前面,自然看不出季节规律。 解决:数据加载后立刻执行 pd.to_datetime 转换,并排序。我现在把这个步骤写进加载函数里,每份数据进来强制走一遍,不留给后面任何出错机会。判断是否中招的方法很简单:df.dtypes 里时间字段显示 object 就一定有隐患,再打印 df['datetime'].head(20),能顺便看出格式混不混。
5.2 图中中文全部变成方块
现象:图标题、轴标签、图例里的中文全部显示成方框,英文和数字正常,图整体看起来像乱码。 原因:matplotlib 默认字体里没有中文字形,Windows 环境最常见的方案是改用 SimHei;Linux 服务器上常用 WenQuanYi,但默认不会自动加载。 解决:在画图脚本开头设置 plt.rcParams['font.sans-serif'] = ['SimHei'] 和 plt.rcParams['axes.unicode_minus'] = False。注意如果机器上没有 SimHei 字体,这个设置不会生效,需要先确认系统字体列表里有没有这个字体,或者换成实际存在的中文字体名。如果换了字体还是方块,检查一下有没有设置过 text.usetex,开启 TeX 渲染时中文经常失效,关掉就好了。
5.3 降水缺测被插补成小数
现象:某站连续缺测 3 天降水,对全字段做 interpolate 后出现 0.3mm 这种降水值,报告里的降水统计比实际偏大,而且无法向评审解释。 原因:对降水这种事件型变量用了连续变量的插补方法,把“无记录”补成了“有降水”。降水是典型的事件型数据,缺测不意味着降水为 0,但也绝不能线性插值。 解决:降水字段统一 fillna(0),或者保留 NaN 并在报告数据说明里注明缺测处理方式,绝不使用 interpolate。这条我踩过一次之后,每次处理事件型变量都会单独列出来,不与连续变量混在一起清洗。类似的还有风速极大值、能见度这类变量,同样要单独考虑缺测语义。
5.4 站号前导 0 丢失导致数据错位
现象:多站数据合并后,站号 05001 变成 5001,原始 5001 站的数据混进来,站点的气温序列整体错位,但单站数据看都是正常的。 原因:pandas 读取时自动把站号推断为整数类型,前导 0 被吞掉了。站号这类标识字段本质是字符串,不是数值,数值化没有任何意义。 解决:读取时用 dtype={'station_id': str} 强制指定为字符串,或者读完后用 zfill(5) 补位。排查方法是对比合并前后的站号集合,set 的大小不一致就说明有类型转换问题。这是个非常隐蔽的坑,因为数据看着正常,但结果已经错了,只有做双口径校验才能暴露。
5.5 报告里的图和最终数据不一致
现象:报告已经写完,临时调了一下清洗逻辑,图没有重新跑,最后图表和数据对不上,现场演示时被发现。 原因:改数据后没有全流程重跑,旧图混进了新报告。数据分析实验里这是最毁信任的错误,比结果错误更严重,因为它暴露的是流程管理问题。 解决:把清洗、分析、出图、导出报告串成一个可重复执行的流程,按顺序编号运行,比如 01_clean.py、02_analyze.py、03_plot.py,让别人也能按顺序复现。凡是改过数据处理逻辑,必须从第一步重跑到底,不能只重跑其中一个环节。我习惯在报告封面写上数据文件版本号和时间戳,每次重跑更新一次,从一开始就杜绝版本混用。
踩坑经验归纳一下,可以按错误特征快速定位排查方向:
| 错误特征 | 首选排查方向 |
|---|---|
| 月度曲线锯齿、年度混杂 | 时间字段类型 |
| 图里中文方框 | matplotlib 字体配置 |
| 降水统计明显偏大 | 插补方法选错 |
| 多站错位但单站正常 | 站号字段类型 |
| 图表和文字对不上 | 全流程未重跑 |
6. 进阶技巧:用双口径校验让分析结果站得住
报告实验做到最后,最难的不是把图做出来,而是让每一个数字都经得起复现。我常用的验证方法是双口径校验:同一个指标,用两条不同的计算路径分别算一遍,结果必须一致。如果不一致,说明数据里藏着问题,问题可能出在时间索引重复、字段类型错误或者聚合函数用错,总之是数据链路里有脏东西。
以月平均气温为例,方法一是先提取月份再 groupby 求均值,方法二是用 resample 重采样后按月份聚合。两条路径逻辑不同,但结果必须完全相同:
method_a = df.groupby(df['datetime'].dt.month)['temp'].mean() df_indexed = df.set_index('datetime') monthly_resample = df_indexed['temp'].resample('MS').mean() method_b = monthly_resample.groupby(monthly_resample.index.month).mean() diff = (method_a - method_b).abs() print('两条路径最大差异:', diff.max()) assert diff.max() < 1e-6assert 语句是校验的核心,差异大于阈值直接抛异常,逼着你去查数据,而不是带着疑问继续往下写。这个方法同样适用于年降水量:一条路径按日求和再聚合,另一条路径按原始小时数据直接求和,两者对比,误差必须为零。如果两条路径对不上,优先检查时间索引是否有重复,或者数据里是否混入了非本时段的记录。
另一个值得做的验证是敏感性分析:把缺失比例最高的那个月份剔除,重跑核心指标,看结论方向是否改变。如果剔除前后均值变化很小,结论就是稳健的;如果结论反转,说明你的结论完全被局部数据左右,报告里要如实写出这个不确定性,不能为了好看强行下结论。
气象数据分析项目能不能让人信服,靠的不是图表多漂亮,而是每个数字都能被另一条路径复现。从那以后,我每次出数据分析报告都强制走一遍双口径校验,凡是两条路径对不上的地方绝不绕过,先回去查数据。这个习惯帮我挡掉了好几次拿出去会被当场问倒的报告。数据分析翻车不可怕,可怕的是不知道自己翻车了。希望帮到你。
本文还有配套的精品资源,点击获取