气象数据质量控制:缺测、异常值与物理一致性检验

做新能源的人都知道「垃圾进、垃圾出」,但在气象这一环,垃圾常常不是显眼的乱码,而是一个看起来合理、实际错误的数。一个没被识别的 −9999 填充值、一段被云污染的辐射、一个比额定还高的风速峰值,都能在不报错的情况下把功率预测悄悄带偏几个百分点,等月度「两个细则」考核扣了分才回头追溯。质量控制(Quality Control,QC)就是在数据进模型之前,系统性地找出并标记这些坏点的那道关。它不是某个高深算法,而是一套有顺序、可审计的检查流程:先认出缺测,再用物理与气候学边界筛异常,再做要素之间的一致性核查,最后看时空连续性。本文把这套流程拆开讲清,并给出在运梦气象 API 上的落地姿势。
关键要点
- 气象 QC 的危害源不是程序崩溃,而是「看起来合理、实际错误」的数悄悄进了模型——填充值、被污染的辐射、超界风速峰值,都不会报错却会带偏功率预测。
- QC 应做成有固定顺序的流水线:缺测识别 → 范围/物理边界检验 → 要素间物理一致性检验 → 时空连续性(跳变/卡值)检验 → 标缺/插补/留痕,每一步可审计、可回放。
- 缺测的第一杀手是数值型填充值(−9999、1e20)没被转成 NaN 就进了统计量;处理前必须按数据源约定显式识别并转 NaN。
- 物理一致性是再分析独有的优势也是检查抓手:夜间 GHI 应为 0、DNI·cos(天顶角) 与水平直射分量自洽、露点不高于气温、风速与 u/v 分量平方和开方相符——这些跨要素关系比单要素阈值更能揪出伪值。
- 插补必须分量纲、留痕:短缺口连续量可线性插,风要在 u/v 笛卡尔空间插再合成,辐射缺口要分昼夜,长缺口宁可标缺或丢样本;改了多少点、用了什么规则都要记录可追溯。
为什么 QC 是功率预测的隐形地基
风功率预测吃 100m 风,光伏功率预测吃 GHI/DNI/DHI,电量评估吃长序列再分析——这些输入的质量,直接决定了模型能学到的上限。和缺失值会触发异常的业务数据不同,气象数据的坏点往往「类型正确、量级离谱」:填充值 −9999 是个合法的浮点数,被污染的辐射也还是个正数,模型不会拒绝它们,只会默默把它们学进去。
更麻烦的是,单点坏值经过标准化、滑窗、特征工程之后会扩散。一个 −9999 进了均值,整列统计量被带偏;一段错误的辐射进了滑动平均,前后好几个时刻都被污染。等你在预测残差里看到异常,根因可能已经隔了好几层。所以 QC 的价值不在「补救」,而在「前置」——把坏点挡在特征工程之前,是性价比最高的一道防线。
再分析数据(如 ERA5)在这点上有个天然优势:它由数值模式同化海量观测得到,物理上自洽、时空连续,不像散点站观测那样动辄整段缺。但「连续」不等于「无坏点」。解码错、单位换错、插值引入伪值、源端个别格点异常,都会让一段物理上本该平滑的序列出现说不通的数。QC 要做的,正是把这些「说不通」找出来。
QC 流水线的四道检查
把 QC 拆成有固定顺序的四步,每一步只解决一类问题,前一步的输出是后一步的输入。
一、缺测识别:先把「假数」翻译成 NaN
最优先、也最容易被忽略的一步。气象文件里的缺测不是空白,而是一个填充值(fill value):NetCDF/GRIB 里常见 −9999、1e20、−32767,有的源直接用 NaN。这些数值型填充值如果不先识别,会被当成正常观测进入后续所有计算。
纪律只有一条:在 pipeline 入口处、按数据源约定把所有 fill value 显式转成 NaN,绝不让 −9999 这种「合法浮点」流进均值或标准化。转完之后再统计每个要素、每个时段的缺测率,缺测率本身就是一个数据可用性指标——某要素某段缺测率过高,就该整段降级或弃用,而不是硬补。
二、范围与物理边界检验:单要素的硬上下界
把每个要素和它的物理/气候学边界比一比,超界的标为可疑。这是最便宜的一道筛子:
- 相对湿度必须落在 0–100%,算出 105% 一定是插值或换算引入的伪值;
- 地面风速一般 < 60 m/s,出现 80 m/s 多半是解码或单位错;
- 夜间 GHI(rsds)应为 0,深夜出现非零辐射说明时间轴或单位有问题;
- 气温应落在场站气候范围内,沙漠场站出现 −30°C 显然异常;
- 气压用 hPa 时地面值多在 600–1040 区间,出现 9999 八成是没转的填充值。
边界要分「物理硬界」(如湿度 0–100%,越界必错)和「气候学软界」(如本地历史 3σ 或分位数,越界可疑但未必错)。硬界直接判错,软界只标记、留待人工或下游复核,不要一刀切删掉极端但真实的天气过程——极端天气恰恰是功率预测最该学的部分。
三、要素间物理一致性检验:跨要素才是再分析的看家本领
单要素阈值能抓住离谱值,抓不住「每个都在范围内、组合起来却说不通」的错。物理一致性检验就是利用气象要素之间的客观约束来交叉验证,这是再分析数据最值得用、也最能暴露隐藏错误的一层:
- 辐射三分量自洽:GHI 应约等于 DNI·cos(天顶角) + DHI。三者各自都在合理范围,但加不到一起,说明某个分量被污染或分解算法出错。
- 辐射与昼夜自洽:用太阳位置算出的天顶角 > 90°(太阳在地平线下)时,直射分量应为 0;夜间出现直射辐射必是相位或单位错。
- 湿度与温度自洽:露点温度恒不高于气温,湿球温度介于露点与气温之间;露点超过气温就是矛盾值。
- 风速与分量自洽:合成风速应等于 u/v 分量的平方和开方(ws = √(u²+v²));对不上说明 u/v 与 ws/wd 来自不同口径或被错配。
- 累积量单调性:辐射、降水等累积量在一个累积窗口内应单调不减,逐时差分出现负值就提示累积起点约定没处理对。
这些关系不依赖外部真值,纯靠物理自洽就能查错,是 QC 里信噪比最高的检查。把它们写成一组断言,比堆一堆单要素阈值有用得多。
四、时空连续性检验:跳变、卡值与空间孤立点
最后看时间和空间维度上的「不连续」:
- 时间跳变(spike):相邻时刻的变化超过物理可能的速率(如气温 1 小时跳 20°C),多为单点坏值;
- 卡值(flat-line / persistence):某要素连续多个时刻完全不变,传感器或解码卡死的典型特征,再分析里整段常值往往是源端缺测被填了常数;
- 空间孤立点:某格点的值与周边格点显著不一致(可用相邻格点中位数比对),孤立的「热点/冷点」多是源端异常。
时间检查对逐时序列尤其重要,因为下游的爬坡事件识别、超短期预测都对相邻时刻的真实变化率敏感,一个 spike 就能制造一个假的爬坡信号。
异常之后:标缺、插补与留痕
检查出可疑点不是终点,关键是怎么处理,而处理必须分量纲、可追溯:
- 优先标缺而非默默修改:把可疑点先标成 NaN 和一个质量标志位,保留原值,不要直接覆盖——一旦事后发现误判,原始值还能找回。
- 插补分量纲:短缺口(1–2 小时)的连续量(气温、气压)可线性插值;风必须在 u/v 笛卡尔空间插值再合成风速风向,绝不能对风向角度直接线性插值(359° 和 1° 会插出 180° 的荒谬结果);辐射缺口要分昼夜,夜间补 0、白天宁可标缺也别乱插;长缺口(> 6 小时)不硬补,宁可丢弃该样本或用同源邻近格点回填。
- 全程留痕:每一步记录改了多少点、用了什么规则、质量标志位是什么。质量问题最怕的不是有坏点,而是坏点被悄悄抹平、事后无从追溯——一条可回放的 QC 日志,比任何漂亮的插补曲线都重要。
一个实用的工程结构是把「识别 → 标缺 → 插补 → 留痕」做成 pipeline 里独立、可审计的一段,输入原始序列、输出清洗后序列加一份质量报告,让 QC 既不混进业务逻辑、又随时可复核。
落地:在运梦气象 API 上做 QC
运梦气象 API 在源头替你挡掉了一部分 QC 负担:它对外统一用 CF 命名、tas 直接返回 ℃、rsds 直接返回 W/m²、timezone 为必填项,并以统一 JSON envelope(外层 code/success/data/msg/errorCode,data 里是 timeList 加各字段等长平行数组)返回,从根上消除了命名打架、单位漏换、时区漂移这几类「伪缺测/伪异常」。历史回测与长序列质量分析用 ERA5(dataSourceId="era5",可取 1950 年至今),短期预报用德国气象局(dataSourceId="ger",覆盖未来约 7 天)。
但源头归一不等于免做 QC——数组对齐、物理一致性、时空连续性这几层仍要在你这边落地。下面是一段最小骨架,覆盖「状态校验 → 数组等长校验 → 单要素边界 → 风速与分量自洽 → 标缺」:
import os
import numpy as np
import pandas as pd
import requests
API = "https://console.yun-meng.top/api/energy-weather/search/weather/action/downloadSync"
TOKEN = os.environ["YUNMENG_TOKEN"] # 控制台创建的 sk- API Key
payload = {
"dataSourceId": "era5", # 历史质量分析;短期预报改成 "ger"
"lat": 32.03253,
"lon": 117.35184,
"stime": "2024-06-01 00:00",
"etime": "2024-06-07 23:00",
"timezone": "8", # 必填:东八区,源头对齐时间轴
"fields": ["tas", "hurs", "ws", "uas", "vas", "rsds"],
}
resp = requests.post(
API,
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json=payload,
timeout=600,
)
resp.raise_for_status() # 1) 过 HTTP 层
result = resp.json()
if not result.get("success"): # 2) 判业务层状态,失败时 data 可能为 null
raise RuntimeError(result.get("msg", "查询失败"))
data = result["data"]
time_list = data["timeList"]
# 3) 数组等长校验:任一字段与 timeList 长度对不上即不可信
for f in ("tas", "hurs", "ws", "uas", "vas", "rsds"):
assert len(data[f]) == len(time_list), f"字段 {f} 与 timeList 长度不一致"
df = pd.DataFrame({f: data[f] for f in payload["fields"]},
index=pd.to_datetime(time_list))
# 4) 单要素物理边界(硬界越界→标 NaN,保留质量标志)
flags = pd.DataFrame(index=df.index)
flags["hurs_oob"] = ~df["hurs"].between(0, 100) # 相对湿度 0–100%
flags["ws_oob"] = ~df["ws"].between(0, 60) # 地面风速 < 60 m/s
flags["rsds_neg"] = df["rsds"] < 0 # 辐射不应为负
# 5) 要素间物理一致性:同高度合成风速应 ≈ √(u²+v²)
# ws 与 uas/vas 同为 10m 口径,可直接交叉验证;切忌拿 10m 风速去对 100m 分量
ws10 = np.hypot(df["uas"], df["vas"])
flags["wind_inconsistent"] = (ws10 - df["ws"]).abs() > 1 # 阈值按场站标定
# 6) 标缺而非覆盖:可疑点置 NaN,原值已在原始响应里留底
suspect = flags.any(axis=1)
clean = df.mask(suspect.values[:, None] & df.columns.isin(["hurs", "ws", "rsds"]))
print(f"共 {len(df)} 条,疑似坏点 {int(suspect.sum())} 个")
print(clean.head())
注意几个细节:先判 success 再取 data,避免失败响应里 data 为 null 时静默拿空列表;数组等长校验挡住下标错位;边界检验只对硬界判错、用质量标志位记录而非直接删;风速自洽用同高度 u/v 平方和开方交叉验证(注意 ws 与 uas/vas 同为 10m 口径,切勿拿 10m 风速去对 100m 分量);可疑点 mask 成 NaN 而非覆盖,原始值在响应里仍可回溯。这套骨架可以直接扩成完整的 QC 流水线,把缺测、异常、不一致一次性挡在功率预测模型之外。
常见问题
气象数据已经是再分析了,为什么还要做质量控制?
再分析(如 ERA5)由数值模式同化观测得到,物理自洽、时空连续,确实比散点站观测干净,但「连续」不等于「无坏点」。解码错、单位换错、插值伪值、源端个别格点异常都会引入说不通的数,且这些坏点往往「类型正确、量级离谱」,模型不会报错只会默默学进去。QC 的价值在前置——把坏点挡在特征工程之前。
缺测处理最容易踩的坑是什么?
数值型填充值(−9999、1e20、−32767)没被识别成缺测就进了统计量。这类值是合法浮点,不报错,但一个 −9999 进均值就能把整列统计量带偏。处理前必须在 pipeline 入口按数据源约定把所有 fill value 显式转成 NaN。
物理一致性检验具体查什么?
利用要素间客观约束交叉验证:GHI ≈ DNI·cos(天顶角)+DHI、夜间直射应为 0、露点不高于气温、合成风速等于 u/v 分量平方和开方、累积量在窗口内单调不减。这些关系不依赖外部真值,纯靠物理自洽就能揪出「每个都在范围内、组合起来却矛盾」的隐藏错误,是 QC 里信噪比最高的一层。
风的缺口可以直接对风向角度做线性插值吗?
不能。风向是 0°/360° 环绕的角度量,直接线性插值会让 359° 和 1° 插出 180° 的荒谬结果。正确做法是在 u/v 笛卡尔空间插值,再合成风速风向;辐射缺口要分昼夜,长缺口宁可标缺或丢样本。
运梦气象 API 在 QC 上帮我做了哪些、还需要我自己做哪些?
API 在源头统一了 CF 命名、单位(tas 返 ℃、rsds 返 W/m²)和时区(timezone 必填),消除了命名打架、单位漏换、时区漂移这类伪缺测/伪异常。但数组对齐、物理一致性、时空连续性检验仍需你在接入侧落地:先判 success、再校验各字段数组与 timeList 等长、再做边界与一致性检查,把坏点标缺留痕后才进模型。
收尾
气象数据的质量控制,本质上是在数据进模型之前,用一套有顺序、可审计的检查把「看起来合理、实际错误」的数找出来:先认缺测、再筛边界、再做要素间物理一致性、最后看时空连续性,可疑点标缺留痕而非默默修改。这些都不是高深算法,但每一道关都能挡掉一类让功率预测悄悄失准的隐患。把 QC 做成 pipeline 里独立、可回放的一段,配合运梦气象 API 在命名、单位、时区上的源头归一,数据这一层就能成为预测精度可靠的地基,而不是事后追溯的黑洞。
参考与延伸阅读
- World Meteorological Organization. Guide to Instruments and Methods of Observation (WMO-No. 8), Volume V: Quality Assurance and Management of Observing Systems. https://library.wmo.int/idurl/4/68695
- ECMWF / Copernicus Climate Change Service. ERA5: data documentation (Copernicus Knowledge Base). https://confluence.ecmwf.int/display/CKB/ERA5%3A+data+documentation
- Eaton, B., Gregory, J., Drach, B., Taylor, K., Hankin, S., et al. NetCDF Climate and Forecast (CF) Metadata Conventions. https://cfconventions.org/
- 运梦气象 API:ERA5 数据源产品页、数据要素全量目录、API 参考。