第1章:特征工程基础——特征构造、筛选与分箱

各位同学好,我是老周。在风控建模这行摸爬滚打了快十年,今天咱们聊聊特征工程。

很多人觉得建模嘛,算法调参最重要。其实不然。我见过太多项目,模型效果不好,90%的原因出在特征上。说白了,你喂给模型的是垃圾,它吐出来的只能是垃圾。

这一章,咱们把特征工程的三个核心环节讲透:特征构造、特征筛选、特征分箱。嗯,都是硬骨头,但啃下来之后,你会发现建模思路清晰很多。

特征工程核心流程 原始数据 特征构造 衍生变量 | 交叉特征 | 时间窗口特征 特征筛选 IV值 | 相关性分析 | 方差过滤 特征分箱 等距分箱 | 等频分箱 | 卡方分箱 建模数据

1.1 特征构造:从原始数据里挖金子

特征构造,说白了就是创造新的特征。原始数据往往不够用,你得想办法从里面提炼出更有价值的信息。

衍生变量是最基础的操作。比如你手里有用户的「借款金额」和「月收入」,那「负债收入比」就是一个典型的衍生变量。我在项目中遇到过,光靠原始特征建模,KS值只有0.25,加了这个衍生变量后直接跳到0.33。你想想看,这就是信息增益的力量。

常见的衍生变量类型:

  • 比率类:负债收入比、信用卡使用率、还款金额/应还金额
  • 差值类:当前年龄-开户年龄、最近一次借款距今月数
  • 聚合类:近3个月平均消费、近6个月最大逾期天数

交叉特征,就是把两个或多个特征组合起来。比如「年龄」和「职业」交叉,可以生成「年轻程序员」、「中年公务员」这样的组合特征。为什么这么做?因为单一特征可能区分度不够,组合起来往往能发现新规律。

我记得有个项目,单独看「性别」IV值只有0.02,单独看「职业」IV值0.08,但交叉后IV值飙到0.31。这就是1+1>2的效果。

# 交叉特征示例代码
import pandas as pd

# 假设有年龄和职业两个特征
df['age_group'] = pd.cut(df['age'], bins=[0,25,35,50,100], labels=['青年','中青年','中年','老年'])
df['age_job_cross'] = df['age_group'].astype(str) + '_' + df['job'].astype(str)

# 计算交叉特征的IV值
def calc_iv(df, feature, target):
    # 这里省略IV计算细节,后面会讲
    pass

时间窗口特征,这个在信贷风控里特别重要。借款人过去的行为,往往能预测未来的还款表现。比如「近3个月查询次数」、「近6个月逾期天数」、「近12个月平均使用额度」。

我的经验:时间窗口不是越大越好。窗口太长,数据稀疏;窗口太短,信息不足。我个人习惯先试3个月、6个月、12个月三个窗口,看哪个IV值最高就用哪个。

1.2 特征筛选:去粗取精的艺术

特征构造完了,你可能手里有几百个特征。但别高兴太早,这里面很多是噪音。特征筛选就是帮你把真正有用的特征挑出来。

IV值(Information Value)是风控领域最常用的筛选指标。它衡量的是特征对目标变量的区分能力。IV值越高,说明这个特征越能区分好坏客户。

IV值范围 预测能力
< 0.02 无预测能力,建议剔除
0.02 - 0.10 弱预测能力
0.10 - 0.30 中等预测能力
0.30 - 0.50 强预测能力
> 0.50 可疑,需检查是否过拟合

注意:IV值超过0.5的特征要小心。我曾经遇到一个特征IV值高达0.8,后来发现是数据泄露——它包含了未来信息。嗯,这种坑踩过一次就记住了。

相关性分析,主要是解决特征之间的多重共线性问题。两个特征高度相关,模型会不稳定,解释性也会变差。我一般用皮尔逊相关系数,阈值设在0.7。超过0.7的,保留IV值高的那个,剔除另一个。

# 相关性分析示例
import seaborn as sns
import matplotlib.pyplot as plt

# 计算相关系数矩阵
corr_matrix = df.corr()

# 找出高度相关的特征对
high_corr_pairs = []
for i in range(len(corr_matrix.columns)):
    for j in range(i+1, len(corr_matrix.columns)):
        if abs(corr_matrix.iloc[i, j]) > 0.7:
            high_corr_pairs.append((corr_matrix.columns[i], 
                                    corr_matrix.columns[j], 
                                    corr_matrix.iloc[i, j]))

方差过滤,这个最简单。如果一个特征在所有样本上的取值几乎一样,那它对模型没有任何贡献。比如「性别」在某个数据集里99%都是男性,那这个特征基本没用。我一般设置方差阈值,低于阈值的直接扔掉。

1.3 特征分箱:连续变量离散化

分箱,就是把连续变量变成几个区间。为什么要分箱?三个原因:

  • 处理异常值:极端值会被分到边界箱子里,不会影响模型
  • 增强稳定性:连续变量容易过拟合,分箱后更鲁棒
  • 提升解释性:「年龄在30-40岁之间」比「年龄35.7岁」好理解得多

等距分箱,就是把数据按值域等分成N份。比如年龄0-100岁,等距分成5份就是0-20、20-40、40-60、60-80、80-100。但这种方法有个问题——如果数据分布不均匀,有的箱子里可能没几个样本。

等频分箱,就是让每个箱子里有相同数量的样本。比如1000个样本分5箱,每箱200个。这种方法能保证每个箱子都有足够的样本,但边界值可能不好解释。

卡方分箱,这是风控里最常用的方法。它基于卡方检验,把相邻且分布相似的箱子合并。说白了,就是让好客户和坏客户在每个箱子里的比例差异最大化。

分箱后的评估指标:

  • WOE(Weight of Evidence):衡量每个箱子的好坏比相对于整体的差异
  • IV值:分箱后重新计算IV,看分箱是否合理
  • 单调性:好的分箱,WOE值应该随变量取值单调变化
# 卡方分箱示例(简化版)
import numpy as np
from scipy.stats import chi2

def chi_merge(df, feature, target, max_bins=5):
    """
    卡方分箱:合并相邻且分布相似的箱子
    """
    # 初始分箱:每个唯一值作为一个箱子
    bins = sorted(df[feature].unique())
    
    while len(bins) > max_bins:
        # 计算相邻箱子的卡方值
        chi_values = []
        for i in range(len(bins)-1):
            # 构建2x2列联表
            left = df[df[feature] == bins[i]][target].value_counts()
            right = df[df[feature] == bins[i+1]][target].value_counts()
            # 计算卡方统计量(简化)
            chi2_val = calc_chi2(left, right)
            chi_values.append(chi2_val)
        
        # 合并卡方值最小的相邻箱子
        min_idx = np.argmin(chi_values)
        bins[min_idx] = f"{bins[min_idx]}_{bins[min_idx+1]}"
        bins.pop(min_idx+1)
    
    return bins

避坑指南:我曾经在分箱时犯过一个错误——分箱后WOE值不单调。比如年龄特征,WOE值从青年到中年上升,到老年又下降。这种非单调的特征在逻辑回归里很难解释。后来我改用有监督的分箱方法,强制单调性,效果就好多了。

好了,特征工程的三个核心环节就讲到这里。特征构造是创造信息,特征筛选是去粗取精,特征分箱是化繁为简。这三步走扎实了,后面的建模工作会轻松很多。

下一章咱们聊特征工程里的一个进阶话题——时间序列特征的处理。嗯,到时候见。


公众号:蓝海资料掘金营,微信deep3321