ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

气象统计实习一.rar实战:Python处理气象数据与统计分析

气象统计实习一.rar实战:Python处理气象数据与统计分析

简介:这份气象统计实习资料面向气象、大气科学及数据科学方向的初学者与在校学生,围绕用Python处理气象数据这一主线,帮助读者打通从数据读取到统计分析与可视化的完整流程。压缩包共74个文件,以72张png结果图为主,辅以1个py脚本和1个dat数据文件,整体约4.38MB,图片按气候场、均方差、距平场等模块分类,便于对照脚本理解每一步输出。资源以H500等气象场数据为样例,覆盖缺失值填充、异常值剔除、日期类型转换等清洗操作,并借助numpy、scipy完成均值、标准差与相关分析,再通过matplotlib、seaborn呈现折线图、箱线图等统计图形,同时涉及geopandas与plotly的地理空间及交互式可视化思路。目前已有1298人学习,适合希望用一份可运行脚本加成套结果图快速上手气象统计实习的读者参考。

1. 气象统计实习一.rar:一个压缩包背后到底藏着什么

如果你手里拿到一个叫「气象统计实习一.rar」的压缩包,第一反应大概率是懵的——没有说明文档,没有目录截图,只有一个 rar。但拆开来看,这类实习包的核心内容通常跑不出三样东西:一份站点观测数据(CSV 或 Excel)、一套统计计算任务书(PDF 或 Word)、以及若干张需要自己填的空白表格。它解决的不是什么高深问题,而是让你把气温、降水、风速这些逐日观测值,变成能写进报告里的统计量——均值、方差、频率分布、相关系数、趋势线。适合气象、地理、环境类专业的学生,也适合刚入行做气候数据分析、需要快速补齐统计基本功的从业者。说白了,这就是一个「用真实观测数据把描述统计和推断统计跑一遍」的入门训练包。

2. 先搞清楚压缩包里有什么:数据格式与任务拆解

2.1 典型文件构成与识别方法

拿到一个来源不明的 rar,不要急着双击解压看内容。我一般先用命令行列出文件清单,确认里面有没有嵌套压缩、有没有乱码文件名、有没有超大文件需要单独处理。在 Linux 或 macOS 上:

# 列出 rar 内文件清单,不解压 unrar l 气象统计实习一.rar # 如果 unrar 没装,用 7z 也可以 7z l 气象统计实习一.rar

逻辑说明:l参数只列出内容不释放文件,避免解压出一堆散落文件污染工作目录。参数上,unrar的l和7z的l行为一致,但 7z 对中文文件名兼容性更好。如果看到文件名是乱码,加-mcp=936指定 GBK 编码(Windows 下常见),或者直接在 Windows 上用 Bandizip 打开。

常见的气象统计实习包,文件结构大致是:

文件/目录典型格式用途
站点日值数据CSV / TXT / XLSX逐日气温、降水、风速等
任务书PDF / DOCX统计项目、公式、提交要求
空白表格XLSX / DOCX填计算结果
站点信息表CSV / XLSX站号、经纬度、海拔
可能有的脚本.m / .py / .R老师给的半成品或示例

注意:如果压缩包里有.exe或.bat,先别运行。气象统计实习包极少需要可执行文件,遇到就单独放到沙箱里看。

2.2 数据读取与第一轮质量检查

假设解压后拿到一个station_daily.csv,列名可能是中文或英文,分隔符可能是逗号也可能是制表符。不要假设,先读前几行看结构:

import pandas as pd # 先读前 5 行,不指定分隔符,让 pandas 自己猜 df = pd.read_csv('station_daily.csv', nrows=5) print(df.head()) print(df.dtypes)

逻辑说明:nrows=5只读前 5 行,快速看列名和值的样子。dtypes看每列被推断成什么类型——如果日期列被读成 object、气温列被读成 object,说明有非数值字符混进去了。参数上,如果分隔符是制表符,加sep='\t';如果中文列名乱码,加encoding='gbk'或encoding='utf-8-sig'。

第一轮质量检查至少做四件事:查缺失值、查重复日期、查气温是否超出物理范围(比如 -60 到 60 摄氏度之外)、查降水是否有负值。用一行代码就能扫一遍:

# 缺失值统计 print(df.isnull().sum()) # 重复日期检查(假设有 'date' 和 'station_id' 列) dup = df.duplicated(subset=['date', 'station_id']).sum() print(f'重复记录数: {dup}') # 气温范围检查 if 'temp' in df.columns: print(df['temp'].describe()) print('超范围气温记录:', df[(df['temp'] < -60) | (df['temp'] > 60)].shape[0])

逻辑说明:isnull().sum()按列统计缺失数量,比df.info()更直观。duplicated指定子集,因为同一日期不同站点是合法重复。describe()给出均值、标准差、最小最大值,一眼能看出有没有离谱值。参数上,subset要根据实际列名改,别硬抄。

2.3 任务书里的统计项目怎么映射到代码

任务书通常不会写「用 pandas 的 groupby」,而是写「计算各站月平均气温」「绘制降水频率直方图」「分析气温与纬度的相关关系」。你要做的是把自然语言翻译成操作:

  • 「各站月平均气温」→ 按站号和月份分组,对气温列求均值
  • 「降水频率直方图」→ 对降水列分箱,统计各箱频数,画柱状图
  • 「气温与纬度相关」→ 取各站平均气温和纬度,算 Pearson 相关系数

这一步的坑在于:任务书说的「月平均」可能是日平均值再平均,也可能是月总降水再除以天数。两种算法结果不同,必须看任务书里的公式定义。如果没写,默认用日值直接求算术平均,并在报告里注明。

3. 用 Python 把描述统计跑通:从均值到频率分布

3.1 分组统计:各站各月的气温均值与方差

气象统计里最基础的操作是分组聚合。假设数据有station_id、date、temp、precip四列,要算各站各月的平均气温和降水总量:

import pandas as pd df = pd.read_csv('station_daily.csv', encoding='gbk', parse_dates=['date']) # 提取月份 df['month'] = df['date'].dt.month # 分组聚合:气温求均值,降水求和 monthly = df.groupby(['station_id', 'month']).agg( temp_mean=('temp', 'mean'), temp_std=('temp', 'std'), precip_sum=('precip', 'sum'), precip_days=('precip', lambda x: (x > 0.1).sum()) # 日降水大于 0.1mm 算降水日 ).reset_index() print(monthly.head(10))

逻辑说明:groupby后agg可以对不同列用不同函数。temp_std用std()默认是样本标准差(ddof=1),如果任务书要求总体标准差,加ddof=0。precip_days用 lambda 统计大于 0.1mm 的天数,这是气象上常用的降水日定义,不是简单的大于 0。参数上,reset_index()把分组键变回普通列,方便后续导出。

注意:如果数据里有缺测标记(比如 9999 或 -999),必须在分组前替换成 NaN,否则均值会被严重拉偏。常见做法是df.replace([9999, -999], pd.NA, inplace=True)。

3.2 频率分布与直方图:降水区间怎么分

降水频率分布是实习报告里的常客。关键在分箱边界——不能等距分,因为降水集中在 0 到 10mm,等距分会导致大部分箱子空着。气象上常用自定义边界:

import numpy as np import matplotlib.pyplot as plt # 自定义降水区间(单位:mm) bins = [0, 0.1, 10, 25, 50, 100, 250, 9999] labels = ['无降水', '小雨', '中雨', '大雨', '暴雨', '大暴雨', '特大暴雨'] df['precip_class'] = pd.cut(df['precip'], bins=bins, labels=labels, right=False) # 统计各类频率 freq = df['precip_class'].value_counts(normalize=True).sort_index() print(freq) # 画图 freq.plot(kind='bar', color='steelblue', edgecolor='black') plt.xlabel('降水等级') plt.ylabel('频率') plt.title('降水频率分布') plt.tight_layout() plt.savefig('precip_freq.png', dpi=150)

逻辑说明:pd.cut按自定义边界分箱,right=False表示左闭右开区间,避免 0.1 被分到「无降水」。value_counts(normalize=True)直接给频率而不是频数。参数上,bins的边界参考了气象上的降水量等级标准,但不同任务书可能用不同阈值,以任务书为准。dpi=150保证图清晰但不至于文件太大。

3.3 相关分析与趋势线:气温和纬度到底有没有关系

实习里经常要求分析气温与纬度、海拔的关系。用scipy.stats算相关系数并做显著性检验:

from scipy import stats import numpy as np # 假设有站点信息表 stations = pd.read_csv('station_info.csv', encoding='gbk') # 算各站年平均气温 annual_temp = df.groupby('station_id')['temp'].mean().reset_index() annual_temp.columns = ['station_id', 'temp_annual'] # 合并站点信息 merged = annual_temp.merge(stations, on='station_id') # Pearson 相关 r, p = stats.pearsonr(merged['temp_annual'], merged['latitude']) print(f'气温与纬度相关系数 r={r:.3f}, p={p:.4f}') # 线性拟合 slope, intercept = np.polyfit(merged['latitude'], merged['temp_annual'], 1) print(f'拟合斜率: {slope:.3f} ℃/度')

逻辑说明:pearsonr返回相关系数和 p 值,p 小于 0.05 才算统计显著。polyfit做一次多项式拟合,斜率表示纬度每增加 1 度,气温平均变化多少。参数上,如果数据不是正态分布,改用spearmanr做秩相关。注意:相关不等于因果,报告里别写「纬度导致气温下降」,写「气温与纬度呈显著负相关」。

4. 避坑与排查:气象统计实习里最容易翻车的 5 个地方

4.1 缺测值被当成真实值参与计算

现象:某站月平均气温算出来是 300 多度,或者降水总量大得离谱。原因:数据里用 9999 或 32766 表示缺测,直接参与均值计算。解决:读数据后第一件事就是替换缺测标记,df.replace([9999, 32766, -999], np.nan, inplace=True),然后再做任何统计。如果不确定缺测标记是什么,看数据字典或任务书,都没有就看数值分布——正常气温不会超过 60。

4.2 日期解析错误导致月份分组错乱

现象:分组后月份出现 13 或者只有 1 个月。原因:日期列格式不统一,有的写2023/1/1,有的写2023-01-01,pandas 推断失败。解决:用pd.to_datetime(df['date'], format='mixed', errors='coerce'),把解析失败的变成 NaT,再检查有多少 NaT。如果超过 5%,说明格式问题严重,需要手动清洗。

4.3 降水日定义不一致导致频率偏差

现象:自己算的降水频率和同学对不上。原因:有人用>0算降水日,有人用>=0.1,有人用>0.1。解决:统一用任务书定义。如果任务书没写,默认用>=0.1mm,这是气象业务上的常用标准。在报告里明确写出你的定义,别只写「降水日」。

4.4 分组聚合后索引丢失导致合并失败

现象:merge时提示 key 不存在。原因:groupby后分组键变成了索引,不是列。解决:groupby后加reset_index(),或者用as_index=False参数。我一般习惯写df.groupby([...], as_index=False).agg(...),省得后面忘。

4.5 画图中文乱码

现象:图表标题和轴标签显示成方框。原因:matplotlib 默认字体不支持中文。解决:在画图前设置plt.rcParams['font.sans-serif'] = ['SimHei'](Windows)或['Arial Unicode MS'](macOS),并加plt.rcParams['axes.unicode_minus'] = False解决负号显示问题。如果服务器上没有中文字体,导出数据用 Excel 画图,别硬刚。

5. 从实习包到可复用流程:把一次作业变成自己的统计模板

5.1 封装一个可复用的气象统计函数

每次实习都重写一遍分组聚合太浪费时间。我一般会把常用操作封装成函数,下次换一份数据直接调用:

def monthly_summary(df, station_col='station_id', date_col='date', temp_col='temp', precip_col='precip'): """ 输入逐日气象数据,返回各站各月统计摘要。 参数: df: DataFrame,至少包含站号、日期、气温、降水四列 station_col: 站号列名 date_col: 日期列名,需为 datetime 类型 temp_col: 气温列名 precip_col: 降水列名 返回: DataFrame,含 temp_mean, temp_std, precip_sum, precip_days """ df = df.copy() df[date_col] = pd.to_datetime(df[date_col], format='mixed', errors='coerce') df['_month'] = df[date_col].dt.month result = df.groupby([station_col, '_month'], as_index=False).agg( temp_mean=(temp_col, 'mean'), temp_std=(temp_col, 'std'), precip_sum=(precip_col, 'sum'), precip_days=(precip_col, lambda x: (x >= 0.1).sum()) ) result.rename(columns={'_month': 'month'}, inplace=True) return result

逻辑说明:函数内部先复制一份,避免修改原始数据。format='mixed'兼容多种日期格式,errors='coerce'把解析失败的变成 NaT。as_index=False保证分组键保留为列。参数上,列名都做成可配置的,因为不同实习包列名不一样。这个函数不处理缺测替换,缺测替换应该在读数据后、调用函数前单独做,保持职责单一。

5.2 用配置文件管理不同实习包的差异

如果一学期有多次实习,每次数据格式略有不同,可以写一个简单的 YAML 配置文件:

# config.yaml dataset: file: station_daily.csv encoding: gbk date_col: date station_col: station_id temp_col: temp precip_col: precip missing_values: [9999, 32766, -999] output: monthly_csv: output/monthly_summary.csv figure_dir: output/figures/

然后用 Python 读配置:

import yaml with open('config.yaml', 'r', encoding='utf-8') as f: cfg = yaml.safe_load(f) df = pd.read_csv(cfg['dataset']['file'], encoding=cfg['dataset']['encoding']) df.replace(cfg['dataset']['missing_values'], pd.NA, inplace=True)

逻辑说明:配置文件把「数据长什么样」和「怎么算」分开,换一份数据只改配置不改代码。参数上,missing_values用列表,方便一次替换多个标记。yaml.safe_load比yaml.load安全,不会执行任意代码。

5.3 验证结果是否合理的三个快速检查

算完统计量后,别急着交报告。我一般做三个检查:第一,各站年平均气温是否在 -20 到 30 度之间(取决于地区);第二,月降水总量是否超过该地区历史极值;第三,相关系数的符号是否符合物理直觉(比如气温和纬度应该是负相关)。如果任何一条不满足,先回去查数据质量,别怀疑统计方法。

提示:把这三个检查写成断言,跑数据时自动触发。比如assert merged['temp_annual'].between(-20, 30).all(), '气温超出合理范围'。断言失败时直接报错,比事后翻报告快得多。

5.4 一个具体技巧:用透视表快速对比多站多要素

最后分享一个我常用的技巧——pivot_table做多要素对比。比如要同时看各站各月的平均气温和降水总量:

pivot = df.pivot_table( values=['temp', 'precip'], index='station_id', columns=df['date'].dt.month, aggfunc={'temp': 'mean', 'precip': 'sum'} ) print(pivot.round(1))

逻辑说明:pivot_table的aggfunc可以传字典,对不同列用不同聚合函数。columns用月份,index用站号,输出就是一个二维表,直接贴进报告。参数上,round(1)保留一位小数,避免表格太宽。如果列名有多层索引,用pivot.columns = ['_'.join(map(str, c)) for c in pivot.columns]拍平。

这套流程跑下来,一个「气象统计实习一.rar」就不再是一次性的作业,而是一个可以反复用的分析模板。下次拿到「实习二」或者别的观测数据,改改配置就能跑。我自己踩过最深的坑是缺测值没替换,算出来的月平均气温比实际高了十几度,报告交上去被退回来重做。从那以后,我养成了读数据后先describe()再干别的的习惯。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进