本文利用python实现RFE特征选择、基于树模型的特征选择、lasso回归特征选择、Chi2特征选择、特征间相关性进行特征选择、IV值特征选择,K-S值特征选择。并放到一个类中,方便在数据建模时直接调用。
对与机器学习建模。在海量特征时,特征工程选择是必要的。特征工程很大程度上决定了模型的效果和模型的稳定性。特征工程中包函内容很多,包括数据分析,特征组合变换,特征选择和特征降维等等的技术。特征工程和数据的清洗占据了建模过程中绝大部分的时间。其中特征选择是必不可少的阶段。
当建模样本数量不足,但特征较多的时候。特征选择是必须的。因为参数的数量规模往往是与特征的多少是正相关的。如果没有足够的样本支撑特征参数的估计时,我们必须增加样本量,或减少估计的参数。
一般出现两种情况时,我们要进行特征选择。
首先,特征之间存在明显的相关性,我们称之为“冗余特征”,将这些特征全部加入模型训练是不必要的,甚至会导致参数的估计是不稳定,甚至是错误的。(计量经济学中也是同样的道理,样本数量小于参数数量,参数是估计不出来的。参数之间的共线性,导致参数估计不准确。)
其次,当一个特征与我们模型的目标值不相关的时候,我们称之为“无关特征”。这类特征加入模型后相当于模型增加了噪声,模型变得“厚重”且“不稳定”。在建模的时候也必须将无用或者几乎没有作用的特征删除。
特征选择对于建模往往具有以下作用:
特征选择的目的是选择选择特征集的一个子集,对于特征集,需要搜索里面的可能特征子集使我们的评价函数达到最优。关于特征搜索,有一下三种方式:
而根据特征选择和评价的方式的不同,特征选择的算法通常分为三类:
关于特征选择的方法,有很多文章进行了生动讲述,此处就不再重复了。可参考:
链接: 机器学习:特征选择(feature selection)
特征选择(feature selection)
其中对于数值型特征,常用的特征选择方法有RFE特征选择、基于树模型的特征选择、lasso回归选择,其中一些方法选择特征时要注意对特征进行标准化。在风控领域中特常用IV值,K-S值进行特征选择。
对于类别型特征,常用的特征选择方式是利用假设检验的方式进行卡方检验。
**本文利用python实现RFE特征选择、基于树模型的特征选择、lasso回归特征选择、Chi2特征选择、特征间相关性进行特征选择、IV值特征选择,K-S值特征选择。**并放到一个类中,方便在数据建模时直接调用。
# -*- coding: utf-8 -*-
"""
Created on Thu Dec 12 19:23:11 2019
@author: nbszg
"""
import pandas as pd
import numpy as np
from scipy import stats
from sklearn.feature_selection import RFE
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.linear_model import LassoCV
#特征选择器
class Feature_Selector(object):
#用于计算特征列de WOE,WOE的计算是计算IV的基础步骤
def woe_cul(self, df,col,label_col):
'''
用于计算特征列的 WOE
df:存储特征与标签的dataframe
col:需要计算的特征列名称
label_col:标签列名称
return:WOE计算结果的dataframe
'''
subdata=pd.DataFrame(df.groupby(col)[col].count()) #记录每个分组的数量
subdata.rename(columns={col:'total'}, inplace=True)
suby=pd.DataFrame(df.groupby(col)[label_col].sum()) #记录每个分组的正样本数量
data=pd.DataFrame(pd.merge(subdata,suby,how="left",left_index=True,right_index=True))
b_total=data[label_col].sum() #记录正样本总数量
total=data['total'].sum() #总样本数量
g_total=total-b_total #负样本数量
data["bad"]=data[label_col]/b_total #每组正样本占比
data["good"]=(data['total']-data[label_col])/g_total #每组负样本占比
data["WOE"]=data.apply(lambda x:np.log(x.bad/x.good),axis=1) #woe计算公式
return data.loc[:,["bad","good","WOE"]]
#计算IV
def iv_cul(df):
'''
用于计算特征列的IV值
df:WOE计算结果的dataframe
return:计算特征列的IV值
'''
df["IV"]=df.apply(lambda x:(x.bad-x.good)*x.WOE,axis=1) #IV的计算公式
IV=sum(df["IV"])
return IV
#基于IV值的特征选择函数
def iv_selector(self, df, feature_col, label_col, threshold=0.1):
'''
使用特征列与label列的IV值进行特征选择
df:存储特征与标签的dataframe
feature_col:可选择的特征列名列表为list
label_col:标签列名称,为str
threshold:选择的阈值,若ks值大于threshold,则加入到选择变量列表
return:函数最终选择的特征列表
'''
n=20 #计算IV时需要将连续变量进行分组,这里分20组
k=100//n #用于后面的计算分位数
iv_col=[]
for col in feature_col:
df_copy = df[[col, label_col]].copy()
df_copy[col+'_group'] = df_copy[col] #用于记录分组变量
for i in range(n):
if i<n-1:
df_copy.loc[(df_copy[col]>=df_copy[col].quantile(k*i/100)) & (df_copy[col]<df_copy[col].quantile(k*(i+1)/100)),col+'_group'] = i+1 #记录变量所在的组数
else:
df_copy.loc[(df_copy[col]>=df_copy[col].quantile(k*i/100)) & (df_copy[col]<=df_copy[col].quantile(k*(i+1)/100)),col+'_group'] = i+1 #边界值特殊处理
woe_trian = self.woe_cul(df_copy, col+'_group', label_col)#计算woe
iv=self.iv_cul(woe_trian) #计算IV值
if iv>=threshold:
iv_col.append(col)
return iv_col
#计算ks值
def ks_cal(self, df, score_col, label_col):
'''
用于计算特征列与标签列的ks值
df:存储特征与标签的dataframe
score_col:需要计算的特征列名称
label_col:标签列名称
return:计算ks值
'''
ks=0
if (isinstance(score_col, str) and isinstance(label_col, str)):
Bad = df.ix[df[label_col]==1,score_col] #记录 label=1
Good = df.ix[df[label_col]==0, score_col] #记录 label=0
ks,pvalue = stats.ks_2samp(Bad.values,Good.values) #计算ks
else:
raise TypeError("score_col or class_col must be str")
return ks
#基于ks值特征选择函数
def ks_selector(self, df, feature_col, label_col, threshold=0.1):
'''
使用特征列与label列的ks值进行特征选择
df:存储特征与标签的dataframe
feature_col:可选择的特征列名列表为list
label_col:标签列名称,为str
threshold:选择的阈值,若ks值大于threshold,则加入到选择变量列表
return:函数最终选择的特征列表
'''
ks_col=[]
for col in feature_col:
ks = self.ks_cal(df, col, label_col)
if ks>=threshold:
ks_col.append(col) #若ks值大于threshold,则加入到选择变量列表
return ks_col
#基于卡方检验的特征选择器
def chi2_selector(self, df, feature_col, label_col, p=0.05):
'''
基于卡方检验的特征选择器
df:存储特征与标签的dataframe
feature_col:可选择的特征列名列表为list
label_col:标签列名称,为str
p:检验统计量对应p值的阈值
return:函数最终选择的特征列表
'''
test = SelectKBest(score_func=chi2, k=len(feature_col))
fit = test.fit(df[feature_col], df[label_col])
select_col=np.array(feature_col)[test.pvalues_<p]
return list(select_col)
#基于Logistic的RFE特征选择函数
def rfe_selector(self, df, feature_col, label_col, select_num):
'''
使用特征列与label列的进行RFE特征选择
df:存储特征与标签的dataframe
feature_col:可选择的特征列名列表为list
label_col:标签列名称,为str
select_num:需要选择的特征数量
return:函数最终选择的特征列表
'''
if select_num>len(feature_col):
raise ValueError("rfe_selector's select_num greater than length of feature_col") #选择数量不可大于原有特征数量
model = LogisticRegression()
rfe = RFE(model, select_num)
rfe = rfe.fit(df[feature_col], df[label_col]) #使用RFE进行选择
rfe_col = list(np.array(feature_col)[rfe.support_])
return rfe_col
#基于树模型的选择函数
def tree_selector(self, df, feature_col, label_col, select_num):
'''
使用特征列与label列,基于树模型进行特征选择
df:存储特征与标签的dataframe
feature_col:可选择的特征列名列表为list
label_col:标签列名称,为str
select_num:需要选择的特征数量
return:函数最终选择的特征列表
'''
if select_num>len(feature_col):
raise ValueError("tree_selector's select_num greater than length of feature_col") #选择数量不可大于原有特征数量
model = ExtraTreesClassifier() #分类器
model.fit(df[feature_col], df[label_col])
importance = pd.DataFrame()
importance['col'] = feature_col
importance['importance'] = model.feature_importances_
importance.sort_values('importance',ascending=False, inplace=True) #按照树模型得到特征重要性排序
tree_col = list(importance['col'][0:select_num]) #选择排在前select_num个特征
return tree_col
#基于lasso回归的特征选择,lasso回归可理解为带l1正则化的线性回归。
def lassoCV_selector(self, df, feature_col, label_col, select_num, scale=True):
'''
基于lasso回归的特征选择,lasso回归可理解为带l1正则化的线性回归。
df:存储特征与标签的dataframe
feature_col:可选择的特征列名列表为list
label_col:标签列名称,为str
select_num:需要选择的特征数量
scale:是否进行归一化,与树模型不同。回归参数大小对于规模敏感,因此需要归一化
return:函数最终选择的特征列表
'''
if select_num>len(feature_col):
raise ValueError("lassoCV_selector's select_num greater than length of feature_col") #选择数量不可大于原有特征数量
df_copy = df[feature_col].copy()
df_copy[label_col] = df[label_col]
if scale==True:
for col in feature_col:
df_copy[col] = (df_copy[col]-df_copy[col].min())/(df_copy[col].max()-df_copy[col].min()) #因为基于回归,对规模敏感,因此需要归一化
model_lasso = LassoCV(alphas = [0.1,1,0.001, 0.0005]).fit(df_copy[feature_col], df_copy[label_col])
coef = pd.Series(model_lasso.coef_, index = feature_col)
feature_importance = pd.DataFrame(coef.sort_values()).reset_index()
head_list = list(feature_importance['index'].head(int(select_num/2))) #选择参数大小排在select_num//2的特征
tail_list = list(feature_importance['index'].tail(int(select_num/2))) #因为参数估计可能为负数,设参数为正负数量各一半,因此还选择参数大小排在最后select_num//2的特征,也可以直接对参数直接取绝对值,排序后直接取select_num个
lassoCV_col = head_list+tail_list
return lassoCV_col
#基于特征间相关性的特征选择器
def relation_selector(self, df, feature_col, threshold=0.5): #判断类别型特征间的相关性,将相关性高于threshold的两个特征删除一个
'''
基于特征间相关性的特征选择器
df:存储特征与标签的dataframe
feature_col:可选择的特征列名列表为list
threshold:设定的特征相关性的阈值,相关系数高于threshold的两列特征,需要删除一列
return:函数最终选择的特征列表
'''
drop_col = [] #用于记录删除的列
corr_df=df[feature_col].corr() #计算相关系数矩阵
for i in range(len(feature_col)-1):
#相关性高的两个特征中,将一个加入要删除的列表
drop_col = drop_col+list(corr_df.ix[i][i+1:][corr_df.ix[i][i+1:]>threshold].index) #因为对对称矩阵,且为了防止重复删除,只遍历矩阵的下三角部分即可,大于阈值的列加入删除列
drop_col=list(set(drop_col)) #去重
res_col=[]
for col in feature_col: #取feature_colfeature_col与drop_col的差集
if col not in drop_col:
res_col.append(col)
return res_col