☰
气象数据分析全流程:从数据清洗到报告生成的一站式资源包
2026/9/29 2:04:10 网站建设 项目流程

简介:这份资源面向数据分析初学者与气象数据爱好者,围绕“爬取中国天气网某城市天气数据并完成可视化分析”这一完整实验展开,帮助读者掌握从数据采集到图表呈现的全流程方法。压缩包共39个文件,约1.69MB,以20张png图表、15个xml配置文件和4个rels关系文件为主,png对应雷达图、条形图等分析结果,xml与rels则支撑实验报告文档的结构与样式。源代码基于Jupyter Notebook编写,可直接运行复现爬虫抓取与数据处理过程,并配有整理后的实验报告,便于对照图表理解分析结论。目前已有1852人学习下载,适合课程实验、数据分析入门练习或作为报告撰写参考,读者可借此获得可运行的爬虫脚本、可视化代码与完整实验文档,快速搭建自己的天气数据分析项目。

1. 气象数据分析资源包:从原始观测到可复现报告的一条龙拆解

拿到一份气象观测数据,很多人第一反应是打开 Excel 画个折线图交差。但真正做过气象分析报告的人都知道,原始数据里藏着大量缺测、格式不统一、时间戳错位的问题,直接画图往往得到一条锯齿状的废线。这次拆解的资源包,核心就是解决从「拿到原始气象数据」到「产出一份能写进实验报告的分析结论」这条链路。它包含数据清洗脚本、统计指标计算模块、可视化模板和报告生成框架,适合气象、环境、地理方向的学生和需要做数据分析报告的从业者。无论你是第一次接触气象数据,还是已经用过 pandas 但被时间序列索引折磨过,这份资源都能帮你省掉大量重复造轮子的时间。

2. 气象数据清洗与预处理:把缺测和异常值处理干净

2.1 为什么气象数据清洗不能直接 dropna

气象观测数据最常见的来源是地面气象站逐小时或逐日观测,字段通常包括气温、气压、相对湿度、风速风向、降水量等。这类数据有个特点:缺测不是随机的。传感器故障、通讯中断、极端天气导致的设备停机,都会让缺测集中在某些时段。如果直接dropna(),你会把整个台风过境期间的数据全部丢掉,而这恰恰是最需要分析的时段。

常见做法是分字段处理。气温、气压这类变化连续的变量,用时间序列插值补全;降水量这种间歇性变量,缺测就保留 NaN,不要插值,否则会凭空造出虚假降水。资源包里的清洗脚本采用的就是这套策略,按字段类型走不同分支。

import pandas as pd import numpy as np # 读取原始数据,假设时间列为 'datetime',其余为气象要素 df = pd.read_csv('raw_weather.csv', parse_dates=['datetime']) df = df.set_index('datetime').sort_index() # 气温:线性插值,限制最大连续插值长度为 3 小时 df['temp'] = df['temp'].interpolate(method='linear', limit=3) # 气压:同样插值,但气压变化更平滑,可以用样条插值 df['pressure'] = df['pressure'].interpolate(method='spline', order=2, limit=3) # 降水量:不插值,缺测保持 NaN # 相对湿度:插值后裁剪到 0-100 范围 df['humidity'] = df['humidity'].interpolate(method='linear', limit=3).clip(0, 100) # 风速:插值后不能为负 df['wind_speed'] = df['wind_speed'].interpolate(method='linear', limit=3).clip(lower=0)

这段代码的关键参数是limit=3,意思是连续缺测超过 3 个时间步就不插值了,保留 NaN。为什么是 3?因为逐小时数据里,连续缺 3 小时以上往往意味着设备真的出了问题,强行插值会引入虚假的平滑信号。method='spline'用于气压是因为气压日变化曲线比较规则,样条插值比线性更贴合物理规律。湿度裁剪到 0-100 是硬约束,插值可能产生超出物理范围的值。

2.2 异常值检测:三倍标准差和物理阈值双管齐下

清洗完缺测,下一步是异常值。气象数据里的异常值分两种:一种是物理上不可能的,比如气温 60°C、风速 -5 m/s;另一种是物理上可能但统计上罕见的,比如夏季午后突然出现 5°C 的气温。前者用物理阈值直接卡掉,后者用统计方法标记。

# 物理阈值检查 df.loc[df['temp'] > 60, 'temp'] = np.nan df.loc[df['temp'] < -80, 'temp'] = np.nan df.loc[df['wind_speed'] < 0, 'wind_speed'] = np.nan # 统计异常值:三倍标准差 for col in ['temp', 'pressure', 'humidity']: mean = df[col].mean() std = df[col].std() lower = mean - 3 * std upper = mean + 3 * std df[f'{col}_outlier'] = (df[col] < lower) | (df[col] > upper)

这里没有直接把三倍标准差之外的值删掉,而是新增了一列布尔标记。原因是气象里的极端值有时候是真实信号,比如寒潮导致的气温骤降,直接删掉会丢失重要信息。标记出来之后,可以在后续分析里决定是否纳入统计,或者在报告里单独说明。

提示:三倍标准差对非正态分布字段(如降水量)不适用,降水量建议用分位数方法,比如超过 99.9 分位数的标记为极端降水。

3. 统计指标计算与时间序列分析:从均值到趋势检验

3.1 核心统计量的计算逻辑与陷阱

气象分析报告里最常见的统计量包括日均值、月均值、极值、日较差、累积降水量等。这些看起来简单,但计算时有几个容易翻车的地方。

第一个坑是日均值的计算方式。气象上的日平均气温通常不是 24 小时算术平均,而是用 02、08、14、20 时四个定时观测值平均。如果你用逐小时数据算算术平均,结果会和气象业务标准有偏差。资源包里提供了两种计算模式,可以通过参数切换。

def daily_mean_temperature(df, method='24h'): """ 计算日平均气温 method='24h': 24小时算术平均 method='4obs': 02,08,14,20时四个定时值平均 """ if method == '24h': return df['temp'].resample('D').mean() elif method == '4obs': # 筛选四个定时时刻 hours = [2, 8, 14, 20] mask = df.index.hour.isin(hours) return df.loc[mask, 'temp'].resample('D').mean()

第二个坑是月均值不能直接用日均值再平均。如果某个月有缺测日,直接对日均值序列取平均会引入偏差。正确做法是用该月所有有效小时数据的总和除以有效小时数。资源包里的monthly_stat函数处理了这个逻辑,会自动统计有效样本数。

第三个坑是风向的统计。风向是角度量,不能直接算算术平均。比如 350° 和 10° 的平均应该是 0°,而不是 180°。需要用矢量平均法,把风向分解为 u、v 分量再合成。

def wind_direction_mean(angles): """矢量法计算平均风向""" rad = np.deg2rad(angles) u = np.mean(np.cos(rad)) v = np.mean(np.sin(rad)) mean_dir = np.rad2deg(np.arctan2(v, u)) return mean_dir % 360

3.2 趋势检验:Mann-Kendall 和线性回归怎么选

做气象分析报告,趋势分析是绕不开的。常见方法有两种:线性回归求斜率,或者 Mann-Kendall 非参数检验。两者适用场景不同。

线性回归适合数据近似正态、没有明显自相关的情况,输出的是「每年变化多少」这种直观的斜率。Mann-Kendall 不要求数据分布,对异常值不敏感,输出的是趋势是否显著,适合检验气温、降水这类可能不满足正态假设的序列。

from scipy import stats import pymannkendall as mk # 线性回归趋势 slope, intercept, r_value, p_value, std_err = stats.linregress( range(len(annual_temp)), annual_temp ) print(f"线性趋势: {slope:.3f} °C/年, p={p_value:.4f}") # Mann-Kendall 趋势检验 result = mk.original_test(annual_temp) print(f"MK检验: 趋势={result.trend}, p={result.p:.4f}, Sen斜率={result.slope:.3f}")

资源包里两个方法都封装了,建议在报告里同时给出。如果两者结论一致,可信度高;如果不一致,需要检查数据是否存在自相关或周期性问题。Sen 斜率是 MK 检验配套的斜率估计,比线性回归斜率更稳健。

注意:做趋势分析前一定要确认时间序列的完整性。如果某几年缺测严重,趋势结果会严重失真。资源包里有个check_completeness函数,会输出每年的有效数据比例,低于 80% 的年份建议在报告中标注。

4. 可视化与报告生成:让图表能直接放进实验报告

4.1 气象常用图表的代码模板

气象分析报告的图表有几类固定套路:时间序列曲线、风玫瑰图、气温降水双轴图、空间分布热力图。资源包里每个都有模板,改数据源就能用。

时间序列曲线是最基础的,但要注意多要素叠加时的坐标轴处理。比如气温和气压量级差很大,放同一个 y 轴会有一条线被压平。正确做法是用双 y 轴或者归一化。

import matplotlib.pyplot as plt import matplotlib.dates as mdates fig, ax1 = plt.subplots(figsize=(12, 5)) # 气温用左轴 ax1.plot(df.index, df['temp'], color='tab:red', label='气温 (°C)') ax1.set_ylabel('气温 (°C)', color='tab:red') ax1.tick_params(axis='y', labelcolor='tab:red') # 气压用右轴 ax2 = ax1.twinx() ax2.plot(df.index, df['pressure'], color='tab:blue', label='气压 (hPa)') ax2.set_ylabel('气压 (hPa)', color='tab:blue') ax2.tick_params(axis='y', labelcolor='tab:blue') # 时间轴格式化 ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d')) ax1.xaxis.set_major_locator(mdates.DayLocator(interval=5)) plt.setp(ax1.xaxis.get_majorticklabels(), rotation=45) fig.tight_layout() plt.savefig('temp_pressure_timeseries.png', dpi=300)

风玫瑰图需要用到windrose库,资源包里已经集成了。关键参数是风向分组数和风速分级阈值,这两个参数直接影响图的可读性。分组太细图会碎,太粗会丢失信息。一般风向分 16 个方位,风速分 4-5 档。

from windrose import WindroseAxes ax = WindroseAxes.from_ax() ax.bar(df['wind_dir'], df['wind_speed'], normed=True, opening=0.8, edgecolor='white', bins=[0, 2, 4, 6, 8, 10]) ax.set_legend(title='风速 (m/s)', loc='best') plt.savefig('windrose.png', dpi=300)

4.2 报告自动化:从图表到 Markdown 的流水线

资源包的报告生成模块用的是 Jinja2 模板引擎,把统计结果和图表路径填入模板,直接输出 Markdown 或 HTML。这样做的好处是每次更新数据后,报告可以一键重新生成,不用手动改数字。

from jinja2 import Template report_template = Template(""" # {{ station_name }} 气象数据分析报告 ## 数据概况 - 分析时段:{{ start_date }} 至 {{ end_date }} - 有效数据比例:{{ valid_ratio }}% ## 气温统计 - 平均气温:{{ temp_mean }} °C - 最高气温:{{ temp_max }} °C - 最低气温:{{ temp_min }} °C - 趋势:{{ temp_trend }} ## 图表 ![气温气压时间序列]({{ fig1_path }}) ![风玫瑰图]({{ fig2_path }}) """) report = report_template.render( station_name='北京站', start_date='2023-01-01', end_date='2023-12-31', valid_ratio=96.5, temp_mean=13.2, temp_max=41.1, temp_min=-15.3, temp_trend='+0.35 °C/年 (p<0.05)', fig1_path='temp_pressure_timeseries.png', fig2_path='windrose.png' ) with open('report.md', 'w', encoding='utf-8') as f: f.write(report)

这个模板可以根据具体实验报告的要求自由修改。比如课程实验报告要求有「数据与方法」章节,直接在模板里加一段就行。资源包里提供了三个模板:基础版、课程实验版、科研论文版,区别在于章节结构和统计指标的详细程度。

提示:生成的 Markdown 里的图片路径是相对路径,如果要把报告发给别人,记得把图片一起打包,或者用 base64 嵌入。

5. 避坑与常见问题:那些让我重跑过三次的坑

5.1 时间戳时区问题导致日界错位

现象:计算日均值时,发现某些天的数据被分到了前一天或后一天,日统计结果对不上。

原因:原始数据的时间戳可能是 UTC,也可能是本地时间,但脚本里没有统一。更隐蔽的情况是数据跨了夏令时切换,某些天有 23 或 25 个小时。

解决:拿到数据第一件事就是确认时间戳的时区。资源包里有个standardize_time函数,统一转成 UTC+8 并去除夏令时影响。如果数据源明确是本地时间,直接tz_localize然后tz_convert到目标时区。

5.2 风向数据 0° 和 360° 的边界处理

现象:风玫瑰图在正北方向出现一个异常缺口,或者平均风向算出来是 180° 但实际应该是 0°。

原因:风向数据里 0° 和 360° 是同一个方向,但数值上差了 360。直接做算术运算或者分组统计时,这两个值会被分到不同的 bin。

解决:在做风向分组前,先把 360° 转成 0°。资源包里的normalize_wind_dir函数做了这个处理。另外,计算平均风向一定要用矢量法,不要用算术平均。

5.3 插值把降水缺测补成了虚假降水

现象:某个月降水量统计出来比历史同期高很多,检查发现是缺测时段被插值补上了虚假的降水。

原因:降水量是间歇性变量,大部分时间是 0,缺测时用线性插值会补出中间值,比如前后都是 0 但中间缺测,插值可能补出 0.5mm 的虚假降水。

解决:降水量字段不做插值,缺测保持 NaN。统计月累积降水时,用sum(min_count=1)而不是sum(),这样如果全月都是 NaN,结果也是 NaN 而不是 0。

5.4 趋势分析忽略了自相关导致显著性虚高

现象:Mann-Kendall 检验显示趋势非常显著(p<0.01),但把数据打乱后重新检验,显著性依然很高。

原因:气象时间序列通常有自相关,相邻年份的数据不独立。标准 MK 检验假设样本独立,自相关会让 p 值偏小,造成假显著。

解决:资源包里提供了修正版 MK 检验(mk.hamed_rao_modification_test),会考虑自相关的影响。如果修正后 p 值变大甚至不显著,说明原始趋势的显著性被高估了。

5.5 图表中文字体缺失导致乱码

现象:生成的图表里中文标签全部变成方框。

原因:matplotlib 默认字体不含中文字形。

解决:在绘图脚本开头设置中文字体。资源包里已经预置了字体配置代码,但需要确认系统里有对应字体文件。Linux 服务器上常见做法是下载 SimHei 字体放到指定目录,然后font_manager.fontManager.addfont()注册。

import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Noto Sans CJK SC'] plt.rcParams['axes.unicode_minus'] = False

6. 进阶技巧:用滚动统计和合成分析挖出更多信息

基础统计和趋势检验做完之后,如果想让报告更有深度,可以加两个进阶分析:滚动统计和合成分析。

滚动统计就是用一个滑动窗口计算均值或方差,看气象要素的波动特征。比如用 30 天滑动平均看气温的季内变化,用 7 天滑动标准差看气温的稳定性。资源包里封装了rolling_stat函数,支持自定义窗口和统计量。

# 30天滑动平均气温 df['temp_30d_ma'] = df['temp'].rolling(window=30*24, min_periods=20*24).mean() # 7天滑动标准差 df['temp_7d_std'] = df['temp'].rolling(window=7*24, min_periods=5*24).std()

min_periods参数很关键,它控制窗口内最少需要多少个有效值才输出结果。设得太小,窗口边缘的结果不可靠;设得太大,缺测多的时段会全是 NaN。我一般设成窗口长度的 70% 左右。

合成分析适合做事件对比,比如把所有降水日的气温序列提取出来求平均,和所有非降水日对比。资源包里有个composite_analysis函数,传入事件日期列表和变量名,自动输出合成图和显著性检验结果。

# 降水日和非降水日的气温合成对比 rainy_days = df[df['precip'] > 0].index.normalize().unique() dry_days = df[df['precip'] == 0].index.normalize().unique() composite = composite_analysis(df, 'temp', rainy_days, dry_days) # 输出包含合成均值曲线和 t 检验 p 值

这个分析在实验报告里很加分,因为它不只是描述「气温是多少」,而是回答了「降水对气温有什么影响」这种有因果意味的问题。做合成分析时要注意事件窗口的选择,一般取事件前后各 5-10 天,太短看不出变化,太长会混入其他信号。

还有一个容易被忽略的技巧:把分析结果存成 NetCDF 或 Parquet 格式,而不是 CSV。气象数据量大,CSV 读写慢且占空间。Parquet 列式存储,读取速度快一个量级,而且保留数据类型。资源包里的save_processed函数默认输出 Parquet,如果下游工具只认 CSV,再单独导出一次就行。

从那以后我每次拿到新的气象数据,都强制先跑一遍check_completeness和standardize_time,确认数据完整性和时间轴没问题再往下做。这两个步骤花不了五分钟,但能省掉后面几小时的排查时间。希望帮到你。

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

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

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

立即咨询