第3章 数据质量检查与清洗流程
机会评分模型对输入数据的质量高度敏感。缺失值、异常值和不一致数据会直接扭曲评分分布,导致模型区分度下降甚至失效。本章围绕缺失值处理规范、异常值识别标准和数据一致性验证三个核心环节,建立可执行的清洗SOP,确保进入建模环节的数据符合质量门槛。
适用场景
- 原始机会数据首次入库后,进入特征工程前的强制质检环节。
- 模型迭代时增量数据接入后的质量复核。
- 跨系统(CRM、ERP、第三方数据源)合并数据后的冲突消解。
- 定期(月度/季度)数据健康度巡检。
前置条件
- 已完成数据采集与初步集成(参见第2章)。
- 已定义字段级元数据:字段名称、数据类型、取值范围、是否可空。
- 已准备数据质量检查环境(Python 3.8+ / SQL 分析工具 / 可视化平台)。
- 已制定缺失值、异常值、一致性规则的业务阈值文档。
操作步骤
以下流程围绕三个检查维度并行展开,完成后合并输出数据质量报告。
核心原则:先检查后处理;保留原始字段快照;所有修改必须可追溯;阈值需经业务方确认。
步骤1:数据加载与字段画像
- 加载目标数据集,记录总行数、字段数、内存占用。
- 对每个字段计算:非空计数、唯一值计数、均值/中位数/标准差(数值型)、频数分布(分类型)。
- 生成字段画像摘要表,作为后续清洗的基线。
import pandas as pd
import numpy as np
df = pd.read_parquet("opportunities_raw.parquet")
profile = {
"total_rows": len(df),
"fields": []
}
for col in df.columns:
info = {
"field": col,
"dtype": str(df[col].dtype),
"non_null": int(df[col].notna().sum()),
"null_pct": round(df[col].isna().mean() * 100, 2),
"n_unique": int(df[col].nunique())
}
if df[col].dtype in ["float64", "int64"]:
info["mean"] = round(df[col].mean(), 2)
info["std"] = round(df[col].std(), 2)
info["min"] = round(df[col].min(), 2)
info["max"] = round(df[col].max(), 2)
profile["fields"].append(info)
步骤2:缺失值处理规范
- 缺失模式识别:计算每个字段缺失率,标记缺失率 > 50% 的字段为高风险;绘制缺失矩阵图,识别系统性缺失。
- 缺失机制判定:区分完全随机缺失(MCAR)、随机缺失(MAR)、非随机缺失(MNAR),不同机制选择不同处理策略。
- 处理策略选择:
- 缺失率 < 1%:直接删除缺失行(需记录删除行数)。
- 缺失率 1%–30%:根据字段类型选择填充方式(均值/中位数/众数/前向填充/模型预测)。
- 缺失率 > 30% 且无法补充:删除该字段并记录原因。
- 插值与填充执行:对关键连续型字段使用多重插补(MICE)或KNN插值;分类字段使用众数或“未知”标记。
# 缺失率计算与标记
null_pct = df.isna().mean().sort_values(ascending=False)
high_risk = null_pct[null_pct > 0.5].index.tolist()
print("高风险字段(缺失率>50%):", high_risk)
# 均值填充示例(MAR机制)
df["amount_filled"] = df["amount"].fillna(df["amount"].median())
# 多重插补(使用sklearn的IterativeImputer)
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
imputer = IterativeImputer(max_iter=10, random_state=42)
df_imputed = imputer.fit_transform(df[numeric_cols])
步骤3:异常值识别标准
- 统计分布检测:
- Z-score 法:|Z| > 3 标记为异常(适用于近似正态分布数据)。
- IQR 法:低于 Q1−1.5×IQR 或高于 Q3+1.5×IQR 标记为温和异常;3×IQR 为极端异常。
- 领域规则校验:根据业务阈值直接判定(如:机会金额 ≤ 0 或 > 年预算上限、概率值不在 [0,1] 区间)。
- 异常值标记与处理:
- 创建异常标记字段(is_outlier),记录异常原因。
- 业务可解释的异常:保留但参与模型时降权;不可解释的异常:删除或截尾处理(winsorize)。
# Z-score 异常检测
from scipy import stats
z_scores = np.abs(stats.zscore(df["score"]))
df["score_outlier_z"] = z_scores > 3
# IQR 异常检测
Q1 = df["amount"].quantile(0.25)
Q3 = df["amount"].quantile(0.75)
IQR = Q3 - Q1
df["amount_outlier_iqr"] = (df["amount"] < (Q1 - 1.5*IQR)) | (df["amount"] > (Q3 + 1.5*IQR))
# 领域规则:金额必须 > 0 且 < 500万
df["amount_outlier_biz"] = (df["amount"] <= 0) | (df["amount"] > 5_000_000)
步骤4:数据一致性验证
- 格式校验:日期字段统一为 YYYY-MM-DD;电话号码、邮箱按正则表达式校验;编码字段匹配字典表。
- 范围校验:数值字段是否在声明范围内;分类字段值是否在允许列表中。
- 跨字段逻辑校验:
- “成交日期” ≥ “创建日期”。
- “机会金额” = Σ“产品行项金额”。
- “客户行业”与“产品线”的适用关系。
- 不一致数据追溯与修正:记录不一致记录ID,按优先级修正;无法修正的标记并隔离。
-- SQL 跨字段一致性检查示例
SELECT
opportunity_id,
create_date,
close_date,
CASE
WHEN close_date < create_date THEN '日期倒挂'
WHEN close_date = create_date THEN '同日关闭'
ELSE '正常'
END AS date_consistency,
amount,
(SELECT SUM(line_amount) FROM line_items WHERE line_items.opp_id = opp.opportunity_id) AS calc_amount,
CASE
WHEN ABS(amount - calc_amount) > 0.01 THEN '金额不匹配'
ELSE '一致'
END AS amount_consistency
FROM opportunities opp
WHERE close_date IS NOT NULL;
步骤5:生成数据质量报告
- 汇总各维度检查结果:缺失字段列表、异常值统计、不一致记录清单。
- 输出清洗前后对比指标:行数变化、缺失率变化、异常值占比。
- 报告经数据负责人与业务方会签后归档。
输入输出物
| 类别 | 名称 | 格式 | 说明 |
|---|---|---|---|
| 输入 | 原始机会数据集 | Parquet / CSV / 数据库表 | 待检查的原始业务数据 |
| 输入 | 字段元数据字典 | JSON / YAML / 数据字典文档 | 字段名、类型、允许值、是否可空 |
| 输入 | 业务阈值规则表 | Excel / 配置文件 | 缺失率阈值、异常值阈值、一致性规则 |
| 输出 | 清洗后数据集 | Parquet / CSV | 经过缺失、异常、一致性处理后的数据 |
| 输出 | 数据质量报告 | PDF / HTML | 包含检查结果、处理记录、清洗前后对比 |
| 输出 | 操作日志 | JSON Lines / 数据库日志表 | 每步操作的详细记录,用于审计追溯 |
责任边界
| 角色 | 职责 | 决策权 |
|---|---|---|
| 数据工程师 | 编写并执行清洗脚本;生成数据质量报告 | 技术方案选择(插值算法、检测方法) |
| 数据分析师 | 验证清洗逻辑的合理性;审核质量报告 | 阈值微调建议;标记字段是否可删除 |
| 业务负责人 | 确认业务规则与阈值;审批最终数据质量报告 | 高缺失字段保留/删除决策;异常值业务含义判定 |
| 模型负责人 | 接收清洗后数据并确认可用性 | 对清洗结果提出补充要求 |
记录材料
- 数据检查清单:逐字段记录缺失率、异常值数、不一致数及处理方式。
- 清洗操作日志:每步操作的时间、执行人、影响行数、参数配置。
- 例外事项登记表:无法自动处理的异常记录,标注原因和人工处理状态。
- 数据质量报告:包含清洗前基线、清洗后指标、对比分析、审批签字。
# 清洗操作日志示例结构
{
"timestamp": "2025-03-21T14:30:00Z",
"operator": "data_engineer_01",
"action": "缺失值填充",
"field": "amount",
"method": "中位数填充",
"affected_rows": 1247,
"params": {"median_value": 85000.0},
"status": "completed"
}
异常处理
常见异常与应对措施: 遇到以下情况时暂停自动清洗流程,升级至人工判定。
| 异常场景 | 识别标志 | 处理措施 |
|---|---|---|
| 全字段缺失率 > 80% | 字段画像中大部分字段缺失率极高 | 停止清洗,检查数据源是否采集失败;与数据源确认后重新拉取 |
| 异常值占比 > 15% | Z-score / IQR 标记异常比例异常高 | 重新审视阈值设定;排查是否存在数据录入系统故障 |
| 跨字段一致性严重冲突 | 超过 20% 记录存在逻辑矛盾 | 检查ETL映射关系;与业务确认规则定义是否准确 |
| 插值后数据分布畸变 | 填充后均值/标准差突变 > 30% | 改用更保守的填充策略(如前向填充);评估是否删除该字段 |
| 数据量级不符合预期 | 行数与上一周期差异 > 50% | 核对数据源抽取范围;检查过滤条件是否正确 |
验收标准
- 缺失率达标:所有字段缺失率 ≤ 5%(业务特批字段除外),且已记录缺失原因。
- 异常值受控:异常值占比 ≤ 3%,所有异常记录已标记 is_outlier 字段并填写异常原因。
- 一致性通过:核心逻辑规则(日期顺序、金额匹配、编码合法性)通过率 = 100%,不一致记录已隔离或修正。
- 可追溯:所有数据修改操作均有日志记录,原始数据快照完整保留。
- 报告完备:数据质量报告包含三个维度的检查结果、处理明细、清洗前后对比,并经业务负责人签字确认。
- 模型可用性确认:模型负责人运行基础特征工程脚本无报错,特征分布符合预期。
经验提示: 数据清洗不是一次性的。建议在模型训练集、验证集、测试集上分别执行相同的清洗逻辑,并检查清洗后分布是否一致。若分布差异显著,说明清洗规则对数据子集有偏向,需要调整策略。