4、特征工程:WOE编码、IV值计算、变量分箱

特征工程这步,我个人的经验是——它往往决定模型的天花板。算法调得再好,喂进去的变量如果是垃圾,出来也是垃圾。今天咱们就聊三个核心操作:WOE编码、IV值计算、变量分箱。这三兄弟在信用评分模型里天天见,少一个都不行。

一句话总结:分箱是把连续变量切成几段,WOE是把每段变成有业务含义的数字,IV则是告诉你这个变量到底有没有用。

4.1 变量分箱——把连续值砍成几段

为什么要分箱?说白了,信用模型里很少直接把年龄、收入这种连续值塞进去。一来模型容易过拟合,二来业务解释性差。你想想看,跟业务方说“年龄每增加一岁,风险下降0.03”,对方一脸懵。但你说“年龄在25岁以下,属于高风险段”,他就懂了。

我在项目中常用的分箱方法有这几种:

方法做法优点缺点
等距分箱按值范围平均切简单样本分布不均时容易出空箱
等频分箱按样本量平均分每箱样本均衡可能把相近值切到不同箱
最优分箱基于卡方或决策树业务解释性强计算量稍大
自定义分箱按业务经验切最符合业务逻辑需要领域知识
我曾经踩过的坑:有一次做等频分箱,把贷款金额切成五等份。结果中间那箱包含了好几个极端值,导致该箱的坏账率异常高。后来我改成先用箱线图去掉极端值,再分箱——效果立马正常了。所以分箱前,一定要看一眼分布。

4.2 WOE编码——给变量赋予业务含义

分箱之后,每箱需要一个数值去代表它。直接用好坏样本的比例差?可以。但信用评分领域更流行用WOE(Weight of Evidence,证据权重)。公式是这样的:

WOE_i = ln( (好样本占比_i) / (坏样本占比_i) )

其中好样本占比 = 该箱好样本数 / 所有好样本数,坏样本占比同理。WOE为正,说明该箱好样本比例高于整体,风险低;WOE为负,说明风险高。这个方向跟业务直觉一致:分数越高越安全。

我个人习惯在分箱后马上计算WOE,然后检查单调性。如果WOE在整个变量上不是单调的(比如先升后降),说明分箱边界可能有问题,或者变量本身是非线性的。嗯,这时候就要回头调整分箱了。

4.3 IV值——衡量变量预测力

IV(Information Value,信息量)是WOE的加权求和。公式:

IV = Σ ( (好占比_i - 坏占比_i) * WOE_i )

IV越大,变量区分好坏的能力越强。业内通常这么看:

IV范围预测力处理建议
< 0.02几乎无预测力可考虑删除
0.02 – 0.10较弱保留但谨慎使用
0.10 – 0.30中等可用
0.30 – 0.50较强优先使用
> 0.50可疑(可能过强)检查是否泄露了标签
注意:IV高于0.5不代表变量好,反而可能是数据泄漏。比如“是否逾期90天”这个变量,在建模时已经包含了目标信息——这种变量一定不能用。我见过有人把“实际利率”当特征,结果IV高达0.7,后来发现是因为高风险客户被收了高利率,属于事后信息。所以IV高不一定就是宝。

4.4 完整流程:从原始变量到最终特征

下面这张图我把流程串起来了。从左到右,一步一步看。

WOE编码 · IV计算 · 变量分箱 完整流程 原始连续变量 变量分箱 等频/等距/最优分箱 WOE编码 证据权重映射 IV值计算 变量预测力评估 最终特征 分箱 → WOE编码 → IV计算,每一步都影响最终特征质量 IV过低则删除变量,IV过高则检查数据泄漏

4.5 实战中的避坑清单

光知道公式不够,落地才是硬道理。我整理了几条常踩的坑:

  1. 分箱后WOE不单调。 原因可能是分箱边界选错了,或者变量和好坏关系本来就不是线性的。我一般先用决策树分箱,再手动微调边界。
  2. IV极高但逻辑不通。 比如“近三个月查询次数”IV值0.6,查一下是不是跟逾期有后向关联——查询发生在逾期之前才算有效特征。
  3. WOE编码后的特征维度爆炸。 每个分箱变成一列?别。WOE编码本质上是把类别变量映射成一个数值,只生成一列就够了。多元WOE那种做法只在特定场景下用。
  4. 缺失值处理。 分箱前先看缺失率。缺失较少可以用单独一箱,缺失很多就用“缺失WOE”单独处理。我习惯把缺失作为一个独立箱,单独算WOE,不要硬插。
小技巧:计算IV时,如果某个分箱的好占比或坏占比为0,会导致WOE变成无穷。这时候可以加一个平滑项:好样本加0.5,坏样本加0.5,或者所有箱统一加一个极小值。我一般用“好样本数+0.5,坏样本数+0.5”的处理方式,稳定且不影响排序。

4.6 代码实现——快速上手

下面是我在项目中用过的一个分箱+WOE+IV计算的核心片段。代码用Python,纯手写不用现成库,方便理解原理:

import pandas as pd
import numpy as np

def woe_iv_calc(df, feature, target):
    # 分箱(这里简化成等频分箱)
    df['bin'] = pd.qcut(df[feature], q=5, duplicates='drop')
    
    # 统计每箱好坏样本数
    grouped = df.groupby('bin')[target].agg(['count', 'sum'])
    grouped.columns = ['total', 'bad']
    grouped['good'] = grouped['total'] - grouped['bad']
    
    # 计算好占比、坏占比(加平滑)
    total_good = grouped['good'].sum() + 0.5
    total_bad  = grouped['bad'].sum()  + 0.5
    grouped['good_pct'] = (grouped['good'] + 0.5) / total_good
    grouped['bad_pct']  = (grouped['bad']  + 0.5) / total_bad
    
    # WOE
    grouped['woe'] = np.log(grouped['good_pct'] / grouped['bad_pct'])
    
    # IV
    grouped['iv'] = (grouped['good_pct'] - grouped['bad_pct']) * grouped['woe']
    iv_total = grouped['iv'].sum()
    return grouped[['total','bad','good','good_pct','bad_pct','woe','iv']], iv_total

实际用的时候,我建议把分箱逻辑单独抽成函数,方便调整分箱策略。代码里用了加平滑的方法,避免0除错。

记住:WOE编码后的特征可以直接喂给逻辑回归,但如果是树模型(XGBoost、LightGBM),WOE编码并不一定更好。树模型对原始值分箱或保留原值都行。我自己的经验是:逻辑回归一定要用WOE,树模型可以省掉这步,直接用分箱后的编号特征或原始值。