• 数据分析中的变量选择——德国信贷数据集(variable selection in data analysis-German credit datasets)


    最近看了一本《Python金融大数据风控建模实战:基于机器学习》(机械工业出版社)这本书,看了其中第7章:变量选择 内容,总结了主要内容以及做了代码详解,分享给大家。

    1. 主要知识点

    变量选择是特征工程中非常重要的一部分。特征工程是一个先升维后降维的过程。升维的过程是结合业务理解尽可能多地加工特征,是一个非常耗时且需要发散思维的过程。而变量选择就是降维的过程,因为传统评分卡模型为了保证模型的稳定性与Logisitc回归模型的准确性,往往对入模变量有非常严格的考量,并希望入模变量最好不要超过20个,且要与业务强相关。这时变量选择显得尤为重要,特征加工阶段往往可以得到几百维或更高维度的特征,而从诸多特征中只保留最有意义的特征也是重要且耗时的过程。
    变量选择的方法很多,常用的方法有过滤法(Filter)、包装法(Wrapper)、嵌入法(Embedding),并且在上述方法中又有单变量选择、多变量选择、有监督选择、无监督选择。

    2. 代码

    数据的使用还是德国信贷数据集,具体数据集介绍和获取方法请看 数据清洗与预处理代码详解——德国信贷数据集(data cleaning and preprocessing - German credit datasets)

    注意:

    import variable_bin_methods as varbin_meth
    import variable_encode as var_encode

    中 variable_bin_methods 和 variable_encode 分别是 第5章 变量编码第6章 变量分箱 中的代码。

    主代码:

      1 # -*- coding: utf-8 -*-
      2 """
      3 第7章:变量选择
      4 数据获取
      5 """
      6 import os
      7 import pandas as pd
      8 import numpy as np
      9 from sklearn.model_selection import train_test_split
     10 import variable_bin_methods as varbin_meth
     11 import variable_encode as var_encode
     12 import matplotlib
     13 import matplotlib.pyplot as plt
     14 # matplotlib.use('Qt5Agg')
     15 matplotlib.rcParams['font.sans-serif'] = ['SimHei']
     16 matplotlib.rcParams['axes.unicode_minus'] = False
     17 from sklearn.linear_model import LogisticRegression
     18 from sklearn.feature_selection import VarianceThreshold
     19 from sklearn.feature_selection import SelectKBest, f_classif
     20 from sklearn.feature_selection import RFECV
     21 from sklearn.svm import SVR
     22 from sklearn.feature_selection import SelectFromModel
     23 import seaborn as sns
     24 from sklearn.tree import DecisionTreeClassifier
     25 from feature_selector import FeatureSelector
     26 import warnings
     27 warnings.filterwarnings("ignore")  # 忽略警告
     28 
     29 
     30 # 数据读取
     31 def data_read(data_path, file_name):
     32     df = pd.read_csv(os.path.join(data_path, file_name), delim_whitespace=True, header=None)
     33     # 变量重命名
     34     columns = [
     35         'status_account', 'duration', 'credit_history', 'purpose', 'amount',
     36         'svaing_account', 'present_emp', 'income_rate', 'personal_status',
     37         'other_debtors', 'residence_info', 'property', 'age', 'inst_plans',
     38         'housing', 'num_credits', 'job', 'dependents', 'telephone',
     39         'foreign_worker', 'target'
     40     ]
     41     df.columns = columns
     42     # 将标签变量由状态1,2转为0,1;0表示好用户,1表示坏用户
     43     df.target = df.target - 1
     44     # 数据分为data_train和 data_test两部分,训练集用于得到编码函数,验证集用已知的编码规则对验证集编码
     45     data_train, data_test = train_test_split(df, test_size=0.2, random_state=0, stratify=df.target)
     46     return data_train, data_test
     47 
     48 
     49 # 离散变量与连续变量区分
     50 def category_continue_separation(df, feature_names):
     51     categorical_var = []
     52     numerical_var = []
     53     if 'target' in feature_names:
     54         feature_names.remove('target')
     55     # 先判断类型,如果是int或float就直接作为连续变量
     56     numerical_var = list(df[feature_names].select_dtypes(
     57         include=['int', 'float', 'int32', 'float32', 'int64', 'float64']).columns.values)
     58     categorical_var = [x for x in feature_names if x not in numerical_var]
     59     return categorical_var, numerical_var
     60 
     61 
     62 if __name__ == '__main__':
     63     path = os.getcwd()
     64     data_path = os.path.join(path, 'data')
     65     file_name = 'german.csv'
     66     # 读取数据
     67     data_train, data_test = data_read(data_path, file_name)
     68 
     69     print("训练集中好样本数 = ", sum(data_train.target == 0))
     70     print("训练集中坏样本数 = ", data_train.target.sum())
     71 
     72     # 区分离散变量与连续变量
     73     feature_names = list(data_train.columns)
     74     feature_names.remove('target')
     75     # 通过判断输入数据的类型来区分连续变量和连续变量
     76     categorical_var, numerical_var = category_continue_separation(data_train, feature_names)
     77 
     78     print("连续变量个数 = ", len(numerical_var))
     79     print("离散变量个数 = ", len(categorical_var))
     80     for s in set(numerical_var):
     81         print('变量 ' + s + ' 可能取值数量 = ' + str(len(data_train[s].unique())))
     82         # 如果连续变量的取值个数 <= 10,那个就把它列入到离散变量中
     83         if len(data_train[s].unique()) <= 10:
     84             categorical_var.append(s)
     85             numerical_var.remove(s)
     86             # 同时将后加的数值变量转为字符串
     87             # 这里返回的是true和false,数据类型是series
     88             index_1 = data_train[s].isnull()
     89             if sum(index_1) > 0:
     90                 data_train.loc[~index_1, s] = data_train.loc[~index_1, s].astype('str')
     91             else:
     92                 data_train[s] = data_train[s].astype('str')
     93             index_2 = data_test[s].isnull()
     94             if sum(index_2) > 0:
     95                 data_test.loc[~index_2, s] = data_test.loc[~index_2, s].astype('str')
     96             else:
     97                 data_test[s] = data_test[s].astype('str')
     98     print("现连续变量个数 = ", len(numerical_var))
     99     print("现离散变量个数 = ", len(categorical_var))
    100 
    101     # 连续变量分箱
    102     dict_cont_bin = {}
    103     for i in numerical_var:
    104         # print(i)
    105         dict_cont_bin[i], gain_value_save, gain_rate_save = varbin_meth.cont_var_bin(
    106                 data_train[i],
    107                 data_train.target,
    108                 method=2,
    109                 mmin=3,
    110                 mmax=12,
    111                 bin_rate=0.01,
    112                 stop_limit=0.05,
    113                 bin_min_num=20)
    114 
    115     # 离散变量分箱
    116     dict_disc_bin = {}
    117     del_key = []
    118     for i in categorical_var:
    119         dict_disc_bin[i], gain_value_save, gain_rate_save, del_key_1 = varbin_meth.disc_var_bin(
    120                 data_train[i],
    121                 data_train.target,
    122                 method=2,
    123                 mmin=3,
    124                 mmax=8,
    125                 stop_limit=0.05,
    126                 bin_min_num=20)
    127         if len(del_key_1) > 0:
    128             del_key.extend(del_key_1)
    129     # 删除分箱数只有1个的变量
    130     if len(del_key) > 0:
    131         for j in del_key:
    132             del dict_disc_bin[j]
    133 
    134     # ---------------------- 训练数据分箱 ------------------- #
    135     # 连续变量分箱映射
    136     df_cont_bin_train = pd.DataFrame()
    137     for i in dict_cont_bin.keys():
    138         df_cont_bin_train = pd.concat([
    139             df_cont_bin_train,
    140             varbin_meth.cont_var_bin_map(data_train[i], dict_cont_bin[i])], axis=1)
    141     # 离散变量分箱映射
    142     df_disc_bin_train = pd.DataFrame()
    143     for i in dict_disc_bin.keys():
    144         df_disc_bin_train = pd.concat([
    145             df_disc_bin_train,
    146             varbin_meth.disc_var_bin_map(data_train[i], dict_disc_bin[i])], axis=1)
    147 
    148     # --------------------- 测试数据分箱 --------------------- #
    149     # 连续变量分箱映射
    150     df_cont_bin_test = pd.DataFrame()
    151     for i in dict_cont_bin.keys():
    152         df_cont_bin_test = pd.concat([
    153             df_cont_bin_test,
    154             varbin_meth.cont_var_bin_map(data_test[i], dict_cont_bin[i])], axis=1)
    155 
    156     # 离散变量分箱映射
    157     df_disc_bin_test = pd.DataFrame()
    158     for i in dict_disc_bin.keys():
    159         df_disc_bin_test = pd.concat([
    160             df_disc_bin_test,
    161             varbin_meth.disc_var_bin_map(data_test[i], dict_disc_bin[i])], axis=1)
    162 
    163     # 组成分箱后的训练集与测试集
    164     df_disc_bin_train['target'] = data_train.target
    165     data_train_bin = pd.concat([df_cont_bin_train, df_disc_bin_train], axis=1)
    166     df_disc_bin_test['target'] = data_test.target
    167     data_test_bin = pd.concat([df_cont_bin_test, df_disc_bin_test], axis=1)
    168 
    169     data_train_bin.reset_index(inplace=True, drop=True)
    170     data_test_bin.reset_index(inplace=True, drop=True)
    171 
    172     # #WOE编码
    173     var_all_bin = list(data_train_bin.columns)
    174     var_all_bin.remove('target')
    175 
    176     # 训练集WOE编码
    177     df_train_woe, dict_woe_map, dict_iv_values, var_woe_name = var_encode.woe_encode(
    178         data_train_bin,
    179         data_path,
    180         var_all_bin,
    181         data_train_bin.target,
    182         'dict_woe_map',
    183         flag='train')
    184 
    185     # 测试集WOE编码
    186     df_test_woe, var_woe_name = var_encode.woe_encode(data_test_bin,
    187                                                       data_path,
    188                                                       var_all_bin,
    189                                                       data_test_bin.target,
    190                                                       'dict_woe_map',
    191                                                       flag='test')
    192     y = np.array(data_train_bin.target)
    193 
    194     # ------------------------ 过滤法特征选择 ---------------------------- #
    195     # --------------- 方差筛选 ----------------- #
    196     # 获取woe编码后的数据
    197     df_train_woe = df_train_woe[var_woe_name]
    198     # 得到进行woe编码后的变量个数
    199     len_1 = df_train_woe.shape[1]
    200     # VarianceThreshold 方差阈值法,用于特征选择,过滤器法的一种,去掉那些方差没有达到阈值的特征。默认情况下,删除零方差的特征
    201     # 实例化一个方差筛选的选择器,阈值设置为0.01
    202     select_var = VarianceThreshold(threshold=0.01)
    203     # 让这个选择器拟合df_train_woe数据
    204     select_var_model = select_var.fit(df_train_woe)
    205     # 将df_train_woe数据缩小为选定的特征
    206     df_1 = pd.DataFrame(select_var_model.transform(df_train_woe))
    207     # 获取所选特征的掩码或整数索引,保留的索引,即可以看到哪些特征被保留
    208     save_index = select_var.get_support(True)
    209     print("保留下来的索引: = ", save_index)
    210 
    211     # 获取保留下来的变量名字
    212     var_columns = [list(df_train_woe.columns)[x] for x in save_index]
    213     df_1.columns = var_columns
    214     # 删除变量的方差
    215     var_delete_variance = select_var.variances_[[x for x in range(len_1) if x not in save_index]]
    216     print("删除变量的方差值 = ", var_delete_variance)
    217     var_delete_variance_columns = list((df_train_woe.columns)[x] for x in range(len_1) if x not in save_index)
    218     print("删除变量的列名 = ", var_delete_variance_columns)
    219 
    220     # ------------------------ 单变量筛选 ------------------------- #
    221     # 参数:SelectKBest(score_func= f_classif, k=10)
    222     # score_func:特征选择要使用的方法,默认适合分类问题的F检验分类:f_classif。 k :取得分最高的前k个特征,默认10个。
    223     # f_calssif计算ANOVA中的f值。方差分析ANOVA F用于分类任务的标签和/特征之间的值
    224     # 当样本xx属于正类时,xixi会取某些特定的值(视作集合S+S+),当样本xx属于负类时,xixi会取另一些特定的值(S−S−)。
    225     # 我们当然希望集合S+S+与S−S−呈现出巨大差异,这样特征xixi对类别的预测能力就越强。落实到刚才的方差分析问题上,就变成了我们需要检验假设H0:μS+=μS−H0:μS+=μS− ,我们当然希望拒绝H0H0,所以我们希望构造出来的ff值越大越好。也就是说ff值越大,我们拒绝H0H0的把握也越大,我们越有理由相信μS+≠μS−μS+≠μS−,越有把握认为集合S+S+与S−S−呈现出巨大差异,也就说xixi这个特征对预测类别的帮助也越大!
    226     # 我们可以根据样本的某个特征xi的f值来判断特征xi对预测类别的帮助,f值越大,预测能力也就越强,相关性就越大,从而基于此可以进行特征选择。
    227     select_uinvar = SelectKBest(score_func=f_classif, k=15)
    228     # 传入特征集df_train_woe和标签y拟合数据
    229     select_uinvar_model = select_uinvar.fit(df_train_woe, y)
    230     # 转换数据,返回特征过滤后保留下的特征数据集
    231     df_1 = select_uinvar_model.transform(df_train_woe)
    232     # 看得分
    233     len_1 = len(select_uinvar_model.scores_)
    234     # 得到原始列名
    235     var_name = [str(x).split('_BIN_woe')[0] for x in list(df_train_woe.columns)]
    236     # 画图
    237     plt.figure(figsize=(10, 6))
    238     fontsize_1 = 14
    239     # barh 函数用于绘制水平条形图
    240     plt.barh(np.arange(0, len_1), select_uinvar_model.scores_, color='c', tick_label=var_name)
    241     plt.xticks(fontsize=fontsize_1)
    242     plt.yticks(fontsize=fontsize_1)
    243     plt.xlabel('得分', fontsize=fontsize_1)
    244     plt.show()
    245 
    246     # ------------------------- 分析变量相关性 ------------------------ #
    247     # 计算相关矩阵。 dataFrame.corr可以返回各类型之间的相关系数DataFrame表格
    248     correlations = abs(df_train_woe.corr())
    249     # 相关性绘图
    250     fig = plt.figure(figsize=(10, 6))
    251     fontsize_1 = 10
    252     # 绘制热力图
    253     sns.heatmap(correlations,
    254                 cmap=plt.cm.Greys,
    255                 linewidths=0.05,
    256                 vmax=1,
    257                 vmin=0,
    258                 annot=True,
    259                 annot_kws={'size': 6, 'weight': 'bold'})
    260     plt.xticks(np.arange(len(var_name)) + 0.5, var_name, fontsize=fontsize_1, rotation=20)
    261     plt.yticks(np.arange(len(var_name)) + 0.5, var_name, fontsize=fontsize_1)
    262     plt.title('相关性分析')
    263     #    plt.xlabel('得分',fontsize=fontsize_1)
    264     plt.show()
    265 
    266     # -------------------- 包装法变量选择:递归消除法 -------------------- #
    267     # 给定学习器, Epsilon-Support Vector Regression.实例化一个SVR估算器
    268     estimator = SVR(kernel="linear")
    269     # 递归消除法, REFCV 具有递归特征消除和交叉验证选择最佳特征数的特征排序。用来挑选特征
    270     # REF(Recursive feature elimination) 就是使用机器学习模型不断的去训练模型,每训练一个模型,就去掉一个最不重要的特征,直到特征达到指定的数量
    271     # sklearn.feature_selection.RFECV(estimator, *, step=1, min_features_to_select=1, cv=None, scoring=None, verbose=0, n_jobs=None)
    272     # estimator: 一种监督学习估计器。
    273     # step: 如果大于或等于1,则step对应于每次迭代要删除的个特征个数。如果在(0.0,1.0)之内,则step对应于每次迭代要删除的特征的百分比(向下舍入)。
    274     # cv: 交叉验证拆分策略
    275     select_rfecv = RFECV(estimator, step=1, cv=3)
    276     # Fit the SVM model according to the given training data.
    277     select_rfecv_model = select_rfecv.fit(df_train_woe, y)
    278     df_1 = pd.DataFrame(select_rfecv_model.transform(df_train_woe))
    279     # 查看结果
    280     # 选定特征的掩码。哪些特征入选最后特征,true表示入选
    281     print("SVR support_ = ", select_rfecv_model.support_)
    282     # 利用交叉验证所选特征的数量。挑选了几个特征
    283     print("SVR n_features_ = ", select_rfecv_model.n_features_)
    284     # 特征排序,使ranking_[i]对应第i个特征的排序位置。选择的(即估计的最佳)特征被排在第1位。
    285     # 每个特征的得分排名,特征得分越低(1最好),表示特征越好
    286     print("SVR ranking = ", select_rfecv_model.ranking_)
    287 
    288     # --------------------- 嵌入法变量选择 -------------------------- #
    289     # 选择学习器
    290     # C    正则化强度 浮点型,默认:1.0;其值等于正则化强度的倒数,为正的浮点数。数值越小表示正则化越强。
    291     # penalty 是正则化类型
    292     lr = LogisticRegression(C=0.1, penalty='l2')
    293     # 嵌入法变量选择
    294     # SelectFromModel(estimator, *, threshold=None, prefit=False, norm_order=1, max_features=None)
    295     # estimator用来构建变压器的基本估算器
    296     # prefit: bool, default False,预设模型是否期望直接传递给构造函数。如果为True,transform必须直接调用和SelectFromModel不能使用cross_val_score,
    297     # GridSearchCV而且克隆估计类似的实用程序。否则,使用训练模型fit,然后transform进行特征选择
    298     # threshold 是用于特征选择的阈值
    299     select_lr = SelectFromModel(lr, prefit=False, threshold='mean')
    300     select_lr_model = select_lr.fit(df_train_woe, y)
    301     df_1 = pd.DataFrame(select_lr_model.transform(df_train_woe))
    302     # 查看结果,.threshold_ 是用于特征选择的阈值
    303     print("逻辑回归 threshold_ = ", select_lr_model.threshold_)
    304     # get_support 获取选出的特征的索引序列或mask
    305     print("逻辑回归 get_support = ", select_lr_model.get_support(True))
    306 
    307     # -------------- 基学习器选择预训练的决策树来进行变量选择 -------------- #
    308     # 先训练决策树
    309     # riterion = gini/entropy 可以用来选择用基尼指数或者熵来做损失函数。
    310     # max_depth = int 用来控制决策树的最大深度,防止模型出现过拟合。
    311     # fit需要训练数据和类别标签
    312     cart_model = DecisionTreeClassifier(criterion='gini', max_depth=3).fit(df_train_woe, y)
    313     # Return the feature importances.
    314     print("决策树 feature_importances_ = ", cart_model.feature_importances_)
    315     # 用预训练模型进行变量选择
    316     select_dt_model = SelectFromModel(cart_model, prefit=True)
    317     df_1 = pd.DataFrame(select_dt_model.transform(df_train_woe))
    318     # 查看结果
    319     print("决策树 get_support(True) = ", select_dt_model.get_support(True))
    
    
  • 相关阅读:
    用UltraISO制作U盘启动盘及设BIOS从U盘启动的方法
    Android 超链接 打开网址
    ActionScript常用正则表达式收集
    Android模拟器安装,使用APK文件
    正则表达式的规则
    WORD的批注
    机械迷城 迷宫全图
    mysql新建用户及授权
    验证IP是否合法
    ThinkPad水货靠谱店
  • 原文地址:https://www.cnblogs.com/ttweixiao-IT-program/p/15457553.html
Copyright © 2020-2023  润新知