2026/10/13 8:13:46

气象统计实习数据包处理全攻略:从缺测值到趋势分析

气象统计实习数据包处理全攻略:从缺测值到趋势分析 简介这份气象统计实习资料面向气象、大气科学及数据科学方向的初学者与进阶学习者围绕Python在气象数据处理中的完整流程展开帮助解决从原始观测数据到统计分析与可视化呈现的实操问题。压缩包共74个文件以72张png结果图、1个py脚本和1个dat数据文件为主整体约4.38MB其中脚本负责数据读取与计算dat文件提供H500等气象场原始数据png则对应气候场、均方差、距平场等分析图件便于对照代码与输出结果。目前已有1298人学习下载。资源覆盖气象数据获取与清洗、缺失值与异常值处理、基于numpy和scipy的均值与标准差等统计量计算以及matplotlib、seaborn绘图和geopandas、plotly空间可视化等环节读者可借此掌握一套可复用的气象数据分析流程理解温度、湿度、风速、气压等变量的时空变化规律为气象预报与气候研究打下编程与统计基础。1. 气象统计实习一.rar一个压缩包背后藏着多少气象数据处理的门道拿到「气象统计实习一.rar」这个标题很多人第一反应是去找解压密码但真正做过气象数据处理的人会先问一句里面装的是站点观测数据、格点再分析资料还是某次实习课的示例数据集这个压缩包本质上是一个教学或实训场景下的数据与脚本集合核心用途是让学习者用真实气象观测记录跑通「数据清洗→统计量计算→可视化→结果解读」这条链路。它适合气象、大气科学、环境科学方向的学生也适合刚转入气象数据分析岗位、需要快速补齐实操经验的开发者。你不需要气象学博士背景但得会一点 Python 或 R能看懂 CSV 和 NetCDF 的基本结构。这个包的价值不在文件本身而在于它逼你面对真实气象数据里那些教科书不讲的脏活缺测值、单位不统一、时间戳错位、站点迁移导致的序列断裂。2. 先搞清楚压缩包里该有什么气象统计实习的数据组织逻辑2.1 典型气象统计实习的数据分层一个结构清晰的气象统计实习压缩包通常不会把所有东西堆在一个文件夹里。我见过比较合理的组织方式是三层原始数据层、中间处理层、结果输出层。原始数据层放站点观测的 CSV 或 Excel比如某地区多个气象站逐日或逐小时的气温、气压、风速、降水量中间处理层放清洗后的统一格式文件通常是 CSV 或 Parquet结果输出层放统计图表和汇总表。如果压缩包里只有一堆命名混乱的 xlsx那说明整理者自己也没想清楚流程你得先做一轮文件审计。常见的气象数据格式有几种站点数据多用 CSV 或 Excel格点数据多用 NetCDF 或 GRIB再分析资料如 ERA5 下载下来是 NetCDF。实习包一般以站点数据为主因为格点数据体量大、读取门槛高。你拿到包后先看文件扩展名分布用一条命令就能摸清底细# 统计压缩包内文件类型分布先解压到临时目录 mkdir -p /tmp/met_insight tar -xf 气象统计实习一.rar -C /tmp/met_insight 2/dev/null || unzip -q 气象统计实习一.rar -d /tmp/met_insight find /tmp/met_insight -type f | sed s/.*\.// | sort | uniq -c | sort -rn这段命令先尝试用 tar 解压失败则用 unzip兼容 rar 和 zip 两种常见封装。find列出所有文件sed提取扩展名uniq -c统计每种扩展名出现次数。如果输出里 csv 占大头说明是站点数据如果 nc 或 grib 多说明涉及格点。参数上注意-C指定解压目录避免污染当前工作区。2.2 站点数据的字段含义与单位陷阱站点气象数据最常见的字段包括站号、纬度、经度、海拔、观测时间、气温、气压、相对湿度、风速、风向、降水量。这里有几个单位陷阱必须提前确认。气温可能是摄氏度也可能是开尔文气压可能是百帕也可能是帕斯卡风速可能是米每秒也可能是节。降水量更麻烦有的是累计值有的是逐小时增量有的在整点记录的是过去一小时累计。你不确认单位就直接算平均结果能偏出十万八千里。我一般会先读一个样本文件看字段名和数值范围。气温如果在 250 到 320 之间基本是开尔文在 -30 到 45 之间基本是摄氏度。气压如果在 1000 附近是百帕在 100000 附近是帕斯卡。风速如果在 0 到 30 之间大概率是米每秒在 0 到 60 之间可能是节。这些经验判断能帮你快速定位单位问题。import pandas as pd # 读取单个站点文件先看前几行和统计摘要 df pd.read_csv(/tmp/met_insight/station_001.csv, encodingutf-8) print(df.head(10)) print(df.describe()) # 检查气温字段范围判断单位 temp_col [c for c in df.columns if temp in c.lower() or 气温 in c][0] temp_min, temp_max df[temp_col].min(), df[temp_col].max() if temp_min 200: print(f气温范围 {temp_min:.1f}-{temp_max:.1f}疑似开尔文需减 273.15) elif -60 temp_min 60: print(f气温范围 {temp_min:.1f}-{temp_max:.1f}疑似摄氏度) else: print(f气温范围异常{temp_min:.1f}-{temp_max:.1f}需人工核查)这段代码先加载 CSV 并打印摘要然后自动判断气温单位。describe()给出均值、标准差、四分位数能快速发现异常值。单位判断逻辑用阈值法开尔文气温不会低于 200摄氏度不会高于 60。参数上注意encoding要跟文件实际编码一致中文气象数据常用 gbk 或 utf-8读错了会报 UnicodeDecodeError。2.3 时间序列的完整性检查气象统计最怕时间序列有洞。缺测值可能被标成 -999、9999、NaN 或者空字符串不同来源的数据标记方式不一样。你得先统一缺测标记再检查时间轴是否连续。逐小时数据应该有 24 条每天逐日数据应该有 365 或 366 条每年。如果某站某年只有 300 条说明有 65 天缺失算年平均值时直接除 365 会偏低。# 统一缺测标记并检查时间连续性 import numpy as np df pd.read_csv(/tmp/met_insight/station_001.csv, encodingutf-8) df.replace([-999, 9999, 99999, ], np.nan, inplaceTrue) # 解析时间列假设列名为 time df[time] pd.to_datetime(df[time], errorscoerce) df df.dropna(subset[time]).sort_values(time) # 检查逐小时数据的每日记录数 daily_count df.groupby(df[time].dt.date).size() print(f每日记录数分布\n{daily_count.value_counts().head()}) # 找出记录数不足 24 的日期 incomplete_days daily_count[daily_count 24] print(f不完整天数{len(incomplete_days)}示例\n{incomplete_days.head(10)})这段代码先把常见缺测标记替换成 NaN再解析时间列并排序。groupby按日期分组统计每日记录数value_counts看分布。如果大部分日期是 24 条少数不足说明那些天有缺测。参数上errorscoerce让无法解析的时间变成 NaT避免整行报错。dropna(subset[time])只删时间缺失的行保留其他字段的 NaN 供后续插补。3. 用 Python 跑通气象统计核心计算从平均到趋势3.1 基础统计量的计算与缺测处理气象统计实习的核心任务通常包括计算月平均气温、年降水量、风速频率分布、气温日较差、相关系数等。这些计算本身不复杂难的是缺测值怎么处理。常见策略有三种整段删除、线性插补、气候态替代。整段删除适合缺测比例低于 5% 的情况线性插补适合短时缺测比如连续几小时气候态替代适合长期缺测用多年同期平均填充。我一般先用整段删除跑一遍看结果是否合理再决定要不要插补。因为插补会引入人为信号在趋势分析里可能造成虚假趋势。如果缺测集中在某几年插补后算出的趋势可能完全不可信。# 计算月平均气温分别用删除和插补两种策略对比 df[month] df[time].dt.to_period(M) # 策略一直接删除缺测 monthly_drop df.dropna(subset[temp]).groupby(month)[temp].mean() # 策略二线性插补后计算 df[temp_interp] df[temp].interpolate(methodlinear, limit6) monthly_interp df.groupby(month)[temp_interp].mean() # 对比两种策略的差异 diff (monthly_interp - monthly_drop).abs() print(f两种策略月平均气温最大差异{diff.max():.2f} 摄氏度) print(f差异超过 0.5 度的月份数{(diff 0.5).sum()})这段代码先按月分组分别用删除和插补两种方式算月平均。interpolate的limit6表示最多连续插补 6 个缺测超过就保留 NaN避免长段插补失真。对比差异能帮你判断缺测对结果的影响程度。如果差异超过 0.5 度的月份很多说明缺测不可忽视得在报告里说明处理方式。3.2 气温趋势分析线性回归与 Mann-Kendall 检验趋势分析是气象统计实习的进阶内容。最简单的是对年均气温做线性回归斜率就是趋势。但气象数据往往不满足正态分布和方差齐性所以更稳健的做法是 Mann-Kendall 非参数检验。Python 里可以用pymannkendall库也可以自己实现。# 年均气温趋势线性回归 Mann-Kendall import numpy as np from scipy import stats annual df.dropna(subset[temp]).groupby(df[time].dt.year)[temp].mean() years annual.index.values temps annual.values # 线性回归 slope, intercept, r_value, p_value, std_err stats.linregress(years, temps) print(f线性趋势{slope:.4f} 摄氏度/年p{p_value:.4f}R²{r_value**2:.3f}) # Mann-Kendall 简易实现 def mann_kendall(x): n len(x) s 0 for i in range(n-1): for j in range(i1, n): s np.sign(x[j] - x[i]) var_s n*(n-1)*(2*n5)/18 if s 0: z (s-1)/np.sqrt(var_s) elif s 0: z (s1)/np.sqrt(var_s) else: z 0 p 2*(1 - stats.norm.cdf(abs(z))) return z, p z, p_mk mann_kendall(temps) print(fMann-KendallZ{z:.3f}p{p_mk:.4f})线性回归给出趋势斜率和显著性linregress返回的p_value小于 0.05 说明趋势显著。Mann-Kendall 的s统计量累加所有点对的符号差var_s是方差z是标准化统计量。参数上注意years和temps要一一对应缺测年份要么插补要么剔除不能留空。两种方法结果一致时趋势更可信不一致时优先信 Mann-Kendall因为它对异常值不敏感。3.3 风玫瑰图与降水频率分布风玫瑰图是气象统计的经典可视化展示风向频率和风速分布。Python 里用windrose库最方便没有的话用 matplotlib 的极坐标图也能画。降水频率分布通常按等级统计比如小雨、中雨、大雨、暴雨各占多少天。# 风玫瑰图数据准备 from windrose import WindroseAxes import matplotlib.pyplot as plt # 假设 df 有 wind_speed 和 wind_direction 列 fig plt.figure(figsize(8, 8)) ax WindroseAxes.from_ax(figfig) ax.bar(df[wind_direction], df[wind_speed], normedTrue, opening0.8, edgecolorwhite) ax.set_legend(title风速 (m/s)) plt.savefig(/tmp/met_insight/windrose.png, dpi150, bbox_inchestight) # 降水等级统计 bins [0, 0.1, 10, 25, 50, 100, 9999] labels [无降水, 小雨, 中雨, 大雨, 暴雨, 大暴雨] df[precip_level] pd.cut(df[precip], binsbins, labelslabels, rightFalse) level_counts df[precip_level].value_counts().reindex(labels) print(level_counts)风玫瑰图的normedTrue表示按频率归一化opening0.8控制扇形间隙。降水等级用pd.cut分箱rightFalse表示左闭右开区间避免 10.0 被分到小雨。参数上 bins 的边界要根据当地气候调整干旱区暴雨阈值可以降低湿润区可以调高。4. 避坑指南气象统计实习里最容易翻车的五个地方4.1 缺测值标记不统一导致统计量失真现象算出的月平均气温比邻近站点低好几度或者年降水量明显偏少。原因不同文件用不同缺测标记有的用 -999有的用 9999有的用空字符串你只替换了一种其他被当成真实值参与计算。解决读入所有文件后先扫描每列的数值范围把明显超出物理极限的值统一替换成 NaN。气温低于 -100 或高于 60、气压低于 800 或高于 1100、风速大于 80 的值都值得怀疑。4.2 时间戳时区不统一造成日界错位现象逐小时数据按日期分组时某些天多出或少了几条记录。原因部分数据用 UTC 时间部分用北京时间混在一起后日界不一致。解决统一转成同一时区再分组。如果原始数据没有时区标记看数据来源说明或者用日出日落时间反推。气象上常用 UTC但国内站点数据很多是北京时间转换时加 8 小时或减 8 小时要搞清楚方向。4.3 站点迁移导致序列断裂被误判为气候突变现象某站气温序列在某一年突然跳变Mann-Kendall 检验显示显著突变。原因站点从山顶搬到山脚海拔变化导致气温系统性偏移不是气候突变。解决查站史元数据看是否有迁移记录。没有元数据时对比邻近站点如果只有这一个站跳变大概率是迁移或仪器更换。处理方式是把迁移前后分成两段分别分析或者做均一性检验。4.4 插补方法选择不当引入虚假趋势现象插补后算出的升温趋势比删除缺测后强很多。原因线性插补在缺测段两端数值差异大时会人为拉平或拉陡序列长期缺测插补后趋势被放大。解决缺测比例超过 10% 的年份直接剔除不要插补。短时缺测用线性插补但限制连续插补长度不超过 6 个时间步。趋势分析前先画缺测分布图看缺测是否集中在某段时期。4.5 单位换算遗漏导致物理量量级错误现象风速频率分布图里风速集中在 0 到 5 之间但实际该地常年大风。原因原始风速单位是节你没换算成米每秒1 节约等于 0.514 米每秒数值偏小一半。解决读数据前先确认单位气温、气压、风速、降水都要查。换算关系记牢开尔文减 273.15 得摄氏度帕斯卡除以 100 得百帕节乘以 0.514 得米每秒。5. 从实习包到可复用流程把一次性分析变成自动化脚本5.1 用配置文件管理站点和参数每次实习都改代码里的路径和参数很烦我习惯用一个 YAML 配置文件把站点列表、字段映射、单位换算、缺测标记都写进去。这样换一批数据只改配置不动代码。# config.yaml stations: - id: 001 path: /tmp/met_insight/station_001.csv time_col: time temp_col: temp temp_unit: celsius precip_col: precip precip_unit: mm - id: 002 path: /tmp/met_insight/station_002.csv time_col: datetime temp_col: temperature temp_unit: kelvin precip_col: rain precip_unit: mm missing_values: [-999, 9999, 99999, ] interp_limit: 6 output_dir: /tmp/met_insight/output配置文件把每个站点的列名和单位都列清楚missing_values统一缺测标记interp_limit控制插补长度。参数上注意 YAML 对缩进敏感用空格不用 Tab。站点多了以后配置文件比代码更易维护。5.2 批量处理与结果汇总有了配置后写一个循环批量处理所有站点把月统计、年统计、趋势结果汇总到一张表里。这样对比不同站点时不用来回翻文件。import yaml import pandas as pd import numpy as np with open(config.yaml, r, encodingutf-8) as f: cfg yaml.safe_load(f) results [] for st in cfg[stations]: df pd.read_csv(st[path], encodingutf-8) df.replace(cfg[missing_values], np.nan, inplaceTrue) df[time] pd.to_datetime(df[st[time_col]], errorscoerce) df df.dropna(subset[time]).sort_values(time) # 单位换算 if st[temp_unit] kelvin: df[st[temp_col]] df[st[temp_col]] - 273.15 # 月统计 df[month] df[time].dt.to_period(M) monthly df.groupby(month)[st[temp_col]].mean() # 年统计 annual df.groupby(df[time].dt.year)[st[temp_col]].mean() results.append({ station: st[id], monthly_mean: monthly.mean(), annual_trend: np.polyfit(annual.index, annual.values, 1)[0] if len(annual) 2 else np.nan }) summary pd.DataFrame(results) summary.to_csv(f{cfg[output_dir]}/summary.csv, indexFalse) print(summary)这段代码遍历配置里的站点统一做缺测替换、时间解析、单位换算然后算月平均和年趋势。np.polyfit做一次多项式拟合返回斜率。参数上len(annual) 2保证至少三年数据才拟合趋势否则返回 NaN。汇总表输出到配置指定的目录方便后续做站点对比。5.3 结果验证用邻近站点交叉检查自动化跑完后别急着写报告。挑几个邻近站点对比它们的月平均气温和年降水量如果差异超过气候学合理范围说明某个站的数据有问题。我一般会画一张散点图横轴是站点 A 的月平均纵轴是站点 B 的月平均理想情况下点应该落在 1:1 线附近。偏离太远的月份回去查原始数据看是缺测、单位还是时间戳问题。import matplotlib.pyplot as plt # 假设 summary 里有 station 和 monthly_mean 列 pivot summary.pivot_table(indexstation, valuesmonthly_mean) # 这里简化处理实际应该按月对齐两个站点的序列 # 画散点图对比两个站点的月平均气温 plt.scatter(monthly_a, monthly_b, alpha0.6) plt.plot([min(monthly_a), max(monthly_a)], [min(monthly_a), max(monthly_a)], r--) plt.xlabel(站点 A 月平均气温 (摄氏度)) plt.ylabel(站点 B 月平均气温 (摄氏度)) plt.savefig(/tmp/met_insight/cross_check.png, dpi150)散点图落在红线附近说明两站一致偏离红线说明有系统偏差。参数上alpha0.6让重叠点可见r--画红色虚线参考线。这个检查能抓住大部分数据质量问题比单站看统计量更可靠。5.4 一个我常犯的错误忽略站点海拔对气温的影响最后说个血泪经验。有次做区域平均气温两个站直线距离不到 20 公里但一个在山顶一个在山谷海拔差 800 米月平均气温差 5 度。我没做海拔订正就直接平均结果整个区域的气温被山顶站拉低。后来学乖了做区域统计前先看站点海拔海拔差超过 200 米的站点要么分开分析要么做气温直减率订正一般每升高 100 米气温降 0.6 度左右。这个教训让我养成了一个习惯拿到站点数据先画一张海拔分布图心里有数再动手算。希望帮到你。本文还有配套的精品资源点击获取