变量选择是特征工程中非常重要的一部分。特征工程是一个先升维后降维的过程
- 升维的过程是结合业务理解尽可能多地加工特征,是一个非常耗时且需要发散思维的过程
- 变量选择就是降维的过程
因为传统评分卡模型为了保证模型的稳定性与Logisitc回归模型的准确性,往往对入模变量有非常严格的考量,并希望入模变量最好不要超过 20 个,且要与业务强相关。这时变量选择显得尤为重要,特征加工阶段往往可以得到几百维或更高维度的特征,而从诸多特征中只保留最有意义的特征也是重要且耗时的过程。
变量选择的方法很多,常用的方法有过滤法(Filter)、包装法(Wrapper)、嵌入法(Embedding),并且在上述方法中又有单变量选择、多变量选择、有监督选择、无监督选择。注意,在实际应用中,单纯从数据挖掘的角度进行变量选择是不够的,还要结合业务理解对选择后的变量进行回测,以符合业务解释,即变量选择的最终结果还要具有业务解释性,脱离了具体业务而得到的变量选择结果,即使可以达到较好的预测效果,也需要进一步推敲。
评分卡中一般根据行业经验特征最好不要超过20个,因为模型要有可解释性
1.过滤法变量选择
过滤法选择是一种与模型无关的变量选择方法,就是先进行变量选择再进行模型训练
既然与模型无关,那就要尽量从变量的预测能力及对标签的区分能力考虑,因此任何反应或衡量变量预测能力的指标都可以用于变量选择,这样的指标有信息增益、信息增益比、基尼系数、IV值等
- 从数据源角度考虑:
- 缺失值情况和自身方差情况可以进行变量选择
- 从相关性角度考虑:
- 要求最大相关、最小冗余
- 即变量与标签之间要尽可能相关性较高
- 变量与变量之间可能没有相关性,即没有多重共线性的问题
理解多重共线性
如果某一个特征可以被别的特征线性表示
正常的是用这些特征(组合一个公式、模型)去表示y的,但是这些特征拟合某个x拟合的很好,这样x和y之间就出现干扰了,可能会带偏模型的参数和拟合的效果。
1.1缺失情况变量筛选
-
一般缺失值大于 80%就应该将该变量删除
- 对模型训练不会起太大作用
评分卡要求可解释性好,特征一般不超过20个 缺失值本身就是一种不确定性,因此那些缺失情况较多的变量不需要保留
-
可以对缺失值进行分享或WOE
- 缺失值在评分卡模型中是比较重要的特征
评分卡这里缺失可能不是随机缺失,很大可能是人为的故意缺失, 或者是与人的隐私相关的信息中,不填可能本身就包含某种含义
1.2方差变量筛选
低方差过滤
api: sklearn.feature_selection.VarianceThreshold(threshold = 0.0)
方差体现着波动性, 方差越小波动越小, 对模型的影响越小
1.3预测能力变量筛选
预测能力相关指标有信息增益、信息增益比、基尼系数和卡方值、IV值等。
- 信息增益
- 越大表示不确定性越好、纯度越高,对好坏的分类效果越好
- 分枝前的熵 - 分枝后的熵
- 信息增益率
- 也是越大越好
- 信息增益比 = 信息增益 / 划分前的熵
- 基尼系数
- 分类:基尼系数小好
- 回归:均方差最小
- 基尼指数(基尼不纯度)= 样本被选中的概率 \ 样本被分错的概率
- 卡方值
- 卡方值小相邻区间就合并
- 卡方值越大两个字段的差异就越大
- IV值
- IV越大说明特征越重要
- 0.02
1.4业务理解的变量筛选(IV、PSI)
这里所说的业务理解是一些可量化的客观业务指标,说白了就是可以根据公式计算的
一般可用于变量选择且与评分卡业务紧密相关的指标为 IV 值和 PSI 值
IV越大越好
- 是从类别角度考虑的,坏样本分布和好样本分布
- 超过0.5是不是异常数据
PSI越小越好
- 从时间变化角度,训练集和测试集的分布 差异越小越好,同分布最好
IV 值就是对称化的 K-L 距离,可以衡量类别分布的差异情况,反映输入变量对标签的预测能力
1.5相关性指标变量筛选(最大相关最小冗余)
准则就是最大相关和最小冗余
- 最大相关:
- x和y之间强相关
- 最小冗余
- 特征之间弱相关
皮尔逊相关系数
相关系数是计算连续变量之间的相关性
如果计算离散变量与离散变量之间的相关性,则需要用卡方检验的方法或IV值
2.包装法变量选择
包装法是一种与模型相关的变量选择方法
本质就是用模型做选择
- 基本思想:
在特征空间中随机挑选特征子集,然后选择一个模型,用交叉验证测试不同子集在模型上的表现
评估方法如KS值、AR、AUC、FI等指标
- 特征子集的构建:
- 1.前向搜索
即初始随机选取一个特征构造模型,然后再随机 选择一个特征加入前一个特征子集中,构成新的特征子集,并再 建立一个模型,最后比较两个模型性能是否提升,以此来判断特 征子集的优劣
- 2.后向搜索
后向搜索与前向搜索相反,从使用特征全集开始建模,然后 每次剔除一个或多个特征,构造新的特征子集,建立新的模型, 评估两个特征子集下模型的性能,以确定最优的特征子集
- 3.双向搜索
双向搜索是将前向与后向搜索相结合的方法,初始时选择一 个变量建模,然后采用前向算法,随机选择一个变量构成由两个 变量组成的特征子集。在下一步构造特征子集的过程中,采用前 向算法进行增加特征,同时采用后向算法减少特征,直到模型的 性能不再提升,则得到最优的特征子集
3.嵌入法变量选择
与包装法类似,也是模型相关的变量选择方法,本质就是找了一个特殊的模型
常用的嵌入法选择变量方法有:
- 加入L1正则的模型
- 随机森林变量选择
- 构建树模型天然就是构建特征选择的过程
- 在评分卡模型中逻辑回归也可以做特征选择
- 分箱后的WOE编码要单调或近似单调
4.一般变量选择过程
1.基于IV值进行初步筛选
IV值越大特征越重要,也不要太大,常用0.02作为阈值
2.聚类分析
将不同特征进行聚类得到不同的簇
目的是在接下来的变量选择中,在选择剔除变量时要有簇的概念,优先从不同的簇中删除变量
3.相关性分析
最大相关最小冗余
计算变量编码后变量之间的相关性,给定
阈值进行变量剔除,剔除时要注意在不同的簇中进行选择。相关
系数的阈值不能太高,可以选择 0.6 左右。另外,也可以做方差
膨胀因子分析,剔除变量
4.逐步回归变量选择(包装法)
逐步回归是较好的一种变量选择方法,可以通过前向、后向或双向的方法进行变量选择
5.随机森林或 Xgboost 模型变量重要性排序,得到最终的变量筛选结果
也就是前面所说的嵌入法变量选择
注意,随机森林或 Xgboost 模型的变量选择并没
有考虑变量间的相关性问题,所以需要先过相关性或多重共线性
筛选后再进行变量重要性排序。通过累积贡献率的方式,或只选
择 TOP20 的变量即可。当然,也不要忽略不同簇的问题,尽量从
多维度保留入模变量的多样性
5.过滤法、包装法、嵌入法源码
1.读取数据区分离散变量与连续变量
2.对连续变量和离散变量分箱(删除分箱数只有1的)
3.对训练数据和测试数据做分箱映射
4.训练集和测试集进行WOE编码
5.过滤法
删除低变量方差
相关性分析(计算相关系数矩阵、画图)
6.包装法
递归消除法:SVR
7.嵌入法
lr、决策树
课本源码
import os
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import variable_bin_methods as varbin_meth
import variable_encode as var_encode
import matplotlib
import matplotlib.pyplot as plt
# matplotlib.use(arg='Qt5Agg')
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['axes.unicode_minus'] = False
from sklearn.linear_model import LogisticRegression
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.feature_selection import RFECV
from sklearn.svm import SVR
from sklearn.feature_selection import SelectFromModel
import seaborn as sns
from sklearn.tree import DecisionTreeClassifier
from feature_selector import FeatureSelector
import warnings
warnings.filterwarnings("ignore") ##忽略警告
##数据读取
def data_read(data_path, file_name):
df = pd.read_csv(os.path.join(data_path, file_name), delim_whitespace=True, header=None)
##变量重命名
columns = ['status_account', 'duration', 'credit_history', 'purpose', 'amount',
'svaing_account', 'present_emp', 'income_rate', 'personal_status',
'other_debtors', 'residence_info', 'property', 'age',
'inst_plans', 'housing', 'num_credits',
'job', 'dependents', 'telephone', 'foreign_worker', 'target']
df.columns = columns
##将标签变量由状态1,2转为0,1;0表示好用户,1表示坏用户
df.target = df.target - 1
##数据分为data_train和 data_test两部分,训练集用于得到编码函数,验证集用已知的编码规则对验证集编码
data_train, data_test = train_test_split(df, test_size=0.2, random_state=0, stratify=df.target)
return data_train, data_test
##离散变量与连续变量区分
def category_continue_separation(df, feature_names):
categorical_var = []
numerical_var = []
if 'target' in feature_names:
feature_names.remove('target')
##先判断类型,如果是int或float就直接作为连续变量
numerical_var = list(df[feature_names].select_dtypes(
include=['int', 'float', 'int32', 'float32', 'int64', 'float64']).columns.values)
categorical_var = [x for x in feature_names if x not in numerical_var]
return categorical_var, numerical_var
if __name__ == '__main__':
path = r'G:\A_实训前置1\python_workspace\finance_code\chapter7'
data_path = os.path.join(path, 'data')
file_name = 'german.csv'
##读取数据
data_train, data_test = data_read(data_path, file_name)
sum(data_train.target == 0)
data_train.target.sum()
##区分离散变量与连续变量
feature_names = list(data_train.columns)
feature_names.remove('target')
categorical_var, numerical_var = category_continue_separation(data_train, feature_names)
for s in set(numerical_var):
print('变量' + s + '可能取值' + str(len(data_train[s].unique())))
if len(data_train[s].unique()) <= 10:
categorical_var.append(s)
numerical_var.remove(s)
##同时将后加的数值变量转为字符串
index_1 = data_train[s].isnull()
if sum(index_1) > 0:
data_train.loc[~index_1, s] = data_train.loc[~index_1, s].astype('str')
else:
data_train[s] = data_train[s].astype('str')
index_2 = data_test[s].isnull()
if sum(index_2) > 0:
data_test.loc[~index_2, s] = data_test.loc[~index_2, s].astype('str')
else:
data_test[s] = data_test[s].astype('str')
###连续变量分箱
dict_cont_bin = {}
for i in numerical_var:
print(i)
dict_cont_bin[i], gain_value_save, gain_rate_save = varbin_meth.cont_var_bin(data_train[i], data_train.target,
method=2, mmin=3, mmax=12,
bin_rate=0.01, stop_limit=0.05,
bin_min_num=20)
###离散变量分箱
dict_disc_bin = {}
del_key = []
for i in categorical_var:
dict_disc_bin[i], gain_value_save, gain_rate_save, del_key_1 = varbin_meth.disc_var_bin(data_train[i],
data_train.target,
method=2, mmin=3,
mmax=8, stop_limit=0.05,
bin_min_num=20)
if len(del_key_1) > 0:
del_key.extend(del_key_1)
###删除分箱数只有1个的变量
if len(del_key) > 0:
for j in del_key:
del dict_disc_bin[j]
##训练数据分箱
##连续变量分箱映射
df_cont_bin_train = pd.DataFrame()
for i in dict_cont_bin.keys():
df_cont_bin_train = pd.concat(
[df_cont_bin_train, varbin_meth.cont_var_bin_map(data_train[i], dict_cont_bin[i])], axis=1)
##离散变量分箱映射
# ss = data_train[list( dict_disc_bin.keys())]
df_disc_bin_train = pd.DataFrame()
for i in dict_disc_bin.keys():
df_disc_bin_train = pd.concat(
[df_disc_bin_train, varbin_meth.disc_var_bin_map(data_train[i], dict_disc_bin[i])], axis=1)
##测试数据分箱
##连续变量分箱映射
df_cont_bin_test = pd.DataFrame()
for i in dict_cont_bin.keys():
df_cont_bin_test = pd.concat([df_cont_bin_test, varbin_meth.cont_var_bin_map(data_test[i], dict_cont_bin[i])],
axis=1)
##离散变量分箱映射
# ss = data_test[list( dict_disc_bin.keys())]
df_disc_bin_test = pd.DataFrame()
for i in dict_disc_bin.keys():
df_disc_bin_test = pd.concat([df_disc_bin_test, varbin_meth.disc_var_bin_map(data_test[i], dict_disc_bin[i])],
axis=1)
###组成分箱后的训练集与测试集
df_disc_bin_train['target'] = data_train.target
data_train_bin = pd.concat([df_cont_bin_train, df_disc_bin_train], axis=1)
df_disc_bin_test['target'] = data_test.target
data_test_bin = pd.concat([df_cont_bin_test, df_disc_bin_test], axis=1)
data_train_bin.reset_index(inplace=True, drop=True)
data_test_bin.reset_index(inplace=True, drop=True)
###WOE编码
var_all_bin = list(data_train_bin.columns)
var_all_bin.remove('target')
##训练集WOE编码
df_train_woe, dict_woe_map, dict_iv_values, var_woe_name = var_encode.woe_encode(data_train_bin, data_path,
var_all_bin, data_train_bin.target,
'dict_woe_map', flag='train')
##测试集WOE编码
df_test_woe, var_woe_name = var_encode.woe_encode(data_test_bin, data_path, var_all_bin, data_test_bin.target,
'dict_woe_map', flag='test')
y = np.array(data_train_bin.target)
###过滤法特征选择
##方差筛选
df_train_woe = df_train_woe[var_woe_name]
len_1 = df_train_woe.shape[1]
select_var = VarianceThreshold(threshold=0.01)
select_var_model = select_var.fit(df_train_woe)
df_1 = pd.DataFrame(select_var_model.transform(df_train_woe))
##保留的索引
save_index = select_var.get_support(True)
var_columns = [list(df_train_woe.columns)[x] for x in save_index]
df_1.columns = var_columns
##删除变量的方差
select_var.variances_[[x for x in range(len_1) if x not in save_index]]
[list(df_train_woe.columns)[x] for x in range(len_1) if x not in save_index]
[select_var.variances_[x] for x in range(len_1) if x not in save_index]
##单变量筛选
select_uinvar = SelectKBest(score_func=f_classif, k=15)
select_uinvar_model = select_uinvar.fit(df_train_woe, y)
df_1 = select_uinvar_model.transform(df_train_woe)
##看得分
len_1 = len(select_uinvar_model.scores_)
var_name = [str(x).split('_BIN_woe')[0] for x in list(df_train_woe.columns)]
##
plt.figure(figsize=(10, 6))
fontsize_1 = 14
plt.barh(np.arange(0, len_1), select_uinvar_model.scores_, color='c', tick_label=var_name)
plt.xticks(fontsize=fontsize_1)
plt.yticks(fontsize=fontsize_1)
plt.xlabel('得分', fontsize=fontsize_1)
plt.show()
##分析变量相关性
##计算相关矩阵
correlations = abs(df_train_woe.corr())
##相关性绘图
fig = plt.figure(figsize=(10, 6))
fontsize_1 = 10
sns.heatmap(correlations, cmap=plt.cm.Greys, linewidths=0.05, vmax=1, vmin=0, annot=True,
annot_kws={'size': 6, 'weight': 'bold'})
plt.xticks(np.arange(20) + 0.5, var_name, fontsize=fontsize_1, rotation=20)
plt.yticks(np.arange(20) + 0.5, var_name, fontsize=fontsize_1)
plt.title('相关性分析')
# plt.xlabel('得分',fontsize=fontsize_1)
plt.show()
##包装法变量选择:递归消除法
##给定学习器
estimator = SVR(kernel="linear")
##递归消除法
select_rfecv = RFECV(estimator, step=1, cv=3)
select_rfecv_model = select_rfecv.fit(df_train_woe, y)
df_1 = pd.DataFrame(select_rfecv_model.transform(df_train_woe))
##查看结果
select_rfecv_model.support_
select_rfecv_model.n_features_
select_rfecv_model.ranking_
###嵌入法变量选择
##选择学习器
lr = LogisticRegression(C=0.1, penalty='l2')
##嵌入法变量选择
select_lr = SelectFromModel(lr, prefit=False, threshold='mean')
select_lr_model = select_lr.fit(df_train_woe, y)
df_1 = pd.DataFrame(select_lr_model.transform(df_train_woe))
##查看结果
select_lr_model.threshold_
select_lr_model.get_support(True)
##基学习器选择预训练的决策树来进行变量选择
##先训练决策树
cart_model = DecisionTreeClassifier(criterion='gini', max_depth=3).fit(df_train_woe, y)
cart_model.feature_importances_
##用预训练模型进行变量选择
select_dt_model = SelectFromModel(cart_model, prefit=True)
df_1 = pd.DataFrame(select_dt_model.transform(df_train_woe))
##查看结果
select_dt_model.get_support(True)
###集成学习下的变量选择lightgbm
fs = FeatureSelector(data=df_train_woe, labels=y)
##设置筛选参数
fs.identify_all(selection_params={'missing_threshold': 0.9,
'correlation_threshold': 0.6,
'task': 'classification',
'eval_metric': 'binary_error',
'max_depth': 2,
'cumulative_importance': 0.90})
df_train_woe = fs.remove(methods='all')
##查看结果
fs.feature_importances
fs.corr_matrix
fs.record_low_importance