3. 数据探索与预处理:加载信贷数据集、缺失值处理、异常值检测与处理、数据标准化
数据预处理是风控模型开发中最耗时但最关键的一环。原始信贷数据通常包含大量缺失值、异常分布和量纲差异,直接建模会导致模型偏差或过拟合。本章将系统讲解从数据加载到标准化处理的完整流程,确保后续可解释评分接口的输入数据质量可靠。
3.1 加载信贷数据集
我们使用公开的信贷违约数据集(如 Home Credit Default Risk 或 LendingClub 子集),包含申请人的基本信息、信用历史、负债情况等字段。加载时需注意数据类型推断和内存优化。
关键字段说明: 数据集包含数值型(收入、年龄、贷款金额)、类别型(职业、教育水平、居住状态)和时间型(申请日期、最近逾期日期)三类特征。
import pandas as pd
import numpy as np
# 加载信贷数据集
df = pd.read_csv('credit_data.csv', parse_dates=['application_date'])
# 查看数据概览
print(f"数据集形状: {df.shape}")
print(f"特征列: {list(df.columns)}")
print(f"目标变量分布:\n{df['is_default'].value_counts(normalize=True)}")
| 字段名 | 类型 | 含义 | 缺失率 |
|---|---|---|---|
| AMT_INCOME | float | 申请人年收入 | 2.3% |
| DAYS_BIRTH | int | 年龄(负值天数) | 0.0% |
| EXT_SOURCE_1 | float | 外部信用评分1 | 35.7% |
| OCCUPATION_TYPE | object | 职业类型 | 12.1% |
| is_default | int | 是否违约(目标变量) | 0.0% |
3.2 缺失值处理
信贷数据中缺失值普遍存在,处理策略需结合业务含义和缺失率。高缺失率特征(>50%)通常直接删除;中等缺失率特征(10%-50%)根据业务逻辑填充;低缺失率特征(<10%)可用统计量填充。
业务经验: 外部信用评分(EXT_SOURCE)缺失往往意味着申请人信用记录不足,这类缺失本身具有信息量,建议用 -999 或中位数填充并保留缺失指示变量。
# 计算缺失率
missing_rate = df.isnull().mean().sort_values(ascending=False)
# 策略1:删除高缺失率特征(>50%)
high_missing_cols = missing_rate[missing_rate > 0.5].index
df.drop(columns=high_missing_cols, inplace=True)
# 策略2:数值型特征用中位数填充
num_cols = df.select_dtypes(include=['float64', 'int64']).columns
for col in num_cols:
if df[col].isnull().sum() > 0:
df[col].fillna(df[col].median(), inplace=True)
# 策略3:类别型特征用众数填充,并添加缺失指示列
cat_cols = df.select_dtypes(include=['object']).columns
for col in cat_cols:
if df[col].isnull().sum() > 0:
df[f'{col}_missing'] = df[col].isnull().astype(int)
df[col].fillna(df[col].mode()[0], inplace=True)
3.3 异常值检测与处理
信贷数据中的异常值可能来自数据录入错误、欺诈行为或极端信用状况。常用检测方法包括 IQR 规则、Z-Score 和业务规则过滤。
注意: 风控场景中,部分“异常值”实际上是高风险客户的真实特征(如极高负债率),直接删除可能导致模型对极端风险样本的识别能力下降。建议先标记再评估。
def detect_outliers_iqr(df, column, multiplier=1.5):
"""使用IQR规则检测异常值"""
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - multiplier * IQR
upper_bound = Q3 + multiplier * IQR
outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
return outliers, lower_bound, upper_bound
# 对收入字段检测异常值
outliers_income, low, high = detect_outliers_iqr(df, 'AMT_INCOME')
print(f"收入异常值数量: {len(outliers_income)} ({len(outliers_income)/len(df)*100:.2f}%)")
# 处理策略:对极端值进行截断(Winsorize)
df['AMT_INCOME'] = df['AMT_INCOME'].clip(lower=low, upper=high)
| 检测方法 | 适用场景 | 阈值 | 处理方式 |
|---|---|---|---|
| IQR 规则 | 偏态分布特征(收入、负债) | 1.5×IQR / 3×IQR | 截断或删除 |
| Z-Score | 近似正态分布特征(年龄、信用分) | |Z| > 3 | 删除或替换 |
| 业务规则 | 有明确业务约束的特征 | 年龄 < 18 或 > 100 | 直接修正或删除 |
| 孤立森林 | 高维特征异常检测 | 异常分数 > 0.7 | 标记后人工审核 |
3.4 数据标准化
风控模型(尤其是逻辑回归、SVM、KNN)对特征量纲敏感。标准化可消除量纲影响,使不同特征具有可比性,同时加速模型收敛。常用方法包括 Z-Score 标准化和 Min-Max 归一化。
选择建议: 对于存在异常值的特征,优先使用 RobustScaler(基于中位数和 IQR);对于稀疏特征,使用 MaxAbsScaler;对于深度学习模型,推荐 BatchNormalization。
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# 选择数值型特征进行标准化
num_features = ['AMT_INCOME', 'DAYS_BIRTH', 'EXT_SOURCE_1', 'CNT_FAM_MEMBERS']
# 方法1:Z-Score 标准化(均值为0,标准差为1)
scaler_standard = StandardScaler()
df[num_features] = scaler_standard.fit_transform(df[num_features])
# 方法2:Min-Max 归一化(缩放到[0,1]区间)
scaler_minmax = MinMaxScaler()
df[num_features] = scaler_minmax.fit_transform(df[num_features])
# 方法3:RobustScaler(对异常值鲁棒)
scaler_robust = RobustScaler()
df[num_features] = scaler_robust.fit_transform(df[num_features])
3.5 预处理流程总结
完整的数据预处理流程应形成可复用的 Pipeline,确保训练集和测试集使用相同的变换参数。以下是推荐的执行顺序:
- 数据加载与类型转换 — 解析日期、类别编码、内存优化
- 缺失值处理 — 删除高缺失率特征,填充中位数/众数,添加缺失指示变量
- 异常值检测与处理 — IQR/Z-Score 检测,截断或标记
- 特征编码 — 类别特征 One-Hot 或 Label Encoding
- 数据标准化 — 根据特征分布选择 StandardScaler 或 RobustScaler
- 训练/测试集划分 — 保持时间顺序或随机分层抽样
工程实践: 使用 sklearn 的 Pipeline 和 ColumnTransformer 将上述步骤封装为可持久化的预处理对象,便于在评分接口中复用相同的变换逻辑,避免训练-服务不一致问题。
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
# 定义数值型特征处理管道
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
# 定义类别型特征处理管道
cat_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
# 组合预处理
preprocessor = ColumnTransformer([
('num', num_pipeline, num_features),
('cat', cat_pipeline, cat_features)
])
# 拟合并转换
X_processed = preprocessor.fit_transform(X_train)