2. 近岸SST数据质量检查与缺测标记
为什么近岸SST必须先做质量检查
近岸SST数据在进入异常识别流程前,必须先经过质量检查。原因在于近岸环境本身存在大量干扰:卫星反演在岸边受陆地污染和混合像元影响,浮标数据可能因生物附着、传感器漂移或通信中断产生缺测与跳变,再分析产品在浅水与岸线附近也可能出现不合理的插值值。若直接计算距平,这些缺陷会被放大成虚假的“异常事件”。因此本案例的任务是:对一份近岸SST数据完成缺测识别、重复记录剔除、物理越界判定、时间跳变检测,输出缺测掩膜与质量标记,并统计各站位的有效数据比例。
数据对象与检查规则
假设我们拿到一份近岸SST观测表,字段包括:station_id(站位编号)、time(观测时间,逐日)、sst(海表温度,℃)、lon、lat。检查规则按以下顺序执行,顺序本身很重要:先剔除重复,再判缺测,再判越界,最后判跳变,避免重复记录干扰跳变判断。
- 重复:同一
station_id与time出现多条记录,保留第一条,其余标记为重复。 - 缺测:
sst为空、NaN 或等于填充值(如 -999)。 - 越界:近岸SST物理合理范围取 -2 ℃ 至 35 ℃,超出即判越界。
- 跳变:同一站位相邻两日SST差值绝对值超过 5 ℃/天,判为可疑跳变(近岸日变化通常远小于此值)。
质量检查与标记的处理链
下图展示从原始记录到质量标记与有效比例统计的处理链,以及每条规则对应的判定条件与输出字段。注意跳变检测依赖“前一个有效值”,因此它必须排在缺测与越界剔除之后。
用Python实现检查与标记
下面给出一个可直接运行的实现。它读取一份示例CSV,按上述顺序生成 qc_flag 与 missing_mask,并输出各站位有效比例。示例数据在代码中直接构造,便于验证逻辑。
import pandas as pd
import numpy as np
# 构造教学示例数据(含重复、缺测、越界、跳变)
rows = [
("S01", "2023-01-01", 18.2), ("S01", "2023-01-02", 18.5),
("S01", "2023-01-02", 18.5), # 重复
("S01", "2023-01-03", np.nan), # 缺测
("S01", "2023-01-04", 19.0),
("S01", "2023-01-05", 40.0), # 越界
("S01", "2023-01-06", 19.4),
("S02", "2023-01-01", 16.0), ("S02", "2023-01-02", 16.3),
("S02", "2023-01-03", 16.1), ("S02", "2023-01-04", 25.0), # 跳变
("S02", "2023-01-05", 16.8),
]
df = pd.DataFrame(rows, columns=["station_id", "time", "sst"])
df["time"] = pd.to_datetime(df["time"])
df = df.sort_values(["station_id", "time"]).reset_index(drop=True)
# ① 重复:同站位同时间保留首条
df["qc_flag"] = 0
dup = df.duplicated(subset=["station_id", "time"], keep="first")
df.loc[dup, "qc_flag"] = 1
# ② 缺测:空值 / NaN / -999
miss = df["sst"].isna() | (df["sst"] == -999)
df.loc[miss & (df["qc_flag"] == 0), "qc_flag"] = 2
# ③ 越界:-2 ~ 35 ℃
out = (df["sst"] < -2) | (df["sst"] > 35)
df.loc[out & (df["qc_flag"] == 0), "qc_flag"] = 3
# ④ 跳变:与前一有效值比较,|Δsst| > 5
df["prev_valid"] = df.groupby("station_id")["sst"].transform(
lambda s: s.where(df.loc[s.index, "qc_flag"] == 0).ffill().shift(1)
)
jump = (df["qc_flag"] == 0) & (df["prev_valid"].notna()) & \
((df["sst"] - df["prev_valid"]).abs() > 5)
df.loc[jump, "qc_flag"] = 4
# 缺测掩膜与有效比例
df["missing_mask"] = df["qc_flag"].isin([2, 3, 4])
valid_ratio = df.groupby("station_id").apply(
lambda g: (g["qc_flag"] == 0).sum() / len(g)
).rename("valid_ratio")
print(df[["station_id", "time", "sst", "qc_flag", "missing_mask"]])
print(valid_ratio)
运行后应观察到:S01 的 2023-01-02 第二条记录 qc_flag=1;2023-01-03 为 qc_flag=2;2023-01-05 的 40.0 ℃ 为 qc_flag=3;S02 的 2023-01-04 的 25.0 ℃ 相对前一有效值 16.1 ℃ 跳变 8.9 ℃,为 qc_flag=4。有效比例 S01 为 4/7≈0.571,S02 为 4/5=0.8(示例数据下的预期结果,实际运行以输出为准)。
shift(1) 而不屏蔽缺测与越界值,跳变判断会被无效值污染,产生误报或漏报。本例通过 where(qc_flag==0) 先屏蔽无效值再前向填充,保证比较对象是真正的有效观测。结果判读与后续使用
质量标记表是后续所有步骤的入口。计算距平时,应只使用 qc_flag==0 的记录;缺测掩膜用于在空间分布图上直接留白,避免把缺测误画成异常。各站位有效比例则用于筛选:若某站位有效比例过低(例如低于 0.6),其距平序列的统计意义不足,应在异常识别前剔除或单独说明。下表给出本例的判读要点。
| qc_flag | 含义 | 后续处理 |
|---|---|---|
| 0 | 有效观测 | 参与距平计算与异常识别 |
| 1 | 重复记录 | 剔除,不参与统计 |
| 2 | 缺测 | 写入缺测掩膜,距平留空 |
| 3 | 物理越界 | 视为无效,检查传感器或反演问题 |
| 4 | 时间跳变 | 标记可疑,人工复核后再决定是否保留 |
完成这一步后,数据已具备进入气候态基准构建的条件:有效观测清晰、缺测位置明确、异常值有据可查。质量标记应随数据一起保存,而不是丢弃,以便在后续发现异常事件时能回溯到原始观测的质量状态。