4. 特征工程实战:WOE编码与IV值计算、特征选择方法
在催收评分卡建模中,原始特征往往包含大量缺失值、异常分布和冗余信息。本章聚焦于特征工程的核心环节:WOE编码与IV值计算(衡量特征预测能力)以及特征选择方法(卡方检验、随机森林重要性),并通过Spark实现高效筛选。
4.1 WOE编码与IV值计算
WOE(Weight of Evidence,证据权重)是一种将分类或连续变量转换为与目标变量(如“逾期/正常”)单调关联的编码方式。IV(Information Value,信息量)则用于量化特征对目标变量的区分能力。
4.1.1 WOE编码原理
对于某个特征的第i个分组(bin),WOE计算公式如下:
WOE_i = ln( (坏样本占比_i) / (好样本占比_i) )
= ln( (坏样本数_i / 总坏样本数) / (好样本数_i / 总好样本数) )
- 正向关系:WOE值越大,表示该分组中坏样本相对比例越高,逾期风险越大。
- 单调性:WOE值应随特征值单调变化,否则需调整分箱。
- 缺失值处理:缺失值单独作为一个分组,计算WOE。
4.1.2 IV值计算与特征筛选标准
IV值基于WOE计算,衡量特征整体区分能力:
IV = Σ ( (坏样本占比_i - 好样本占比_i) * WOE_i )
| IV值范围 | 预测能力 | 建议操作 |
|---|---|---|
| < 0.02 | 无预测能力 | 直接剔除 |
| 0.02 – 0.10 | 弱预测能力 | 谨慎保留或结合业务判断 |
| 0.10 – 0.30 | 中等预测能力 | 建议保留 |
| 0.30 – 0.50 | 强预测能力 | 优先保留 |
| > 0.50 | 过强(可能过拟合) | 需验证稳定性,谨慎使用 |
实战提示:在催收场景中,IV值在0.10~0.30之间的特征往往最稳定。IV过高(>0.5)的特征可能包含未来信息或过拟合风险,建议结合时间外样本验证。
4.1.3 WOE编码与IV计算流程
4.2 特征选择方法
特征选择旨在剔除冗余、噪声特征,提升模型泛化能力与训练效率。催收场景中常用以下三种方法:
4.2.1 卡方检验(Chi-Square Test)
适用于分类特征与二分类目标变量。检验特征与目标是否独立,p值越小表示特征越重要。
- 原假设:特征与目标独立。
- 统计量:χ² = Σ ( (观测值 - 期望值)² / 期望值 )
- 筛选标准:p值 < 0.05 或 0.01 时拒绝原假设,保留特征。
注意:卡方检验对样本量敏感,大样本下微小差异也可能显著。建议结合IV值或业务逻辑综合判断。
4.2.2 随机森林特征重要性
基于树模型的特征重要性评分,常用两种方式:
| 重要性类型 | 计算方式 | 特点 |
|---|---|---|
| Gini重要性 | 特征在节点分裂时减少的Gini不纯度总和 | 计算快,偏向取值多的特征 |
| Permutation重要性 | 打乱特征值后模型性能下降程度 | 更稳健,但计算成本高 |
4.2.3 三种方法对比
| 方法 | 适用特征类型 | 输出 | 优点 | 缺点 |
|---|---|---|---|---|
| IV值 | 分类/连续(需分箱) | IV值排序 | 业务可解释性强 | 分箱方式影响结果 |
| 卡方检验 | 分类特征 | p值 | 统计理论基础扎实 | 大样本下易过度显著 |
| 随机森林重要性 | 分类/连续 | 重要性得分 | 自动捕捉非线性关系 | 可能过拟合,需交叉验证 |
4.3 Spark实现特征筛选
在Spark中,利用DataFrame API和MLlib库可高效完成WOE编码、IV计算及特征选择。以下为关键实现步骤与代码示例。
4.3.1 Spark WOE编码与IV计算
import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window
// 假设df包含特征列"feature"、目标列"label"(1=坏样本,0=好样本)
val totalBad = df.filter(col("label") === 1).count().toDouble
val totalGood = df.filter(col("label") === 0).count().toDouble
// 分箱后计算每个箱的WOE和IV
val woeIV = df.groupBy("feature_bin")
.agg(
sum(when(col("label") === 1, 1).otherwise(0)).alias("bad_cnt"),
sum(when(col("label") === 0, 1).otherwise(0)).alias("good_cnt")
)
.withColumn("bad_pct", col("bad_cnt") / totalBad)
.withColumn("good_pct", col("good_cnt") / totalGood)
.withColumn("woe", log(col("bad_pct") / col("good_pct")))
.withColumn("iv_part", (col("bad_pct") - col("good_pct")) * col("woe"))
.agg(sum("iv_part").alias("iv"))
woeIV.show()
4.3.2 卡方检验Spark实现
import org.apache.spark.ml.feature.ChiSqSelector
import org.apache.spark.ml.feature.VectorAssembler
// 将特征列组装为向量
val assembler = new VectorAssembler()
.setInputCols(Array("feature1", "feature2", "feature3"))
.setOutputCol("features")
val dfVec = assembler.transform(df)
// 卡方选择器:保留前K个特征
val selector = new ChiSqSelector()
.setNumTopFeatures(10)
.setFeaturesCol("features")
.setLabelCol("label")
.setOutputCol("selectedFeatures")
val result = selector.fit(dfVec).transform(dfVec)
result.show()
4.3.3 随机森林特征重要性Spark实现
import org.apache.spark.ml.classification.RandomForestClassifier
val rf = new RandomForestClassifier()
.setLabelCol("label")
.setFeaturesCol("features")
.setNumTrees(100)
val rfModel = rf.fit(dfVec)
// 获取特征重要性
val importance = rfModel.featureImportances
println("Feature Importance: " + importance)
// 将重要性转换为DataFrame便于排序
val importanceDF = spark.createDataFrame(
importance.indices.map(i => (i, importance(i)))
).toDF("featureIndex", "importance")
.orderBy(col("importance").desc)
importanceDF.show()
4.3.4 特征筛选流程整合
4.3.5 特征筛选策略建议
- 初筛:使用IV值剔除IV < 0.02的特征。
- 复筛:结合卡方检验(p < 0.05)和随机森林重要性(保留Top 20~50)。
- 终筛:取三种方法的交集,或按业务规则加权排序。
- 验证:在时间外样本上验证特征稳定性,避免过拟合。
实战要点:在催收场景中,特征筛选不是一次性工作。建议每季度或模型迭代时重新计算IV和重要性,监控特征衰减。