4. 数据准备与特征工程:客户基础数据清洗、缺失值处理、异常值截断、WOE分箱与IV值计算、特征筛选
在授信决策引擎的构建中,数据质量直接决定模型的上限。本章聚焦于从原始客户数据到可用于评分卡建模的标准化特征集的全流程操作,涵盖数据清洗、缺失值处理、异常值截断、WOE分箱与IV值计算,以及最终的特征筛选。以下内容将结合Python策略编排实践,逐一拆解每个环节的核心技术与避坑要点。
核心原则:特征工程的目标不是“处理掉所有异常”,而是“让数据分布更符合业务逻辑与模型假设”。每一步操作都应保留可追溯的转换日志,便于后续策略复盘与模型迭代。
4.1 客户基础数据清洗
数据清洗是特征工程的基石,主要解决数据格式不一致、重复记录、逻辑错误等问题。常见清洗操作包括:
- 字段类型统一:将日期字段转为datetime类型,金额字段转为float,身份证号、手机号转为字符串。
- 重复记录去重:基于客户唯一标识(如身份证号、客户ID)保留最新或最完整的记录。
- 逻辑校验:年龄 < 0 或 > 120、贷款金额为负、申请日期晚于审批日期等明显错误。
- 格式标准化:手机号去除空格与横线,身份证号统一为大写字母。
import pandas as pd
import numpy as np
# 示例:基础数据清洗
df = pd.read_csv('customer_raw.csv')
# 类型转换
df['apply_date'] = pd.to_datetime(df['apply_date'])
df['loan_amount'] = df['loan_amount'].astype(float)
# 去重:保留最新记录
df = df.sort_values('apply_date').drop_duplicates(subset='customer_id', keep='last')
# 逻辑校验:剔除年龄异常
df = df[(df['age'] >= 18) & (df['age'] <= 100)]
# 格式标准化
df['mobile'] = df['mobile'].str.replace(r'\D', '', regex=True)
df['id_card'] = df['id_card'].str.upper()
4.2 缺失值处理
缺失值处理需结合缺失率与业务含义。不同缺失率对应不同策略:
| 缺失率范围 | 推荐策略 | 适用场景 |
|---|---|---|
| < 5% | 直接删除(行删除)或均值/中位数填充 | 随机缺失,样本量充足 |
| 5% ~ 30% | 中位数/众数填充、模型预测填充、KNN填充 | 非随机缺失,需保留样本量 |
| 30% ~ 60% | 单独设为一个类别(如-999或“未知”) | 缺失本身可能包含信息(如客户未填写) |
| > 60% | 直接删除该特征 | 信息量过低,易引入噪声 |
# 缺失值处理示例
# 数值型:中位数填充
df['income'].fillna(df['income'].median(), inplace=True)
# 分类型:众数填充
df['education'].fillna(df['education'].mode()[0], inplace=True)
# 高缺失率特征:单独设类
df['house_loan_status'].fillna('UNKNOWN', inplace=True)
# 缺失率 > 60% 的特征直接删除
threshold = 0.6
df = df.loc[:, df.isnull().mean() < threshold]
注意:缺失值填充后,务必检查填充后的分布是否与原始分布发生显著偏移。对于评分卡模型,建议保留缺失指示变量(如
income_missing),以捕捉“是否缺失”这一信息。
4.3 异常值截断
异常值会拉偏WOE分箱的边界,导致评分卡不稳定。常用截断方法为分位数截断与标准差截断。在风控场景中,更推荐基于业务经验的分位数截断。
| 方法 | 公式 | 适用场景 |
|---|---|---|
| 分位数截断 | 下限 = P1,上限 = P99 | 收入、年龄等长尾分布 |
| 标准差截断 | 下限 = μ - 3σ,上限 = μ + 3σ | 近似正态分布(如信用评分) |
| IQR截断 | 下限 = Q1 - 1.5×IQR,上限 = Q3 + 1.5×IQR | 箱线图异常检测 |
# 分位数截断示例
def cap_outliers(series, lower_percentile=0.01, upper_percentile=0.99):
lower = series.quantile(lower_percentile)
upper = series.quantile(upper_percentile)
return series.clip(lower, upper)
df['income_capped'] = cap_outliers(df['income'])
df['age_capped'] = cap_outliers(df['age'], 0.01, 0.98) # 年龄上限可适当放宽
4.4 WOE分箱与IV值计算
WOE(Weight of Evidence)分箱是评分卡建模的核心步骤,用于将连续变量或离散变量转化为单调的WOE编码,同时计算IV(Information Value)衡量特征预测能力。
4.4.1 分箱原则
- 单调性:WOE值随分箱顺序单调递增或递减,符合业务逻辑。
- 箱内样本充足:每箱至少包含5%的样本,避免过拟合。
- 坏账率区分度:相邻箱的坏账率应有显著差异。
4.4.2 WOE与IV计算公式
WOE_i = ln( (坏样本占比_i) / (好样本占比_i) )
IV = Σ (坏样本占比_i - 好样本占比_i) × WOE_i
| IV值范围 | 预测能力 | 建议 |
|---|---|---|
| < 0.02 | 无预测能力 | 删除 |
| 0.02 ~ 0.10 | 弱 | 可保留,结合业务判断 |
| 0.10 ~ 0.30 | 中等 | 推荐保留 |
| 0.30 ~ 0.50 | 强 | 核心特征 |
| > 0.50 | 可疑(可能过拟合) | 需验证稳定性 |
import scipy.stats as stats
def woe_iv_bin(df, feature, target, bins=10):
# 等频分箱
df['bin'] = pd.qcut(df[feature], q=bins, duplicates='drop')
grouped = df.groupby('bin')[target].agg(['count', 'sum'])
grouped.columns = ['total', 'bad']
grouped['good'] = grouped['total'] - grouped['bad']
total_bad = grouped['bad'].sum()
total_good = grouped['good'].sum()
grouped['bad_pct'] = grouped['bad'] / total_bad
grouped['good_pct'] = grouped['good'] / total_good
# 避免除零
grouped['bad_pct'] = grouped['bad_pct'].replace(0, 0.0001)
grouped['good_pct'] = grouped['good_pct'].replace(0, 0.0001)
grouped['woe'] = np.log(grouped['bad_pct'] / grouped['good_pct'])
grouped['iv'] = (grouped['bad_pct'] - grouped['good_pct']) * grouped['woe']
iv_total = grouped['iv'].sum()
return grouped, iv_total
# 示例:计算年龄特征的IV值
bin_result, iv_value = woe_iv_bin(df, 'age_capped', 'is_default', bins=8)
print(f'年龄特征 IV = {iv_value:.4f}')
实践要点:
- 分箱后务必检查WOE单调性,若不单调可尝试合并相邻箱或调整分箱数。
- IV值过高(>0.5)的特征需警惕过拟合,建议在时间外样本上验证稳定性。
- WOE编码后的特征可直接输入逻辑回归模型,系数解释性更强。
4.5 特征筛选
特征筛选的目标是在保证模型性能的前提下,剔除冗余与噪声特征,提升模型泛化能力。常用筛选方法包括:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| IV值筛选 | 保留IV > 0.02的特征 | 快速初筛 |
| 相关性分析 | 剔除相关系数 > 0.7的特征对 | 消除多重共线性 |
| 方差膨胀因子(VIF) | VIF < 10 保留 | 线性模型专用 |
| 随机森林特征重要性 | 保留重要性排名前N的特征 | 非线性关系检测 |
| 前向/后向逐步选择 | 基于AIC或p值逐步添加/删除 | 小规模特征集 |
# 相关性筛选示例
corr_matrix = df[selected_features].corr().abs()
upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
# 找到需要删除的特征对
to_drop = [column for column in upper.columns if any(upper[column] > 0.7)]
df_reduced = df.drop(columns=to_drop)
print(f'因高相关性删除的特征: {to_drop}')
4.6 本章小结
数据准备与特征工程是授信决策引擎中最耗时但回报最高的环节。通过系统化的数据清洗、缺失值处理、异常值截断、WOE分箱与IV计算,以及多维度特征筛选,可以构建出稳定、可解释且预测能力强的特征集。在Python策略编排中,建议将上述流程封装为可复用的函数或类,配合配置化参数,实现特征工程的自动化与标准化。
下一阶段准备:完成特征筛选后,即可进入评分卡建模环节。建议将WOE分箱结果与IV值保存为元数据文件,便于后续模型监控与特征回溯。