4. 检测数据清洗基础

海底管道检测数据在进入分析之前,几乎不可能“开箱即用”。无论是漏磁(MFL)内检测、超声测厚、还是ROV外检测,原始数据都会因为传感器掉线、里程计漂移、多次检测拼接、人工录入等原因,出现缺失、重复和异常。清洗不是可选的“美化”步骤,而是决定后续壁厚评估、缺陷增长率和剩余寿命判断是否可信的前置工序。本节围绕管道检测中最常见的三类参数——壁厚、缺陷尺寸、位置——讲清楚清洗的判断依据和具体做法。

先明确数据对象与字段约定

假设我们拿到一份内检测缺陷列表,核心字段包括:defect_id(缺陷编号)、distance_m(距起点里程,单位米,沿管道轴向,起点为发球筒侧)、clock(时钟方位,1~12点,用于环向定位)、depth_pct(缺陷深度占壁厚百分比)、length_mm、width_mm、wall_thickness_mm(该处名义壁厚)、inspection_date。位置参照约定必须全文统一:里程沿介质流动方向递增,环向以管道正上方为12点、顺时针编号。清洗中的任何替换都不能破坏这套参照系。

缺失值:先分类,再决定填还是删

缺失值不能一律删除,也不能一律插补。对管道检测数据,建议按“缺失是否可推断”分三类处理:

  • 可确定性推断:如某缺陷的 wall_thickness_mm 缺失,但同一里程段的设计壁厚或相邻测点壁厚已知,可直接按管段规格补齐,并记录来源。
  • 可统计推断:如少量 depth_pct 缺失,且该缺陷长度、宽度完整,可按同类型缺陷(同方位区间、同尺寸量级)的中位数填补,但必须打上“插补”标记,后续统计时单独说明。
  • 不可推断:如 distance_m 或 clock 缺失,缺陷就无法定位到管体,这类记录应剔除并进入待复核清单,而不是随意填一个位置。

用 pandas 实现时,关键是“先标记、后处理”,保留可追溯性:

import pandas as pd
import numpy as np

df = pd.read_csv("mfl_defects.csv")
df["wt_missing"] = df["wall_thickness_mm"].isna()
df["depth_missing"] = df["depth_pct"].isna()

# 壁厚按同管段规格补齐(示例:按里程分段映射设计壁厚)
seg_wt = {0: 12.7, 5000: 11.1, 12000: 12.7}
def fill_wt(row):
    if pd.notna(row["wall_thickness_mm"]):
        return row["wall_thickness_mm"]
    for start in sorted(seg_wt, reverse=True):
        if row["distance_m"] >= start:
            return seg_wt[start]
    return np.nan
df["wall_thickness_mm"] = df.apply(fill_wt, axis=1)

# 深度缺失:按方位区间中位数插补,并标记
df["depth_pct"] = df.groupby(df["clock"].round())["depth_pct"] \
                    .transform(lambda s: s.fillna(s.median()))
df["depth_imputed"] = df["depth_missing"] & df["depth_pct"].notna()

# 位置缺失的记录剔除,进入复核清单
review = df[df["distance_m"].isna() | df["clock"].isna()]
df = df.dropna(subset=["distance_m", "clock"])

重复记录:按“物理同一缺陷”判重

重复记录常见于两次检测结果拼接、同一缺陷被相邻传感器重复报告。判重不能只看 defect_id,因为不同轮次编号会变。更可靠的做法是按物理位置聚类:里程差小于阈值(例如 0.3 m)、环向方位差小于 1 个钟点、且尺寸量级接近的记录,视为同一缺陷,保留信息最完整或检测时间最新的一条。阈值要结合检测器分辨率设定,不能拍脑袋。

df = df.sort_values("distance_m").reset_index(drop=True)
df["clock_num"] = df["clock"].astype(float)
dup_flag = (
    df["distance_m"].diff().abs().lt(0.3) &
    df["clock_num"].diff().abs().le(1.0)
)
# 标记后人工/规则复核,保留 depth_pct 非空且日期较新者
df["is_dup_candidate"] = dup_flag

异常值:用物理边界和统计规则双重把关

异常值识别要区分“物理不可能”和“统计离群”。物理边界是硬约束:depth_pct 必须在 0~100 之间;length_mm、width_mm 必须为正;clock 必须在 1~12;distance_m 不得超过管道总长。超出即判为录入或单位错误,例如把 12.5 mm 壁厚误录为 125,或把百分比写成小数 0.35 而非 35。

统计离群则用 IQR 或分位数规则,但要按管段、按缺陷类型分组计算,避免把真实的大缺陷误删。例如某管段 depth_pct 的 Q1=8、Q3=22,IQR=14,则上界为 22+1.5×14=43。超过 43% 的记录不直接删除,而是标记为“高深度待复核”,因为深缺陷恰恰是评估重点,误删代价极高。

异常值处理的第一原则是“可疑不等于错误”。对深缺陷、长缺陷,优先复核原始信号和检测报告,确认后再决定保留、修正或剔除,切勿用统计规则批量删除。

替换与修正:留下可审计的痕迹

替换异常值时,应遵循“能回查则回查、不能回查则保守替换”的顺序。单位错误可按量纲换算修正;明显笔误可依据同缺陷其他字段反推;无法回查的,用同管段同类型缺陷的中位数替换,并新增 qc_flag 字段记录处理方式(如 unit_fix、median_fill、manual_review)。这样后续做缺陷增长率对比时,能清楚哪些点来自原始数据、哪些经过修正。

管道检测数据清洗:三类问题与处理路径 原始检测记录 defect_id distance_m clock depth_pct length_mm width_mm wall_thickness_mm inspection_date 含缺失 / 重复 / 异常 缺失值 壁厚→按管段规格补齐 深度→同方位中位数+标记 位置缺失→剔除并复核 重复记录 里程差 < 0.3 m 方位差 ≤ 1 钟点 → 同一缺陷 异常值 物理边界:0≤深度≤100 统计离群:按管段 IQR 标记 深缺陷不删,转人工复核 清洗后数据集 位置完整、可定位 无物理越界值 重复缺陷已合并 qc_flag 记录处理方式 → 进入壁厚评估 待复核清单 位置缺失 / 高深度离群 原则:先标记后处理,可疑不等于错误,深缺陷优先复核,所有替换保留 qc_flag 可追溯

清洗后的自检:三个必须回答的问题

完成上述处理后,不要急着进入分析,先做三项自检。第一,位置完整性:是否所有保留记录的 distance_m 都落在 0 到管道总长之间,clock 是否都在 1~12。第二,数值合理性:depth_pct 的分布是否出现不合理的双峰(常见于百分比与小数混录),length_mm 是否出现数量级跳变。第三,可追溯性:每条被修改的记录是否都有 qc_flag,被剔除的记录是否都进入了复核清单。三项都通过,清洗结果才具备进入壁厚评估和缺陷增长率计算的基础。对管道检测而言,清洗的目标不是让数据“好看”,而是让每一个保留的缺陷都能被准确定位、合理量化,并经得起复核。