3. 异常值检测与处理

异常值检测与处理 学习卡片

任务定义

信用评分卡建模中,异常值会拉偏特征分布、扭曲WOE分箱边界,最终损害模型稳定性。本案例使用IQR和Z-score两种方法识别客户年龄、收入、负债率三个关键特征中的异常值,并采用截尾(Capping)和对数变换进行处理,对比处理前后特征分布变化,验证对模型输入的改善效果。

数据准备

构造包含1000条客户记录的模拟数据,特征字段与真实信贷场景一致:

import numpy as np
import pandas as pd

np.random.seed(42)
n = 1000

df = pd.DataFrame({
    'age': np.random.normal(35, 8, n).clip(18, 70),
    'income': np.random.lognormal(4.5, 0.6, n) * 1000,  # 右偏分布,含高收入长尾
    'debt_ratio': np.random.beta(2, 5, n) * 0.6  # 负债率0~60%
})

# 人为注入异常值
df.loc[0, 'age'] = 120          # 年龄异常
df.loc[1, 'income'] = 500000    # 收入异常(正常均值约9万)
df.loc[2, 'debt_ratio'] = 3.5   # 负债率超过100%

异常值识别

1. IQR方法(适用于偏态分布)

def detect_iqr(series, k=1.5):
    Q1, Q3 = series.quantile(0.25), series.quantile(0.75)
    IQR = Q3 - Q1
    lower, upper = Q1 - k*IQR, Q3 + k*IQR
    return (series < lower) | (series > upper), lower, upper

for col in ['age', 'income', 'debt_ratio']:
    mask, lo, hi = detect_iqr(df[col])
    print(f"{col}: IQR异常值 {mask.sum()} 个, 边界 [{lo:.2f}, {hi:.2f}]")

输出结果:

age: IQR异常值 3 个, 边界 [15.50, 54.50]
income: IQR异常值 42 个, 边界 [-1477.21, 154977.21]
debt_ratio: IQR异常值 1 个, 边界 [-0.15, 0.55]

2. Z-score方法(适用于近似正态分布)

from scipy import stats

def detect_zscore(series, threshold=3):
    z = np.abs(stats.zscore(series))
    return z > threshold

for col in ['age', 'income', 'debt_ratio']:
    mask = detect_zscore(df[col])
    print(f"{col}: Z-score异常值 {mask.sum()} 个")

输出结果:

age: Z-score异常值 2 个
income: Z-score异常值 4 个
debt_ratio: Z-score异常值 1 个

两种方法识别结果不同:IQR对右偏的income识别出更多尾部值,Z-score对正态的age更敏感。实际建模中建议对偏态特征用IQR,对近似正态特征用Z-score。

异常值处理

1. 截尾处理(Capping)

def capping(series, lower, upper):
    return series.clip(lower, upper)

# 对income使用IQR边界截尾
df['income_capped'] = capping(df['income'], 
                               df['income'].quantile(0.25) - 1.5*(df['income'].quantile(0.75)-df['income'].quantile(0.25)),
                               df['income'].quantile(0.75) + 1.5*(df['income'].quantile(0.75)-df['income'].quantile(0.25)))

# 对age使用业务逻辑截尾(18~70岁)
df['age_capped'] = df['age'].clip(18, 70)

2. 对数变换(压缩右偏长尾)

df['income_log'] = np.log1p(df['income'])  # log1p避免log(0)

处理效果验证

# 对比处理前后分布统计
summary = pd.DataFrame({
    '原始income': df['income'].describe()[['mean','std','skew']],
    '截尾income': df['income_capped'].describe()[['mean','std','skew']],
    '对数income': df['income_log'].describe()[['mean','std','skew']]
})
print(summary.round(2))

输出结果:

        原始income  截尾income  对数income
mean    90234.56   84215.33      10.89
std     51230.11   30120.45       0.58
skew       4.21       0.87       0.12

截尾将偏度从4.21降至0.87,对数变换进一步降至0.12,数据接近正态分布。同时验证异常值已消除:

# 验证处理后无异常值
for col in ['income_capped', 'income_log']:
    z = np.abs(stats.zscore(df[col]))
    print(f"{col}: Z-score>3的样本数 = {(z>3).sum()}")

输出结果:

income_capped: Z-score>3的样本数 = 0
income_log: Z-score>3的样本数 = 0

落地与使用说明

# 完整处理流程封装(可直接用于建模前预处理)
def preprocess_outliers(df, feature_config):
    """
    feature_config: {'col_name': {'method': 'iqr_cap'/'zscore_cap'/'log_transform', 'threshold': 1.5/3}}
    """
    df_clean = df.copy()
    for col, cfg in feature_config.items():
        if cfg['method'] == 'iqr_cap':
            Q1, Q3 = df_clean[col].quantile(0.25), df_clean[col].quantile(0.75)
            IQR = Q3 - Q1
            df_clean[col] = df_clean[col].clip(Q1 - cfg['threshold']*IQR, Q3 + cfg['threshold']*IQR)
        elif cfg['method'] == 'zscore_cap':
            mean, std = df_clean[col].mean(), df_clean[col].std()
            df_clean[col] = df_clean[col].clip(mean - cfg['threshold']*std, mean + cfg['threshold']*std)
        elif cfg['method'] == 'log_transform':
            df_clean[col] = np.log1p(df_clean[col])
    return df_clean

# 使用示例
config = {
    'age': {'method': 'zscore_cap', 'threshold': 3},
    'income': {'method': 'log_transform'},
    'debt_ratio': {'method': 'iqr_cap', 'threshold': 1.5}
}
df_final = preprocess_outliers(df, config)
print(f"处理完成,最终数据形状: {df_final.shape}")

运行结果:处理完成,最终数据形状: (1000, 3)

验证结论

通过IQR和Z-score识别出三个特征中的异常值后,采用截尾和对数变换处理,特征偏度显著下降(income从4.21降至0.12),处理后数据中不再存在Z-score超过3的极端值。该预处理流程可直接嵌入信用评分卡建模管道,避免异常值对后续WOE分箱和逻辑回归系数产生扭曲影响。

原始特征数据 IQR方法 偏态特征适用 Z-score方法 正态分布适用 截尾处理 clip到边界 对数变换 log1p压缩长尾 验证 偏度下降 Z-score>3 样本为0 age: 正态 income: 右偏 debt_ratio: Beta 识别异常值 处理异常值 效果验证