WOE 编码 + IV 筛选——将原始特征转换为单调风险信号的信用评分标准化工序¶
核心理念¶
WOE(证据权重)编码将任意特征(连续或离散)转换为与目标变量具有单调关系的标准化风险信号。IV(信息价值)量化每个特征对违约预测的贡献,用于特征筛选。加上 Logistic 回归 + 评分刻度转换 = 完整的信用评分卡流水线。
操作步骤¶
步骤 1:WOE 编码——将特征映射到对数几率空间¶
WOE 的核心公式: $\(\text{WOE}_i = \ln\left(\frac{\text{Good}_i / \text{Total Good}}{\text{Bad}_i / \text{Total Bad}}\right) = \ln\left(\frac{\text{该组好客户占比}}{\text{该组坏客户占比}}\right)\)$
WOE 的三种状态: - WOE > 0 → 该组「好客户」比例高于平均(低风险组) - WOE < 0 → 该组「坏客户」比例高于平均(高风险组) - WOE = 0 → 该组风险与平均水平相同
import pandas as pd
import numpy as np
def calculate_woe(df, feature, target, n_bins=10):
"""计算单个特征的 WOE 编码"""
df_temp = df[[feature, target]].copy()
# 等频分箱
df_temp['bin'] = pd.qcut(df_temp[feature], q=n_bins, duplicates='drop')
# 分箱统计
bin_stats = df_temp.groupby('bin').agg(
good=(target, lambda x: (x == 0).sum()),
bad=(target, lambda x: (x == 1).sum()),
total=(target, 'count')
).reset_index()
total_good = bin_stats['good'].sum()
total_bad = bin_stats['bad'].sum()
# WOE 计算
bin_stats['pct_good'] = bin_stats['good'] / total_good
bin_stats['pct_bad'] = bin_stats['bad'] / total_bad
bin_stats['woe'] = np.log(
(bin_stats['pct_good'] + 1e-10) / (bin_stats['pct_bad'] + 1e-10)
)
# 事件率
bin_stats['event_rate'] = bin_stats['bad'] / bin_stats['total']
return bin_stats
WOE 的核心价值: 1. 处理缺失值:缺失值自动成为独立分箱(有专属 WOE 值) 2. 处理异常值:极端值被分到边缘箱中,不影响模型 3. 处理非线性:单调性由 WOE 值保证,不需要对原始特征做非线性变换 4. 标准化不同尺度:所有特征映射到同一对数几率空间
步骤 2:IV 筛选——量化特征预测能力¶
IV 评估标准(行业标准):
| IV 范围 | 预测能力 | 使用建议 |
|---|---|---|
| < 0.02 | 无 | 直接丢弃 |
| 0.02 - 0.1 | 弱 | 作为辅助特征 |
| 0.1 - 0.3 | 中等 | 核心特征 |
| 0.3 - 0.5 | 强 | 强特征,需警惕过拟合 |
| > 0.5 | 过强 | 检查是否特征泄露 |
def calculate_iv(bin_stats):
"""计算单个特征的 IV 值"""
total_good = bin_stats['good'].sum()
total_bad = bin_stats['bad'].sum()
bin_stats = bin_stats.copy()
bin_stats['pct_good'] = bin_stats['good'] / total_good
bin_stats['pct_bad'] = bin_stats['bad'] / total_bad
bin_stats['woe'] = np.log(
(bin_stats['pct_good'] + 1e-10) / (bin_stats['pct_bad'] + 1e-10)
)
bin_stats['iv_component'] = (
(bin_stats['pct_good'] - bin_stats['pct_bad']) * bin_stats['woe']
)
iv = bin_stats['iv_component'].sum()
return iv
# 批量计算所有特征的 IV
def batch_iv_analysis(df, features, target, n_bins=10):
"""批量计算所有特征的 IV 并排序"""
results = []
for feat in features:
bin_stats = calculate_woe(df, feat, target, n_bins)
iv = calculate_iv(bin_stats)
results.append({'feature': feat, 'iv': iv})
iv_df = pd.DataFrame(results).sort_values('iv', ascending=False)
def get_power(iv):
if iv < 0.02: return '无'
elif iv < 0.1: return '弱'
elif iv < 0.3: return '中等'
elif iv < 0.5: return '强'
else: return '过强'
iv_df['predictive_power'] = iv_df['iv'].apply(get_power)
return iv_df
步骤 3:WOE 编码应用——将原始特征转换为 WOE 值¶
def apply_woe_encoding(df, feature, woe_stats):
"""
将数据框中的原始特征替换为 WOE 编码值
"""
df_encoded = df.copy()
# 为每行找到对应分箱的 WOE 值
bin_map = woe_stats.set_index('bin')['woe'].to_dict()
# 等频分箱(使用训练集的分界点)
df_encoded[f'{feature}_bin'] = pd.qcut(
df_encoded[feature],
q=len(woe_stats),
duplicates='drop'
)
df_encoded[f'{feature}_woe'] = df_encoded[f'{feature}_bin'].map(bin_map)
# 未知箱 → WOE = 0(即该组与平均水平相同)
df_encoded[f'{feature}_woe'] = df_encoded[f'{feature}_woe'].fillna(0)
return df_encoded[f'{feature}_woe']
步骤 4:评分刻度转换——从概率到信用评分¶
业界标准评分转换:
其中 \(\text{Odds} = p / (1-p)\) 是违约/正常的几率。
PDO(Points to Double the Odds):几率翻倍时分数减少的分数。业界标准 PDO = 50。
# 评分刻度参数
base_score = 600 # 基础分(该分数对应的 odds)
pdo = 50 # 几率翻倍时分数变化
base_odds = 1/60 # 基准几率(600 分时好:坏 = 60:1)
# 计算 Slope 和 Offset
slope = pdo / np.log(2)
offset = base_score + slope * np.log(base_odds)
def probability_to_score(prob, slope=slope, offset=offset):
"""将违约概率转换为信用评分"""
odds = prob / (1 - prob + 1e-10)
score = offset - slope * np.log(odds + 1e-10)
return np.clip(score, 300, 900) # 截断到合理范围
评分刻度的含义: - 分数越高 = 违约风险越低 = 好客户 - PDO = 50 意味着:如果 A 的分数比 B 高 50 分,A 的违约几率是 B 的一半
步骤 5:决策阈值设定¶
# 三区决策规则
approve_threshold = 650 # 高于此分直接批准
reject_threshold = 550 # 低于此分直接拒绝
# 550-650 之间 → 人工复核
def make_decision(score, approve=650, reject=550):
if score >= approve:
return '批准'
elif score < reject:
return '拒绝'
else:
return '人工复核'
# 阈值设定的业务逻辑:
# 提高批准阈值 → 降低坏账率但减少业务量
# 降低批准阈值 → 增加业务量但提高坏账率
# 最优阈值 = 边际收益 = 边际成本 的点
💡 关键洞察¶
WOE 为什么替代 One-Hot 编码¶
在信用评分中,WOE 编码在所有方面优于 One-Hot: - 维度:每个特征 → 1 列 WOE 值(One-Hot 需要 n_bins 列) - 可解释性:WOE 值直接反映风险方向(正 = 低风险,负 = 高风险) - 单调性:Logistic 回归在 WOE 编码上学习单调模式 - 处理缺失:缺失自动为一个分箱
IV > 0.5 的警告¶
当 IV > 0.5 时,不是说明特征「特别好」——而是说明可能存在特征泄露(如用「是否已逾期」预测「是否违约」——这是事后变量)。过强的 IV 是数据质量问题的信号。
评分卡的部署监控——PSI¶
评分卡部署后需要持续监控总体稳定性指数(PSI):
PSI > 0.25 = 评分分布发生显著变化 → 模型需要重建。
限制与边界¶
- 单调假设:WOE 要求特征与目标的关系是单调的——如果真实的 U 型关系存在(如中等收入群体违约率最高),WOE 会丢失这部分信息
- Logistic 回归的前提:评分卡基于 Logit 模型,假设特征和对数几率之间是线性关系——这在 WOE 编码下通常成立
- 等频分箱的敏感性:分箱数量影响 IV 值——n_bins 少 → IV 被低估,n_bins 多 → IV 被高估
- 不适用于非线性交互:如果需要捕捉特征间的复杂交互,需要使用 GBDT 或神经网络——但那是黑箱模型,不符合信用评分的监管可解释性要求
与概念笔记的关联¶
- → 概念笔记「度量选择是元决策选什么指标决定组织优化方向选错比没有更危险」——选择 IV 作为特征筛选指标本身就是度量选择:你在用「预测能力」而非「业务意义」做筛选。IV 是信息论度量(Kullback-Leibler 散度的变形),选择它而不是业务相关性度量(如「客户收入对违约的直觉理解」)决定了评分卡最终的特征组合
- → 概念笔记「概率分布选择不是数学偏好」——等频分箱将连续特征离散化,本质上是选择了「分段常数」作为分布的近似形式。更关键的是,WOE 编码要求特征与目标之间是单调的——这本身就是对数据生成过程的强假设
- → 概念笔记「机器学习失败金字塔算法选择是塔尖」——PSI 监控和 IV > 0.5 的泄漏警告是 ML 失败金字塔中「评估指标」和「数据质量」在信用评分场景的具体化。Pipeline 中的每一步(数据清洗→WOE分箱→IV筛选→Logistic回归→评分刻度)都有特定的失败模式
- → 概念笔记「因果推断方法阵营分歧不在数学每种方法估计不同的因果量」——评分卡估计的是相关(区分好/坏客户),不是因果(降低利率对违约率的因果效应)。评分卡用于审批决策的前提假设是「被拒绝的申请人如果被批准,其行为与批准的相似申请人一样」——这个假设需要离线因果验证而不能被评分卡本身的 KS 值保证