3. 数据质量初探:缺失值检测与异常值识别

在风控样本偏差校验中,数据质量是后续所有分析的基础。缺失值和异常值会直接扭曲样本分布,导致模型训练偏差与风控策略误判。本节将系统讲解缺失值检测与异常值识别的核心方法及其 SQL 实现。

3.1 缺失值检测

缺失值检测是数据质量检查的第一步。SQL 中主要通过 IS NULL 条件以及 COUNT 与 COUNT(column) 的差异来实现。

3.1.1 IS NULL 与 IS NOT NULL

使用 WHERE 子句配合 IS NULL 可以筛选出某列为空的记录,是逐行排查缺失值的基础手段。

-- 查询 loan_amount 字段为空的记录
SELECT *
FROM loan_application
WHERE loan_amount IS NULL;

-- 统计 loan_amount 字段非空的记录数
SELECT COUNT(*)
FROM loan_application
WHERE loan_amount IS NOT NULL;

3.1.2 COUNT(*) vs COUNT(column) 的差异

这是 SQL 初学者最容易混淆的知识点,也是数据质量检测中的关键陷阱。

函数 行为 是否忽略 NULL 典型用途
COUNT(*) 统计表中所有行数 不忽略,包含 NULL 行 总记录数
COUNT(1) 与 COUNT(*) 等价 不忽略,包含 NULL 行 总记录数(性能优化场景)
COUNT(column) 统计该列非 NULL 值的行数 忽略 NULL 有效值数量
COUNT(DISTINCT column) 统计该列非 NULL 且不重复的值数量 忽略 NULL 唯一值数量
风控实战要点: 通过 COUNT(*) - COUNT(column) 可以快速计算某列的缺失数量。在风控样本校验中,若某关键特征(如收入、负债率)缺失率超过 30%,需评估是否引入偏差。
-- 计算 loan_application 表中各字段的缺失率
SELECT
  COUNT(*) AS total_rows,
  COUNT(loan_amount) AS loan_amount_non_null,
  COUNT(income) AS income_non_null,
  COUNT(credit_score) AS credit_score_non_null,
  ROUND((COUNT(*) - COUNT(loan_amount)) * 100.0 / COUNT(*), 2) AS loan_amount_missing_pct,
  ROUND((COUNT(*) - COUNT(income)) * 100.0 / COUNT(*), 2) AS income_missing_pct,
  ROUND((COUNT(*) - COUNT(credit_score)) * 100.0 / COUNT(*), 2) AS credit_score_missing_pct
FROM loan_application;
缺失值检测流程 原始数据表 IS NULL 筛选 COUNT(*) vs COUNT(col) 缺失值明细记录 缺失数量 / 缺失率 ⚠ 风控提示 缺失率 > 30% 需评估偏差 缺失非随机时需做样本校验 建议记录缺失模式与分布

3.2 异常值识别

异常值(Outlier)是指显著偏离数据集中趋势的观测值。在风控场景中,异常值可能代表欺诈、数据录入错误或极端信用行为。本节介绍两种最常用的统计方法:Z-Score 与 IQR。

3.2.1 Z-Score 方法(基于正态分布假设)

Z-Score 衡量数据点与均值的偏离程度,以标准差为单位。通常认为 |Z| > 3 的数据点为异常值。

计算公式: Z = (x - μ) / σ

其中 μ 为均值,σ 为标准差。

-- 计算 loan_amount 的 Z-Score 并标记异常值
WITH stats AS (
  SELECT
    AVG(loan_amount) AS mean_val,
    STDDEV(loan_amount) AS std_val
  FROM loan_application
  WHERE loan_amount IS NOT NULL
)
SELECT
  id,
  loan_amount,
  (loan_amount - stats.mean_val) / NULLIF(stats.std_val, 0) AS z_score,
  CASE
    WHEN ABS((loan_amount - stats.mean_val) / NULLIF(stats.std_val, 0)) > 3
    THEN '异常'
    ELSE '正常'
  END AS outlier_flag
FROM loan_application, stats
WHERE loan_amount IS NOT NULL;
注意事项: Z-Score 对极端值敏感,且要求数据近似正态分布。在风控数据中,收入、贷款金额等字段通常呈右偏分布,建议先做对数变换或使用稳健 Z-Score(基于中位数和 MAD)。

3.2.2 IQR 方法(四分位距法,稳健型)

IQR 方法不依赖正态分布假设,通过四分位数识别异常值,具有更好的稳健性。

计算步骤:

  1. 计算第一四分位数 Q1(25% 分位数)和第三四分位数 Q3(75% 分位数)
  2. 计算四分位距 IQR = Q3 - Q1
  3. 定义异常值边界:下限 = Q1 - 1.5 × IQR,上限 = Q3 + 1.5 × IQR
  4. 超出上下限的数据点视为异常值
-- 使用 IQR 方法识别 loan_amount 的异常值
WITH quartiles AS (
  SELECT
    PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY loan_amount) AS q1,
    PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY loan_amount) AS q3
  FROM loan_application
  WHERE loan_amount IS NOT NULL
)
SELECT
  id,
  loan_amount,
  q1,
  q3,
  (q3 - q1) AS iqr,
  (q1 - 1.5 * (q3 - q1)) AS lower_bound,
  (q3 + 1.5 * (q3 - q1)) AS upper_bound,
  CASE
    WHEN loan_amount < (q1 - 1.5 * (q3 - q1))
      OR loan_amount > (q3 + 1.5 * (q3 - q1))
    THEN '异常'
    ELSE '正常'
  END AS outlier_flag
FROM loan_application, quartiles
WHERE loan_amount IS NOT NULL;

3.2.3 两种方法对比

对比维度 Z-Score IQR
分布假设 近似正态分布 无分布假设
稳健性 低,受极端值影响大 高,基于分位数
适用场景 对称分布、样本量较大 偏态分布、含极端值
风控常用 信用评分卡标准化 收入、负债、交易金额
SQL 实现复杂度 低(AVG + STDDEV) 中(PERCENTILE_CONT)
异常值识别方法对比 Z-Score 方法 计算均值 μ 计算标准差 σ Z = (x-μ)/σ |Z| > 3 异常 IQR 方法 计算 Q1, Q3 IQR = Q3-Q1 边界 ±1.5×IQR 超边界即异常 需正态假设 稳健无假设

3.3 风控样本偏差校验中的实践建议

最佳实践: 在风控样本偏差校验中,建议将缺失值检测与异常值识别联合使用。先剔除或标记缺失值,再对连续型特征进行异常值识别。对于识别出的异常值,不要直接删除,应结合业务逻辑判断其是否为真实欺诈样本或数据错误。
  • 缺失值处理策略: 缺失率 < 5% 可删除或均值填充;5%-30% 需分析缺失机制(MCAR/MAR/MNAR);> 30% 需评估特征可用性。
  • 异常值处理策略: 先使用 IQR 方法做初步筛查,再结合业务规则(如贷款金额不超过收入的 10 倍)做二次校验。
  • 样本偏差校验联动: 缺失值和异常值的分布差异可能揭示样本选择偏差。例如,拒绝样本中收入缺失率显著高于通过样本,需做 Heckman 校正或倾向得分加权。
-- 综合缺失值与异常值检测的样本质量报告
WITH missing_stats AS (
  SELECT
    COUNT(*) AS total,
    COUNT(income) AS income_non_null,
    COUNT(loan_amount) AS loan_amount_non_null,
    COUNT(credit_score) AS credit_score_non_null
  FROM loan_application
),
outlier_stats AS (
  SELECT
    COUNT(CASE WHEN loan_amount < (q1 - 1.5 * iqr) OR loan_amount > (q3 + 1.5 * iqr) THEN 1 END) AS loan_amount_outliers
  FROM (
    SELECT
      PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY loan_amount) AS q1,
      PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY loan_amount) AS q3,
      PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY loan_amount) - PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY loan_amount) AS iqr
    FROM loan_application
    WHERE loan_amount IS NOT NULL
  ) q, loan_application
)
SELECT
  total,
  ROUND((total - income_non_null) * 100.0 / total, 2) AS income_missing_pct,
  ROUND((total - loan_amount_non_null) * 100.0 / total, 2) AS loan_amount_missing_pct,
  ROUND((total - credit_score_non_null) * 100.0 / total, 2) AS credit_score_missing_pct,
  loan_amount_outliers,
  ROUND(loan_amount_outliers * 100.0 / total, 2) AS outlier_pct
FROM missing_stats, outlier_stats;