第三章 变量筛选方法论:筛选原则与常用方法
变量筛选这件事,说白了就是给模型挑「好料」。
我刚开始做风控建模时,总觉得变量越多越好。结果呢?模型过拟合得一塌糊涂,上线后效果断崖式下跌。后来我明白了——变量筛选不是做加法,而是做减法。你想想看,一百个变量里,真正有用的可能不到二十个。
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 可解释性
可解释性就是变量能不能讲清楚道理。风控模型不像推荐系统,黑箱模型很难过监管。
我一般问三个问题:
- 这个变量业务上合理吗?
- 它的分箱逻辑能解释给业务听吗?
- 如果模型拒绝了一个客户,能用这个变量说明原因吗?
举个例子,用「客户星座」做变量,预测力可能有一点,但你怎么解释?「因为你是天蝎座所以被拒贷」?这肯定不行。
3.2 常用筛选方法概览
筛选方法我一般分四类。嗯,这里我画了一张图,帮你理清思路。
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 筛选流程总结
说了这么多,我总结一下自己的筛选流程:
- 第一步:IV粗筛——剔除IV < 0.02的变量,保留潜在有用的
- 第二步:PSI检查——剔除PSI > 0.25的不稳定变量
- 第三步:相关性去冗余——相关系数 > 0.7或VIF > 10的,保留IV高的那个
- 第四步:随机森林排序——用特征重要性做最终排序,保留累计重要性95%的变量
- 第五步:业务复核——把剩下的变量拿给业务看,确认每个变量都有业务意义
这套流程我用了好几年,基本没出过大问题。当然,具体场景可以微调。比如做反欺诈模型,稳定性要求更高,PSI阈值可以卡到0.1。做信用评分卡,可解释性要求更高,随机森林那步可以换成逻辑回归的系数排序。
变量筛选没有银弹。你想想看,每个数据集都有自己的脾气,关键是理解方法背后的逻辑,然后灵活组合。嗯,今天就聊到这儿,下次我们聊聊分箱的具体技术。