《Python金融大数据风控建模实战》 第7章 变量选择
- 本章引言
- Python代码实现及注释
本章引言
变量选择常见的方法有过滤法、包装法、嵌入法,并且在上述方法中又有单变量选择、多变量选择、有监督选择、无监督选择。在实际应用中,单纯从数据挖掘的角度进行变量选择是不够的,还要结合业务理解对选择后的变量进行回测,以符合业务解释。
Python代码实现及注释
# 第7章:变量选择
'''
在变量分箱的基础上进行变量编码,然后进行变量编码,然后进行变量选择,变量选择程序主要采用scikit-learn包中的feature_selection
模块来完成,实践中常用的函数为:
category_continue_separation: 区分离散变量与连续变量
sklearn.feature_selection.VarianceThreshold: 方差筛选函数
sklearn.feature_selection.SelectKBest: 单变量筛选函数
Pandas.DataFrame.corr: 计算相关系数矩阵
sklearn.feature_selection.RFECV: 递归消除变量筛选函数
sklearn.feature_selection.SelectFromModel: 嵌入法变量选择函数
feature_selector: 一个集成的变量选择函数
程序运行逻辑:数据读取-->划分数据集与测试集-->区分离散变量与连续变量-->变量分箱-->分箱后进行WOE编码-->多种变量选择方法测试
'''
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
'''
Seaborn是matplotlib库的扩展,主要专注于统计学的分析
Seaborn背后有调色板
import seaborn as sns
sns.set_style(‘darkgrid’) 设置一些背景
sns.load_dataset(‘tips’) 加载数据 seaborn datasets
'''
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 = 'D:\\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())))
## 如果变量的值小于等于10,直接放在类型变量中,从数字类型中删去
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)
'''
def cont_var_bin(x, y, method, mmin=5, mmax=10, bin_rate=0.01, stop_limit=0.1, bin_min_num=20):
"""
##连续变量分箱函数
##参数
x:输入分箱数据,pandas series
y:标签变量
method:分箱方法选择,1:chi-merge , 2:IV值, 3:基尼系数分箱
mmin:最小分箱数,当分箱初始化后如果初始化箱数小于等mmin,则mmin=2,即最少分2箱,
如果分两箱也无法满足箱内最小样本数限制而分1箱,则变量删除
mmax:最大分箱数,当分箱初始化后如果初始化箱数小于等于mmax,则mmax等于初始化箱数-1
bin_rate:等距初始化分箱参数,分箱数为1/bin_rate,分箱间隔在数据中的最小值与最大值将等间隔取值
stop_limit:分箱earlystopping机制,如果已经没有明显增益即停止分箱
bin_min_num:每组最小样本数
##返回值
return data , gain_value_save0 ,gain_rate_save0
分箱结果:pandas dataframe
"""
'''
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:
'''
"""
离散变量分箱方法,如果变量过于稀疏最好先编码在按连续变量分箱
##参数:
x:输入分箱数据,pandas series
y:标签变量
method:分箱方法选择,1:chi-merge , 2:IV值, 3:信息熵
mmin:最小分箱数,当分箱初始化后如果初始化箱数小于等mmin,则mmin=2,即最少分2箱,
如果分两厢也无法满足箱内最小样本数限制而分1箱,则变量删除
mmax:最大分箱数,当分箱初始化后如果初始化箱数小于等于mmax,则mmax等于初始化箱数-1
stop_limit:分箱earlystopping机制,如果已经没有明显增益即停止分箱
bin_min_num:每组最小样本数
##返回值
return temp_cont.sort_values(by='bin') , gain_value_save0 , gain_rate_save0,del_key
分箱结果:pandas dataframe
"""
'''
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]
'''
scikit-learn的feature_selection特征选择模块中的VarianceThreshold方法实现了方差极小的变量筛选方法,函数原型如下:
class sklearn.feature_selection.VarianceThreshold(threshold=0.0)
threshold:浮点数,用于指定变量方差的最小阈值,方差小于阈值的变量会被剔除,默认为0
主要方法:
fit(self,X[,y]): 用于模型训练,在训练集上训练模型
transform(self,X):用fit方法得到的模型对原始数据进行方差极小变量剔除,这个过程可以通过fit_transform函数将训练和转化的过程集成在一起完成
get_support(self[,indices]):得到被保留的变量的索引,以便确定哪些变量被删除
'''
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)
'''
scikit-learn中提供了多种变量选择方法,如SelectKBest、SelectPercentile及GenericUnivariateSelect等方法,通过计算特征的统计指标完成变量筛选。SelectKBest提供了通过指标排序,给出得分最高的K个变量作为变量选择的结果。其原型如下:
class sklearn.feature_selection.SelectKBest(score_func=<function f_classif>, k=10)
主要参数说明:
k:整数或字符串,用于指定选择得分最高的前百分之几的特征。如果为字符串all,则保留所有特征。
score_func:用于计算输入变量与目标变量之间统计变量的函数,返回数组(scores,pvalues)或单个得分数组。
常用的指标有:chi2卡方统计分量用于分类任务;
f_classif方差分析用于分类任务
f_regression计算特征与目标变量之间的F值,即用线性回归分析方法来计算统计指标,用于回归问题
主要属性说明:
scores:用于查询变量选择时每个变量的得分
pvalues_:用于查询变量得分的p值
'''
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.bar()
x:表示x坐标,数据类型为int或float类型,刻度自适应调整;也可传dataframe的 object,x轴上等间距排列;
height:表示柱状图的高度,也就是y坐标值,数据类型为int或float类型;
width:表示柱状图的宽度,取值在0~1之间,默认为0.8;
bottom:柱状图的起始位置,也就是y轴的起始坐标;
align:柱状图的中心位置,默认"center"居中,可设置为"lege"边缘;
color:柱状图颜色;
edgecolor:边框颜色;
linewidth:边框宽度;
tick_label:下标标签;
log:柱状图y周使用科学计算方法,bool类型;
orientation:柱状图是竖直还是水平,竖直:“vertical”,水平条:“horizontal”;
'''
plt.barh(np.arange(0,len_1),select_uinvar_model.scores_,color = 'c',tick_label=var_name)
'''
用matplotlib画二维图像时,默认情况下的横坐标和纵坐标显示的值有时达不到自己的需求,需要借助xticks()和yticks()分别对横坐标x-axis和纵坐标y-axis进行设置。
'''
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")
##递归消除法
'''
scikit-learn的feature_selection特征选择模块中并没有实现逐步回归stepwise regression变量选择,但是提供了后向搜索方法的代码实现:
递归消除方法(RFE)与带交叉验证的递归消除方法(RFECV)。RFE方法通过自定义的学习器,开始在全部空间变量中训练学习器,得到每个变量的权重后,剔除当前权重最小的那个变量,
再重新训练学习器,直至变量数满足预定设置值为止。与RFE相比,RFECV不需要预先设定选择的特征数量,通过交叉验证方法得到一个最优的变量个数。其原型如下:
class sklearn.feature_selection.RFECV(estimator, step = 1, min_features_to_select = 1, cv = 'warn', scoring = None, verbose = 0, n_jobs = None)
主要参数说明:
estimator: 学习器,可自行选择,学习器必须具有coef_属性或feature_importance_属性。
step:整数或浮点数,用于指定每次迭代过程(变量选择过程)要剔除的变量数。如果为大于1的整数,则每次剔除这个数量的变量;如果为浮点数,则按变量比例剔除变量
cv:指定交叉验证的次数,默认为3折交叉验证,也可以是交叉验证生成器。
scoring:用于指定学习器的性能评估指标,如果为None,则使用estimator.score()方法评估学习器性能
主要属性说明:
n_feature_:用于查询变量选择后保留的特征数
support_:查询哪个变量被保留,返回一个数组,如果为True表示该变量被保留,反之表示该变量被删除
'''
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')
##嵌入法变量选择
'''
scikit-learn的feature_selection特征选择模块中SelectFromModel方法实现了嵌入法变量选择,其原型如下:
class sklearn.feature_selection.SelectFromModel(estimaor,threshold=None,prefit = False,norm_order = 1,max_features=None)
主要参数说明:
estimator: 学习器,可自行选择,与递归消除方法中对学习器的要求一样,学习器必须具有coef_属性或feature_importance_属性。变量选择过程通过这些权重或重要性进行变量剔除。
threshold:字符串或浮点数,用于指定每次迭代过程(变量选择过程)要剔除的变量数。如果为浮点数,则小于阈值的变量被剔除。如果是字符串则提供一种启发式的变量剔除方法。如为mean,则以特征重要性的均值作为阈值,小于均值的变量被剔除,同理还有median等。
prefit:布尔类型,指定是否为预训练的模型,如果模型已经训练好,则将prefit置为True。
max_features:整数或None,变量选择的最大变量数,与threshold参数类似,如果已经设定了threshold,则该参数可忽略。如果threshold=inf,则该参数起作用。
主要属性说明:
threshold_:用于查询变量选择的阈值
get_support:查看选择了哪些变量
'''
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)
##设置筛选参数
'''
剔除缺失值比例大于0.9,相关性大于0.6,方差为0,变量重要性为0或较低的变量,保留累计重要性达到90%的变量。
corr_matrix:查看变量的相关矩阵
feature_importances:查看变量重要性的累计贡献率
record_low_importance:查看低重要性的变量
'''
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