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计算流程

WOE编码与IV计算流程 原始特征数据 分箱处理 计算WOE值 计算IV值 分箱方法 等距分箱 等频分箱 决策树分箱 WOE公式 ln(坏占比/好占比) 单调性检查 IV公式 Σ(差*WOE) 筛选阈值 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 特征筛选流程整合

Spark特征筛选整合流程 原始特征DataFrame WOE编码 + IV计算 卡方检验 随机森林重要性 IV排序列表 p值排序列表 重要性排序列表 最终特征子集(交集/并集)

4.3.5 特征筛选策略建议

  • 初筛:使用IV值剔除IV < 0.02的特征。
  • 复筛:结合卡方检验(p < 0.05)和随机森林重要性(保留Top 20~50)。
  • 终筛:取三种方法的交集,或按业务规则加权排序。
  • 验证:在时间外样本上验证特征稳定性,避免过拟合。
实战要点:在催收场景中,特征筛选不是一次性工作。建议每季度或模型迭代时重新计算IV和重要性,监控特征衰减。