第三章 变量筛选方法论:筛选原则与常用方法

变量筛选这件事,说白了就是给模型挑「好料」。

我刚开始做风控建模时,总觉得变量越多越好。结果呢?模型过拟合得一塌糊涂,上线后效果断崖式下跌。后来我明白了——变量筛选不是做加法,而是做减法。你想想看,一百个变量里,真正有用的可能不到二十个。

3.1 筛选三大原则:预测力、稳定性、可解释性

我个人习惯把变量筛选比作选人。三个标准:能力强(预测力)、性格稳(稳定性)、好沟通(可解释性)。缺一个,后面都麻烦。

3.1.1 预测力

预测力就是变量区分好坏客户的能力。一个变量如果好坏客户分布差不多,那它基本没用。

衡量指标我常用两个:

  • IV(Information Value):最经典的指标,IV越高,区分能力越强
  • 卡方检验:看变量与目标变量的相关性是否显著

IV值经验阈值:

IV范围预测力处理建议
< 0.02无预测力直接剔除
0.02 - 0.1谨慎使用
0.1 - 0.3中等可用
0.3 - 0.5优先保留
> 0.5极强注意过拟合风险

我的经验:IV超过0.5的变量要小心。我在项目中遇到过某个收入类变量IV高达0.8,结果上线后因为数据源变更,效果直接崩了。太强的变量往往不稳定。

3.1.2 稳定性

稳定性看的是变量在不同时间段的分布是否一致。一个变量今天能用,明天不能用,那再强的预测力也没用。

衡量指标:

  • PSI(Population Stability Index):衡量变量分布偏移程度
  • 特征分布对比:训练集 vs 验证集 vs 近期样本

避坑指南:我曾经遇到一个变量,训练集和测试集PSI只有0.01,看起来稳得很。结果上线三个月后PSI飙到0.3。为什么?因为那个变量依赖第三方数据源,对方改了接口逻辑。所以稳定性不仅要看统计指标,还要看业务逻辑。

3.1.3 可解释性

可解释性就是变量能不能讲清楚道理。风控模型不像推荐系统,黑箱模型很难过监管。

我一般问三个问题:

  1. 这个变量业务上合理吗?
  2. 它的分箱逻辑能解释给业务听吗?
  3. 如果模型拒绝了一个客户,能用这个变量说明原因吗?

举个例子,用「客户星座」做变量,预测力可能有一点,但你怎么解释?「因为你是天蝎座所以被拒贷」?这肯定不行。

3.2 常用筛选方法概览

筛选方法我一般分四类。嗯,这里我画了一张图,帮你理清思路。

变量筛选方法体系 变量筛选 IV 信息量筛选 PSI 稳定性筛选 相关性分析 随机森林特征重要性 核心逻辑 衡量变量区分好坏客户能力 IV > 0.1 为可用 IV > 0.5 需谨慎 核心逻辑 衡量变量分布随时间偏移程度 PSI < 0.1 稳定 PSI > 0.25 需排查 核心逻辑 剔除高度相关的冗余变量 相关系数 > 0.7 需处理 VIF > 10 存在共线性 核心逻辑 基于树模型的特征重要性排序 可处理非线性关系 需注意过拟合风险 建议组合使用:IV + PSI 初筛 → 相关性去冗余 → 随机森林排序 单一方法有局限,多维度交叉验证更可靠

3.2.1 IV筛选——最基础也最常用

IV筛选是我每次建模必做的第一步。计算逻辑不复杂,就是对每个变量分箱后,计算好坏样本的分布差异。

# Python示例:计算单个变量的IV值
import pandas as pd
import numpy as np

def calc_iv(data, feature, target):
    """
    计算变量的IV值
    data: DataFrame
    feature: 变量名
    target: 目标变量名(0好客户,1坏客户)
    """
    # 分箱统计
    grouped = data.groupby(feature)[target].agg(['count', 'sum'])
    grouped.columns = ['total', 'bad']
    grouped['good'] = grouped['total'] - grouped['bad']
    
    # 计算分布
    total_bad = grouped['bad'].sum()
    total_good = grouped['good'].sum()
    
    grouped['bad_pct'] = grouped['bad'] / total_bad
    grouped['good_pct'] = grouped['good'] / total_good
    
    # 计算WOE和IV
    grouped['woe'] = np.log(grouped['bad_pct'] / grouped['good_pct'])
    grouped['iv'] = (grouped['bad_pct'] - grouped['good_pct']) * grouped['woe']
    
    return grouped['iv'].sum()

# 使用示例
iv_value = calc_iv(df, 'age_bin', 'is_default')
print(f'变量age_bin的IV值为: {iv_value:.4f}')

我的习惯:IV筛选时,我会先做一次粗筛(IV > 0.02),再做一次精筛(IV > 0.1)。粗筛是为了保留潜在有用的变量,精筛是为了控制变量数量。别一上来就卡0.1,有些弱变量组合起来效果不错。

3.2.2 PSI稳定性筛选——上线前的必检项

PSI这个指标,说白了就是看变量分布有没有「变脸」。我见过太多模型死在稳定性上。

# PSI计算示例
def calc_psi(actual, expected, bins=10):
    """
    计算PSI值
    actual: 实际分布(如线上数据)
    expected: 期望分布(如训练集)
    """
    # 分箱
    actual_pct = np.histogram(actual, bins=bins)[0] / len(actual)
    expected_pct = np.histogram(expected, bins=bins)[0] / len(expected)
    
    # 避免除零
    actual_pct = np.where(actual_pct == 0, 0.0001, actual_pct)
    expected_pct = np.where(expected_pct == 0, 0.0001, expected_pct)
    
    # 计算PSI
    psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
    return psi

# 判断标准
psi = calc_psi(df_online['age'], df_train['age'])
if psi < 0.1:
    print('变量稳定,放心使用')
elif psi < 0.25:
    print('变量有轻微偏移,建议监控')
else:
    print('变量严重偏移,需要排查原因')

避坑指南:我曾经遇到一个案例,所有变量PSI都小于0.05,但模型效果就是不行。后来发现是变量之间的交互关系变了。所以PSI只能看单个变量,变量间的联合分布变化它看不出来。建议配合模型监控一起做。

3.2.3 相关性分析——剔除冗余变量

相关性分析解决的是「变量打架」的问题。两个变量高度相关,留一个就够了。

我一般看两个指标:

  • 皮尔逊相关系数:看线性相关,阈值0.7以上要处理
  • VIF(方差膨胀因子):看多重共线性,VIF > 10 说明问题严重
# 相关性矩阵与VIF计算
import seaborn as sns
from statsmodels.stats.outliers_influence import variance_inflation_factor

# 1. 相关性矩阵
corr_matrix = df[selected_features].corr()
high_corr_pairs = np.where(np.abs(corr_matrix) > 0.7)
high_corr_pairs = [(corr_matrix.index[x], corr_matrix.columns[y]) 
                   for x, y in zip(*high_corr_pairs) if x != y]

# 2. VIF计算
X = df[selected_features].values
vif_data = pd.DataFrame()
vif_data['feature'] = selected_features
vif_data['VIF'] = [variance_inflation_factor(X, i) for i in range(X.shape[1])]

# 剔除VIF > 10的变量
final_features = vif_data[vif_data['VIF'] <= 10]['feature'].tolist()

我的经验:相关性分析有个坑——它只抓线性关系。两个变量非线性相关但业务上高度关联,相关系数可能很低。比如年龄和收入,不是线性关系,但业务上明显相关。这时候要结合业务判断,别只看数字。

3.2.4 随机森林特征重要性——非线性关系的利器

随机森林的好处是能自动捕捉非线性关系和交互效应。我一般在IV筛选和相关性分析之后,用随机森林做最后排序。

# 随机森林特征重要性
from sklearn.ensemble import RandomForestClassifier

# 训练随机森林
rf = RandomForestClassifier(
    n_estimators=200,
    max_depth=5,
    random_state=42,
    n_jobs=-1
)
rf.fit(X_train, y_train)

# 获取特征重要性
importance_df = pd.DataFrame({
    'feature': feature_names,
    'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)

# 累计重要性达到95%的变量
importance_df['cumsum'] = importance_df['importance'].cumsum()
selected_rf = importance_df[importance_df['cumsum'] <= 0.95]['feature'].tolist()

注意:随机森林的特征重要性有个问题——它对类别多的变量有偏好。一个变量有100个类别,它分叉次数多,重要性自然高。我建议用置换重要性(Permutation Importance)做补充验证。

3.3 筛选流程总结

说了这么多,我总结一下自己的筛选流程:

  1. 第一步:IV粗筛——剔除IV < 0.02的变量,保留潜在有用的
  2. 第二步:PSI检查——剔除PSI > 0.25的不稳定变量
  3. 第三步:相关性去冗余——相关系数 > 0.7或VIF > 10的,保留IV高的那个
  4. 第四步:随机森林排序——用特征重要性做最终排序,保留累计重要性95%的变量
  5. 第五步:业务复核——把剩下的变量拿给业务看,确认每个变量都有业务意义

这套流程我用了好几年,基本没出过大问题。当然,具体场景可以微调。比如做反欺诈模型,稳定性要求更高,PSI阈值可以卡到0.1。做信用评分卡,可解释性要求更高,随机森林那步可以换成逻辑回归的系数排序。

变量筛选没有银弹。你想想看,每个数据集都有自己的脾气,关键是理解方法背后的逻辑,然后灵活组合。嗯,今天就聊到这儿,下次我们聊聊分箱的具体技术。