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;
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 方法不依赖正态分布假设,通过四分位数识别异常值,具有更好的稳健性。
计算步骤:
- 计算第一四分位数 Q1(25% 分位数)和第三四分位数 Q3(75% 分位数)
- 计算四分位距 IQR = Q3 - Q1
- 定义异常值边界:下限 = Q1 - 1.5 × IQR,上限 = Q3 + 1.5 × IQR
- 超出上下限的数据点视为异常值
-- 使用 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) |
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;