1. 单站逐时潮位序列的预处理与质量检查

为什么预处理决定后续分潮参数的可靠性

调和分析的最小二乘拟合假设输入序列是等间隔、同一时间基准、同一单位、无粗差的逐时潮位。原始观测往往同时存在三类问题:缺测(仪器故障、通信中断)、异常值(船舶撞击、气泡、结冰、传感器漂移)、以及时间与单位不统一(地方时/UTC混用、单位混用为cm与m)。这些缺陷若直接进入设计矩阵,会污染M2、S2、K1、O1的振幅与迟角估计,且残差检查难以定位原因。因此本例的任务是:把一段原始逐时序列处理成可追溯的干净序列,并留下处理日志。

处理流程与判定逻辑

下面这张图给出本例的核心处理链:从原始序列出发,依次经过时间基准统一、单位换算、缺测识别、异常值标记与插补决策,最终输出干净序列与处理日志。图中同时标出每一步的判定条件与记录内容,便于复现。

原始逐时潮位 时间戳 + 水位值 含缺测/异常/单位混杂 ① 时间基准统一 地方时 → UTC 检查等间隔=1h ② 单位换算 cm → m(÷100) 统一保留3位小数 ③ 缺测识别 空值/哨兵值(-999) 时间戳跳变 ④ 异常值标记 |Δh| > 阈值 或超出物理上下界 标记不删除 ⑤ 插补决策 连续缺测 ≤ 3h 线性插补 否则标记为不可用 ⑥ 干净序列输出 等间隔逐时序列 单位 m,UTC 附质量标记列 处理日志 缺测数/位置 异常数/位置 插补数/方法 关键判定条件与记录内容 缺测判定 空值、-999、时间戳间隔≠1h 记录:缺测时刻、连续长度 异常值判定 |h(t)-h(t-1)| > 1.5 m/h 或 h 超出站点历史极值范围 插补判定 连续缺测 ≤ 3h:线性插补 连续缺测 > 3h:标记不可用 处理日志字段(每步追加一行) 时间戳 | 原始值 | 处理动作(时间转换/单位换算/缺测标记/异常标记/插补) | 处理后值 | 质量标记(0=正常,1=插补,2=异常,3=不可用) | 备注 日志需可回溯:任何被修改的值都能定位到原始记录与处理依据

用一段可运行的Python完成预处理

下面代码以教学示例数据演示完整流程。示例序列为24小时逐时潮位,故意植入1个缺测(-999)、1个尖峰异常(+3.2 m跳变)、以及单位混用(部分为cm)。代码输出干净序列与处理日志,可直接运行验证。

import pandas as pd
import numpy as np

# ---------- 1. 构造教学示例原始序列 ----------
# 时间:2024-06-01 00:00 至 23:00,地方时 UTC+8
times = pd.date_range("2024-06-01 00:00", periods=24, freq="h", tz="Asia/Shanghai")
# 模拟半日潮,振幅约1.2m,均值2.0m
h_true = 2.0 + 1.2 * np.sin(2 * np.pi * np.arange(24) / 12.42)
raw = h_true.copy()
raw[5] = -999.0          # 缺测哨兵值
raw[12] = raw[12] + 3.2  # 尖峰异常
raw[18] = raw[18] * 100  # 单位误为cm
raw = np.round(raw, 3)

df = pd.DataFrame({"time_local": times, "raw": raw})

# ---------- 2. 时间基准统一:地方时 -> UTC ----------
df["time_utc"] = df["time_local"].dt.tz_convert("UTC")

# ---------- 3. 单位换算:识别cm量级并转为m ----------
# 站点历史水位范围约 0.5~3.5 m,超过10视为cm
df["h_m"] = np.where(df["raw"].abs() > 10, df["raw"] / 100.0, df["raw"])

# ---------- 4. 缺测识别 ----------
df["is_missing"] = (df["h_m"] == -999.0) | df["h_m"].isna()

# ---------- 5. 异常值标记(基于一阶差分) ----------
df["dh"] = df["h_m"].diff().abs()
# 阈值1.5 m/h;缺测点不参与差分判断
df["is_outlier"] = (df["dh"] > 1.5) & (~df["is_missing"])
# 异常点不删除,先标记
df.loc[df["is_outlier"], "h_m"] = np.nan

# ---------- 6. 插补决策:连续缺测<=3h线性插补 ----------
df["h_clean"] = df["h_m"].interpolate(method="linear", limit=3, limit_direction="both")
df["quality"] = 0
df.loc[df["is_missing"], "quality"] = 1
df.loc[df["is_outlier"], "quality"] = 2
df.loc[df["h_clean"].isna(), "quality"] = 3

# ---------- 7. 输出干净序列与处理日志 ----------
clean = df[["time_utc", "h_clean", "quality"]].rename(columns={"h_clean": "h_m"})
log = df[["time_utc", "raw", "h_m", "quality"]].copy()
log["action"] = "正常"
log.loc[df["is_missing"], "action"] = "缺测标记"
log.loc[df["is_outlier"], "action"] = "异常标记"
log.loc[df["quality"] == 1, "action"] = "缺测+插补"
log.loc[df["quality"] == 3, "action"] = "不可用"

print("干净序列前5行:")
print(clean.head())
print("\n处理日志(仅显示非正常记录):")
print(log[log["action"] != "正常"])
print("\n统计:缺测", df["is_missing"].sum(), "个,异常", df["is_outlier"].sum(), "个,不可用", (df["quality"]==3).sum(), "个")

结果判读与通过条件

运行后应看到:缺测1个(第5小时)、异常1个(第12小时)、单位换算1个(第18小时)。第5小时与第12小时因连续缺测长度均为1(≤3h),被线性插补,质量标记为1或2;第18小时经单位换算后恢复正常,质量标记为0。若某段连续缺测超过3小时,该段质量标记为3,不参与后续最小二乘拟合。通过条件为:输出序列时间间隔严格为1h、单位为m、无-999哨兵值、质量标记列完整、日志中每条修改均可回溯到原始值。

注意:异常值阈值1.5 m/h需按站点潮差调整。强潮河口半日潮涨落可达数米每小时,固定阈值会误判正常潮汐为异常。建议先用站点历史序列统计一阶差分的99%分位数作为阈值,并在日志中记录阈值来源。
时间基准统一时务必确认原始时间戳的时区。若原始记录为地方时但未标注时区,需向数据提供方确认,不可默认按UTC处理,否则后续分潮迟角会整体偏移。