变量选择详解与源码实现

在这里插入图片描述

变量选择是特征工程中非常重要的一部分。特征工程是一个先升维后降维的过程

  • 升维的过程是结合业务理解尽可能多地加工特征,是一个非常耗时且需要发散思维的过程
  • 变量选择就是降维的过程

因为传统评分卡模型为了保证模型的稳定性与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越小越好

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qHG5GqJ-1606030616223)(FF360CACEA30431D81B31547272D9750)]

  • 从时间变化角度,训练集和测试集的分布 差异越小越好,同分布最好

IV 值就是对称化的 K-L 距离,可以衡量类别分布的差异情况,反映输入变量对标签的预测能力

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iI941UZh-1606030616227)(2D02861815E743CC8FD24049523ADD97)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oj7hOyEa-1606030616230)(AD7172043D2848EFAAD586FE2C1DFF21)]

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

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WGS.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值