特征选择 Python代码

本文利用python实现RFE特征选择、基于树模型的特征选择、lasso回归特征选择、Chi2特征选择、特征间相关性进行特征选择、IV值特征选择,K-S值特征选择。并放到一个类中,方便在数据建模时直接调用。

一、特征选择

对与机器学习建模。在海量特征时,特征工程选择是必要的。特征工程很大程度上决定了模型的效果和模型的稳定性。特征工程中包函内容很多,包括数据分析,特征组合变换,特征选择和特征降维等等的技术。特征工程和数据的清洗占据了建模过程中绝大部分的时间。其中特征选择是必不可少的阶段。
当建模样本数量不足,但特征较多的时候。特征选择是必须的。因为参数的数量规模往往是与特征的多少是正相关的。如果没有足够的样本支撑特征参数的估计时,我们必须增加样本量,或减少估计的参数。
一般出现两种情况时,我们要进行特征选择。
首先,特征之间存在明显的相关性,我们称之为“冗余特征”,将这些特征全部加入模型训练是不必要的,甚至会导致参数的估计是不稳定,甚至是错误的。(计量经济学中也是同样的道理,样本数量小于参数数量,参数是估计不出来的。参数之间的共线性,导致参数估计不准确。)
其次,当一个特征与我们模型的目标值不相关的时候,我们称之为“无关特征”。这类特征加入模型后相当于模型增加了噪声,模型变得“厚重”且“不稳定”。在建模的时候也必须将无用或者几乎没有作用的特征删除。
特征选择对于建模往往具有以下作用:

  1. 减少建模使用特征的数量,使模型训练和预测用时更少
  2. 降低模型使用特征间的相关性,提高模型参数估计的精度
  3. 减少模型中无用的噪声,提高模型的稳定性

二、特征的方法

特征选择的目的是选择选择特征集的一个子集,对于特征集,需要搜索里面的可能特征子集使我们的评价函数达到最优。关于特征搜索,有一下三种方式:

  1. 完全搜索:即搜索所有可能的特征子集。计入试验,选取最优子集,复杂度太高。
  2. 启发式搜索:使用“贪心”思想,当一个特征加入已选特征子集后,从剩余的待选特征集中选择一个使目标值最优的特征加入已选特征子集。重复此步骤,直到停止条件
  3. 随机搜索:在模型的搜索中设置一些超参数或者条件。这些参数条件会应县特征选择,也使得搜索加速。

而根据特征选择和评价的方式的不同,特征选择的算法通常分为三类:

  1. 过滤式(Filter):事先进行特征选择,再将选择的特征加入我们的机器学习模型。特征选择的方式与目标通常与我们选择的机器学习模型是无关的。常用的方法如:chi2检验,信息增益,相关系数法等等。在风控模型和一些评分卡模型中,也是用IV值,k-s值等进行特征选择。
  2. 包裹时(Wrapper):直接把最终将要使用的机器学习的评价函数作为特征子集的评价准则,选择可以使所用的机器学习性能最好的模型,如sklearn.feature_selection的RFE选择器。
  3. 嵌入式(Embedding):让模型在学习的过程中自动进行参数选择。如L1正则化,其结果是无用或惩罚较大的特征前的系数变为0。

关于特征选择的方法,有很多文章进行了生动讲述,此处就不再重复了。可参考:
链接: 机器学习:特征选择(feature selection)
特征选择(feature selection)

其中对于数值型特征,常用的特征选择方法有RFE特征选择、基于树模型的特征选择、lasso回归选择,其中一些方法选择特征时要注意对特征进行标准化。在风控领域中特常用IV值,K-S值进行特征选择。

对于类别型特征,常用的特征选择方式是利用假设检验的方式进行卡方检验。

三、一些特征选择方法的python实现

**本文利用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

你可能感兴趣的:(python,机器学习,机器学习,python,数据分析)