写点什么

数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(上篇)[xgboots/lightgbm/Catboost 等模型]-- 模型融合:stacking、blending

作者:汀丶
  • 2023-05-17
    浙江
  • 本文字数:24186 字

    阅读完需:约 79 分钟

数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(上篇)[xgboots/lightgbm/Catboost等模型]--模型融合:stacking、blending

数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(上篇)[xgboots/lightgbm/Catboost 等模型]--模型融合:stacking、blending

1.赛题简介

赛题以金融风控中的个人信贷为背景,要求选手根据贷款申请人的数据信息预测其是否有违约的可能,以此判断是否通过此项贷款,这是一个典型的分类问题。通过这道赛题来引导大家了解金融风控中的一些业务背景,解决实际问题,帮助竞赛新人进行自我练习、自我提高。


赛题以预测金融风险为任务,数据集报名后可见并可下载,该数据来自某信贷平台的贷款记录,总数据量超过 120w,包含 47 列变量信息,其中 15 列为匿名变量。为了保证比赛的公平性,将会从中抽取 80 万条作为训练集,20 万条作为测试集 A,20 万条作为测试集 B,同时会对 employmentTitle、purpose、postCode 和 title 等信息进行脱敏。


比赛地址:https://tianchi.aliyun.com/competition/entrance/531830/introduction

项目链接以及码源见文末

1.1 数据介绍

赛题以预测用户贷款是否违约为任务,数据集报名后可见并可下载,该数据来自某信贷平台的贷款记录,总数据量超过 120w,包含 47 列变量信息,其中 15 列为匿名变量。为了保证比赛的公平性,将会从中抽取 80 万条作为训练集,20 万条作为测试集 A,20 万条作为测试集 B,同时会对 employmentTitle、purpose、postCode 和 title 等信息进行脱敏。


一般而言,对于数据在比赛界面都有对应的数据概况介绍(匿名特征除外),说明列的性质特征。了解列的性质会有助于我们对于数据的理解和后续分析。 Tip:匿名特征,就是未告知数据列所属的性质的特征列。


train.csv


  • id 为贷款清单分配的唯一信用证标识

  • loanAmnt 贷款金额

  • term 贷款期限(year)

  • interestRate 贷款利率

  • installment 分期付款金额

  • grade 贷款等级

  • subGrade 贷款等级之子级

  • employmentTitle 就业职称

  • employmentLength 就业年限(年)

  • homeOwnership 借款人在登记时提供的房屋所有权状况

  • annualIncome 年收入

  • verificationStatus 验证状态

  • issueDate 贷款发放的月份

  • purpose 借款人在贷款申请时的贷款用途类别

  • postCode 借款人在贷款申请中提供的邮政编码的前 3 位数字

  • regionCode 地区编码

  • dti 债务收入比

  • delinquency_2years 借款人过去 2 年信用档案中逾期 30 天以上的违约事件数

  • ficoRangeLow 借款人在贷款发放时的 fico 所属的下限范围

  • ficoRangeHigh 借款人在贷款发放时的 fico 所属的上限范围

  • openAcc 借款人信用档案中未结信用额度的数量

  • pubRec 贬损公共记录的数量

  • pubRecBankruptcies 公开记录清除的数量

  • revolBal 信贷周转余额合计

  • revolUtil 循环额度利用率,或借款人使用的相对于所有可用循环信贷的信贷金额

  • totalAcc 借款人信用档案中当前的信用额度总数

  • initialListStatus 贷款的初始列表状态

  • applicationType 表明贷款是个人申请还是与两个共同借款人的联合申请

  • earliesCreditLine 借款人最早报告的信用额度开立的月份

  • title 借款人提供的贷款名称

  • policyCode 公开可用的策略_代码=1 新产品不公开可用的策略_代码=2

  • n 系列匿名特征 匿名特征 n0-n14,为一些贷款人行为计数特征的处理

1.2 预测指标

竞赛采用 AUC 作为评价指标。AUC(Area Under Curve)被定义为 ROC 曲线 下与坐标轴围成的面积。

1.2.1 分类算法常见的评估指标如下:

1、混淆矩阵(Confuse Matrix)


  • (1)若一个实例是正类,并且被预测为正类,即为真正类 TP(True Positive )

  • (2)若一个实例是正类,但是被预测为负类,即为假负类 FN(False Negative )

  • (3)若一个实例是负类,但是被预测为正类,即为假正类 FP(False Positive )

  • (4)若一个实例是负类,并且被预测为负类,即为真负类 TN(True Negative )


2、准确率(Accuracy)准确率是常用的一个评价指标,但是不适合样本不均衡的情况。


3、精确率(Precision)又称查准率,正确预测为正样本(TP)占预测为正样本(TP+FP)的百分比。


4、召回率(Recall)又称为查全率,正确预测为正样本(TP)占正样本(TP+FN)的百分比。


5、F1 Score 精确率和召回率是相互影响的,精确率升高则召回率下降,召回率升高则精确率下降,如果需要兼顾二者,就需要精确率、召回率的结合 F1 Score。


6、P-R 曲线(Precision-Recall Curve)P-R 曲线是描述精确率和召回率变化的曲线



7、ROC(Receiver Operating Characteristic)


  • ROC 空间将假正例率(FPR)定义为 X 轴,真正例率(TPR)定义为 Y 轴。


TPR:在所有实际为正例的样本中,被正确地判断为正例之比率。FPR:在所有实际为负例的样本中,被错误地判断为正例之比率。



8、AUC(Area Under Curve)AUC(Area Under Curve)被定义为 ROC 曲线 下与坐标轴围成的面积,显然这个面积的数值不会大于 1。又由于 ROC 曲线一般都处于 y=x 这条直线的上方,所以 AUC 的取值范围在 0.5 和 1 之间。AUC 越接近 1.0,检测方法真实性越高;等于 0.5 时,则真实性最低,无应用价值。

1.2.2 对于金融风控预测类常见的评估指标如下:

1、KS(Kolmogorov-Smirnov)KS 统计量由两位苏联数学家 A.N. Kolmogorov 和 N.V. Smirnov 提出。在风控中,KS 常用于评估模型区分度。区分度越大,说明模型的风险排序能力(ranking ability)越强。K-S 曲线与 ROC 曲线类似,不同在于


  • ROC 曲线将真正例率和假正例率作为横纵轴

  • K-S 曲线将真正例率和假正例率都作为纵轴,横轴则由选定的阈值来充当。公式如下:KS 不同代表的不同情况,一般情况 KS 值越大,模型的区分能力越强,但是也不是越大模型效果就越好,如果 KS 过大,模型可能存在异常,所以当 KS 值过高可能需要检查模型是否过拟合。以下为 KS 值对应的模型情况,但此对应不是唯一的,只代表大致趋势。



2、ROC


3、AUC

1.3 项目流程介绍

1.4 代码示例

本部分为对于数据读取和指标评价的示例。

1.4.1 数据读取 pandas

import pandas as pd
复制代码


train = pd.read_csv('train.csv')testA = pd.read_csv('testA.csv')
复制代码


print('Train data shape:',train.shape)print('TestA data shape:',testA.shape)
复制代码


Train data shape: (800000, 47)TestA data shape: (200000, 48)
复制代码


train.head()
复制代码

1.4.2 分类指标评价计算示例

## 混淆矩阵import numpy as npfrom sklearn.metrics import confusion_matrixy_pred = [0, 1, 0, 1]y_true = [0, 1, 1, 0]print('混淆矩阵:\n',confusion_matrix(y_true, y_pred))
复制代码


混淆矩阵: [[1 1] [1 1]]
复制代码


## accuracyfrom sklearn.metrics import accuracy_scorey_pred = [0, 1, 0, 1]y_true = [0, 1, 1, 0]print('ACC:',accuracy_score(y_true, y_pred))
复制代码


ACC: 0.5
复制代码


## Precision,Recall,F1-scorefrom sklearn import metricsy_pred = [0, 1, 0, 1]y_true = [0, 1, 1, 0]print('Precision',metrics.precision_score(y_true, y_pred))print('Recall',metrics.recall_score(y_true, y_pred))print('F1-score:',metrics.f1_score(y_true, y_pred))
复制代码


Precision 0.5Recall 0.5F1-score: 0.5
复制代码


## P-R曲线import matplotlib.pyplot as pltfrom sklearn.metrics import precision_recall_curvey_pred = [0, 1, 1, 0, 1, 1, 0, 1, 1, 1]y_true = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]precision, recall, thresholds = precision_recall_curve(y_true, y_pred)plt.plot(precision, recall)
复制代码


[<matplotlib.lines.Line2D at 0x2170d0d6108>]
复制代码



## ROC曲线from sklearn.metrics import roc_curvey_pred = [0, 1, 1, 0, 1, 1, 0, 1, 1, 1]y_true = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]FPR,TPR,thresholds=roc_curve(y_true, y_pred)plt.title('ROC')plt.plot(FPR, TPR,'b')plt.plot([0,1],[0,1],'r--')plt.ylabel('TPR')plt.xlabel('FPR')
复制代码


Text(0.5, 0, 'FPR')
复制代码



## AUCimport numpy as npfrom sklearn.metrics import roc_auc_scorey_true = np.array([0, 0, 1, 1])y_scores = np.array([0.1, 0.4, 0.35, 0.8])print('AUC socre:',roc_auc_score(y_true, y_scores))
复制代码


AUC socre: 0.75
复制代码


## KS值 在实际操作时往往使用ROC曲线配合求出KS值from sklearn.metrics import roc_curvey_pred = [0, 1, 1, 0, 1, 1, 0, 1, 1, 1]y_true = [0, 1, 1, 0, 1, 0, 1, 1, 1, 1]FPR,TPR,thresholds=roc_curve(y_true, y_pred)KS=abs(FPR-TPR).max()print('KS值:',KS)
复制代码


KS值: 0.5238095238095237
复制代码

1.5 拓展知识——评分卡

评分卡是一张拥有分数刻度会让相应阈值的表。信用评分卡是用于用户信用的一张刻度表。以下代码是一个非标准评分卡的代码流程,用于刻画用户的信用评分。评分卡是金融风控中常用的一种对于用户信用进行刻画的手段哦!


#评分卡 不是标准评分卡def Score(prob,P0=600,PDO=20,badrate=None,goodrate=None):    P0 = P0    PDO = PDO    theta0 = badrate/goodrate    B = PDO/np.log(2)    A = P0 + B*np.log(2*theta0)    score = A-B*np.log(prob/(1-prob))    return score
复制代码

2.数据探索性分析

目的:


  • 1.EDA 价值主要在于熟悉了解整个数据集的基本情况(缺失值,异常值),对数据集进行验证是否可以进行接下来的机器学习或者深度学习建模.

  • 2.了解变量间的相互关系、变量与预测值之间的存在关系。

  • 3.为特征工程做准备

2.1 代码示例

2.1.1 导入数据分析及可视化过程需要的库

import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsimport datetimeimport warningswarnings.filterwarnings('ignore')
复制代码


/Users/exudingtao/opt/anaconda3/lib/python3.7/site-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.  import pandas.util.testing as tm
复制代码


以上库都是 pip install 安装就好,如果本机有 python2,python3 两个 python 环境傻傻分不清哪个的话,可以 pip3 install 。或者直接在 notebook 中'!pip3 install ****'安装。

2.1.2 读取文件

data_train = pd.read_csv('./train.csv')
复制代码


data_test_a = pd.read_csv('./testA.csv')
复制代码


  • 读取文件的拓展知识


  • pandas 读取数据时相对路径载入报错时,尝试使用 os.getcwd()查看当前工作目录。

  • TSV 与 CSV 的区别:

  • 从名称上即可知道,TSV 是用制表符(Tab,'\t')作为字段值的分隔符;CSV 是用半角逗号(',')作为字段值的分隔符;

  • Python 对 TSV 文件的支持:Python 的 csv 模块准确的讲应该叫做 dsv 模块,因为它实际上是支持范式的分隔符分隔值文件(DSV,delimiter-separated values)的。delimiter 参数值默认为半角逗号,即默认将被处理文件视为 CSV。当 delimiter='\t'时,被处理文件就是 TSV。

  • 读取文件的部分(适用于文件特别大的场景)

  • 通过 nrows 参数,来设置读取文件的前多少行,nrows 是一个大于等于 0 的整数。

  • 分块读取


data_train_sample = pd.read_csv("./train.csv",nrows=5)
复制代码


#设置chunksize参数,来控制每次迭代数据的大小chunker = pd.read_csv("./train.csv",chunksize=5)for item in chunker:    print(type(item))    #<class 'pandas.core.frame.DataFrame'>    print(len(item))    #5
复制代码

查看数据集的样本个数和原始特征维度

data_test_a.shape
复制代码


(200000, 48)
复制代码


data_train.shape
复制代码


(800000, 47)
复制代码


data_train.columns
复制代码


Index(['id', 'loanAmnt', 'term', 'interestRate', 'installment', 'grade',       'subGrade', 'employmentTitle', 'employmentLength', 'homeOwnership',       'annualIncome', 'verificationStatus', 'issueDate', 'isDefault',       'purpose', 'postCode', 'regionCode', 'dti', 'delinquency_2years',       'ficoRangeLow', 'ficoRangeHigh', 'openAcc', 'pubRec',       'pubRecBankruptcies', 'revolBal', 'revolUtil', 'totalAcc',       'initialListStatus', 'applicationType', 'earliesCreditLine', 'title',       'policyCode', 'n0', 'n1', 'n2', 'n2.1', 'n4', 'n5', 'n6', 'n7', 'n8',       'n9', 'n10', 'n11', 'n12', 'n13', 'n14'],      dtype='object')
复制代码


查看一下具体的列名,赛题理解部分已经给出具体的特征含义,这里方便阅读再给一下:


  • id 为贷款清单分配的唯一信用证标识

  • loanAmnt 贷款金额

  • term 贷款期限(year)

  • interestRate 贷款利率

  • installment 分期付款金额

  • grade 贷款等级

  • subGrade 贷款等级之子级

  • employmentTitle 就业职称

  • employmentLength 就业年限(年)

  • homeOwnership 借款人在登记时提供的房屋所有权状况

  • annualIncome 年收入

  • verificationStatus 验证状态

  • issueDate 贷款发放的月份

  • purpose 借款人在贷款申请时的贷款用途类别

  • postCode 借款人在贷款申请中提供的邮政编码的前 3 位数字

  • regionCode 地区编码

  • dti 债务收入比

  • delinquency_2years 借款人过去 2 年信用档案中逾期 30 天以上的违约事件数

  • ficoRangeLow 借款人在贷款发放时的 fico 所属的下限范围

  • ficoRangeHigh 借款人在贷款发放时的 fico 所属的上限范围

  • openAcc 借款人信用档案中未结信用额度的数量

  • pubRec 贬损公共记录的数量

  • pubRecBankruptcies 公开记录清除的数量

  • revolBal 信贷周转余额合计

  • revolUtil 循环额度利用率,或借款人使用的相对于所有可用循环信贷的信贷金额

  • totalAcc 借款人信用档案中当前的信用额度总数

  • initialListStatus 贷款的初始列表状态

  • applicationType 表明贷款是个人申请还是与两个共同借款人的联合申请

  • earliesCreditLine 借款人最早报告的信用额度开立的月份

  • title 借款人提供的贷款名称

  • policyCode 公开可用的策略_代码=1 新产品不公开可用的策略_代码=2

  • n 系列匿名特征 匿名特征 n0-n14,为一些贷款人行为计数特征的处理


通过 info()来熟悉数据类型


data_train.info()
复制代码


<class 'pandas.core.frame.DataFrame'>RangeIndex: 800000 entries, 0 to 799999Data columns (total 47 columns): #   Column              Non-Null Count   Dtype  ---  ------              --------------   -----   0   id                  800000 non-null  int64   1   loanAmnt            800000 non-null  float64 2   term                800000 non-null  int64   3   interestRate        800000 non-null  float64 4   installment         800000 non-null  float64 5   grade               800000 non-null  object  6   subGrade            800000 non-null  object  7   employmentTitle     799999 non-null  float64 8   employmentLength    753201 non-null  object  9   homeOwnership       800000 non-null  int64   10  annualIncome        800000 non-null  float64 11  verificationStatus  800000 non-null  int64   12  issueDate           800000 non-null  object  13  isDefault           800000 non-null  int64   14  purpose             800000 non-null  int64   15  postCode            799999 non-null  float64 16  regionCode          800000 non-null  int64   17  dti                 799761 non-null  float64 18  delinquency_2years  800000 non-null  float64 19  ficoRangeLow        800000 non-null  float64 20  ficoRangeHigh       800000 non-null  float64 21  openAcc             800000 non-null  float64 22  pubRec              800000 non-null  float64 23  pubRecBankruptcies  799595 non-null  float64 24  revolBal            800000 non-null  float64 25  revolUtil           799469 non-null  float64 26  totalAcc            800000 non-null  float64 27  initialListStatus   800000 non-null  int64   28  applicationType     800000 non-null  int64   29  earliesCreditLine   800000 non-null  object  30  title               799999 non-null  float64 31  policyCode          800000 non-null  float64 32  n0                  759730 non-null  float64 33  n1                  759730 non-null  float64 34  n2                  759730 non-null  float64 35  n2.1                759730 non-null  float64 36  n4                  766761 non-null  float64 37  n5                  759730 non-null  float64 38  n6                  759730 non-null  float64 39  n7                  759730 non-null  float64 40  n8                  759729 non-null  float64 41  n9                  759730 non-null  float64 42  n10                 766761 non-null  float64 43  n11                 730248 non-null  float64 44  n12                 759730 non-null  float64 45  n13                 759730 non-null  float64 46  n14                 759730 non-null  float64dtypes: float64(33), int64(9), object(5)memory usage: 286.9+ MB
复制代码


总体粗略的查看数据集各个特征的一些基本统计量


data_train.describe()
复制代码


data_train.head(3).append(data_train.tail(3))
复制代码

2.1.3 查看数据集中特征缺失值,唯一值等

查看缺失值


print(f'There are {data_train.isnull().any().sum()} columns in train dataset with missing values.')
复制代码


There are 22 columns in train dataset with missing values.
复制代码


上面得到训练集有 22 列特征有缺失值,进一步查看缺失特征中缺失率大于 50%的特征


have_null_fea_dict = (data_train.isnull().sum()/len(data_train)).to_dict()fea_null_moreThanHalf = {}for key,value in have_null_fea_dict.items():    if value > 0.5:        fea_null_moreThanHalf[key] = value
复制代码


fea_null_moreThanHalf
复制代码


{}
复制代码


具体的查看缺失特征及缺失率


#nan可视化missing = data_train.isnull().sum()/len(data_train)missing = missing[missing > 0]missing.sort_values(inplace=True)missing.plot.bar()
复制代码


<matplotlib.axes._subplots.AxesSubplot at 0x1229ab890>
复制代码



  • 纵向了解哪些列存在 “nan”, 并可以把 nan 的个数打印,主要的目的在于查看某一列 nan 存在的个数是否真的很大,如果 nan 存在的过多,说明这一列对 label 的影响几乎不起作用了,可以考虑删掉。如果缺失值很小一般可以选择填充。

  • 另外可以横向比较,如果在数据集中,某些样本数据的大部分列都是缺失的且样本足够的情况下可以考虑删除。


Tips:比赛大杀器 lgb 模型可以自动处理缺失值,Task4 模型会具体学习模型了解模型哦!


查看训练集测试集中特征属性只有一值的特征


one_value_fea = [col for col in data_train.columns if data_train[col].nunique() <= 1]
复制代码


one_value_fea_test = [col for col in data_test_a.columns if data_test_a[col].nunique() <= 1]
复制代码


one_value_fea
复制代码


['policyCode']
复制代码


one_value_fea_test
复制代码


['policyCode']
复制代码


print(f'There are {len(one_value_fea)} columns in train dataset with one unique value.')print(f'There are {len(one_value_fea_test)} columns in test dataset with one unique value.')
复制代码


There are 1 columns in train dataset with one unique value.There are 1 columns in test dataset with one unique value.
复制代码


47 列数据中有 22 列都缺少数据,这在现实世界中很正常。‘policyCode’具有一个唯一值(或全部缺失)。有很多连续变量和一些分类变量。

2.1.4 查看特征的数值类型有哪些,对象类型有哪些

  • 特征一般都是由类别型特征和数值型特征组成,而数值型特征又分为连续型和离散型。

  • 类别型特征有时具有非数值关系,有时也具有数值关系。比如‘grade’中的等级 A,B,C 等,是否只是单纯的分类,还是 A 优于其他要结合业务判断。

  • 数值型特征本是可以直接入模的,但往往风控人员要对其做分箱,转化为 WOE 编码进而做标准评分卡等操作。从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量和因变量的相关度。从而使模型更加稳定。


numerical_fea = list(data_train.select_dtypes(exclude=['object']).columns)category_fea = list(filter(lambda x: x not in numerical_fea,list(data_train.columns)))
复制代码


numerical_fea
复制代码


category_fea
复制代码


['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
复制代码


data_train.grade
复制代码


0         E1         D2         D3         A4         C         ..799995    C799996    A799997    C799998    A799999    BName: grade, Length: 800000, dtype: object
复制代码


  • 划分数值型变量中的连续变量和离散型变量


#过滤数值型类别特征def get_numerical_serial_fea(data,feas):    numerical_serial_fea = []    numerical_noserial_fea = []    for fea in feas:        temp = data[fea].nunique()        if temp <= 10:            numerical_noserial_fea.append(fea)            continue        numerical_serial_fea.append(fea)    return numerical_serial_fea,numerical_noserial_feanumerical_serial_fea,numerical_noserial_fea = get_numerical_serial_fea(data_train,numerical_fea)
复制代码


numerical_serial_fea
复制代码


numerical_noserial_fea
复制代码


['term', 'homeOwnership', 'verificationStatus', 'isDefault', 'initialListStatus', 'applicationType', 'policyCode', 'n11', 'n12']
复制代码


  • 数值类别型变量分析


data_train['term'].value_counts()#离散型变量
复制代码


3    6069025    193098Name: term, dtype: int64
复制代码


data_train['homeOwnership'].value_counts()#离散型变量
复制代码


0    3957321    3176602     863093       1855        814        33Name: homeOwnership, dtype: int64
复制代码


data_train['verificationStatus'].value_counts()#离散型变量
复制代码


1    3098102    2489680    241222Name: verificationStatus, dtype: int64
复制代码


data_train['initialListStatus'].value_counts()#离散型变量
复制代码


0    4664381    333562Name: initialListStatus, dtype: int64
复制代码


data_train['applicationType'].value_counts()#离散型变量
复制代码


0    7845861     15414Name: applicationType, dtype: int64
复制代码


data_train['policyCode'].value_counts()#离散型变量,无用,全部一个值
复制代码


1.0    800000Name: policyCode, dtype: int64
复制代码


data_train['n11'].value_counts()#离散型变量,相差悬殊,用不用再分析
复制代码


0.0    7296821.0       5402.0        244.0         13.0         1Name: n11, dtype: int64
复制代码


data_train['n12'].value_counts()#离散型变量,相差悬殊,用不用再分析
复制代码


0.0    7573151.0      22812.0       1153.0        164.0         3Name: n12, dtype: int64
复制代码


  • 数值连续型变量分析


#每个数字特征得分布可视化f = pd.melt(data_train, value_vars=numerical_serial_fea)g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False)g = g.map(sns.distplot, "value")
复制代码



  • 查看某一个数值型变量的分布,查看变量是否符合正态分布,如果不符合正太分布的变量可以 log 化后再观察下是否符合正态分布。

  • 如果想统一处理一批数据变标准化 必须把这些之前已经正态化的数据提出

  • 正态化的原因:一些情况下正态非正态可以让模型更快的收敛,一些模型要求数据正态(eg. GMM、KNN),保证数据不要过偏态即可,过于偏态可能会影响模型预测结果。


#Ploting Transaction Amount Values Distributionplt.figure(figsize=(16,12))plt.suptitle('Transaction Values Distribution', fontsize=22)plt.subplot(221)sub_plot_1 = sns.distplot(data_train['loanAmnt'])sub_plot_1.set_title("loanAmnt Distribuition", fontsize=18)sub_plot_1.set_xlabel("")sub_plot_1.set_ylabel("Probability", fontsize=15)
plt.subplot(222)sub_plot_2 = sns.distplot(np.log(data_train['loanAmnt']))sub_plot_2.set_title("loanAmnt (Log) Distribuition", fontsize=18)sub_plot_2.set_xlabel("")sub_plot_2.set_ylabel("Probability", fontsize=15)
复制代码


Text(0, 0.5, 'Probability')
复制代码



  • 非数值类别型变量分析


category_fea
复制代码


['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
复制代码


data_train['grade'].value_counts()
复制代码


B    233690C    227118A    139661D    119453E     55661F     19053G      5364Name: grade, dtype: int64
复制代码


data_train['subGrade'].value_counts()
复制代码


C1    50763B4    49516B5    48965B3    48600C2    47068C3    44751C4    44272B2    44227B1    42382C5    40264A5    38045A4    30928D1    30538D2    26528A1    25909D3    23410A3    22655A2    22124D4    21139D5    17838E1    14064E2    12746E3    10925E4     9273E5     8653F1     5925F2     4340F3     3577F4     2859F5     2352G1     1759G2     1231G3      978G4      751G5      645Name: subGrade, dtype: int64
复制代码


data_train['employmentLength'].value_counts()
复制代码


10+ years    2627532 years       72358< 1 year      642373 years       641521 year        524895 years       501024 years       479856 years       372548 years       361927 years       354079 years       30272Name: employmentLength, dtype: int64
复制代码


data_train['issueDate'].value_counts()
复制代码


2016-03-01    290662015-10-01    255252015-07-01    244962015-12-01    232452014-10-01    21461              ...  2007-08-01       232007-07-01       212008-09-01       192007-09-01        72007-06-01        1Name: issueDate, Length: 139, dtype: int64
复制代码


data_train['earliesCreditLine'].value_counts()
复制代码


Aug-2001    5567Sep-2003    5403Aug-2002    5403Oct-2001    5258Aug-2000    5246            ... May-1960       1Apr-1958       1Feb-1960       1Aug-1946       1Mar-1958       1Name: earliesCreditLine, Length: 720, dtype: int64
复制代码


data_train['isDefault'].value_counts()
复制代码


0    6403901    159610Name: isDefault, dtype: int64
复制代码

2.1.4 小结:

  • 上面我们用 value_counts()等函数看了特征属性的分布,但是图表是概括原始信息最便捷的方式。

  • 数无形时少直觉。

  • 同一份数据集,在不同的尺度刻画上显示出来的图形反映的规律是不一样的。python 将数据转化成图表,但结论是否正确需要由你保证。

2.2 变量分布可视化

2.2.1 单一变量分布可视化

plt.figure(figsize=(8, 8))sns.barplot(data_train["employmentLength"].value_counts(dropna=False)[:20],            data_train["employmentLength"].value_counts(dropna=False).keys()[:20])plt.show()
复制代码


2.2.2 根绝 y 值不同可视化 x 某个特征的分布

  • 首先查看类别型变量在不同 y 值上的分布


train_loan_fr = data_train.loc[data_train['isDefault'] == 1]train_loan_nofr = data_train.loc[data_train['isDefault'] == 0]
复制代码


fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 8))train_loan_fr.groupby('grade')['grade'].count().plot(kind='barh', ax=ax1, title='Count of grade fraud')train_loan_nofr.groupby('grade')['grade'].count().plot(kind='barh', ax=ax2, title='Count of grade non-fraud')train_loan_fr.groupby('employmentLength')['employmentLength'].count().plot(kind='barh', ax=ax3, title='Count of employmentLength fraud')train_loan_nofr.groupby('employmentLength')['employmentLength'].count().plot(kind='barh', ax=ax4, title='Count of employmentLength non-fraud')plt.show()
复制代码



  • 其次查看连续型变量在不同 y 值上的分布


fig, ((ax1, ax2)) = plt.subplots(1, 2, figsize=(15, 6))data_train.loc[data_train['isDefault'] == 1] \    ['loanAmnt'].apply(np.log) \    .plot(kind='hist',          bins=100,          title='Log Loan Amt - Fraud',          color='r',          xlim=(-3, 10),         ax= ax1)data_train.loc[data_train['isDefault'] == 0] \    ['loanAmnt'].apply(np.log) \    .plot(kind='hist',          bins=100,          title='Log Loan Amt - Not Fraud',          color='b',          xlim=(-3, 10),         ax=ax2)
复制代码


<matplotlib.axes._subplots.AxesSubplot at 0x126a44b50>
复制代码



total = len(data_train)total_amt = data_train.groupby(['isDefault'])['loanAmnt'].sum().sum()plt.figure(figsize=(12,5))plt.subplot(121)##1代表行,2代表列,所以一共有2个图,1代表此时绘制第一个图。plot_tr = sns.countplot(x='isDefault',data=data_train)#data_train‘isDefault’这个特征每种类别的数量**plot_tr.set_title("Fraud Loan Distribution \n 0: good user | 1: bad user", fontsize=14)plot_tr.set_xlabel("Is fraud by count", fontsize=16)plot_tr.set_ylabel('Count', fontsize=16)for p in plot_tr.patches:    height = p.get_height()    plot_tr.text(p.get_x()+p.get_width()/2.,            height + 3,            '{:1.2f}%'.format(height/total*100),            ha="center", fontsize=15)     percent_amt = (data_train.groupby(['isDefault'])['loanAmnt'].sum())percent_amt = percent_amt.reset_index()plt.subplot(122)plot_tr_2 = sns.barplot(x='isDefault', y='loanAmnt',  dodge=True, data=percent_amt)plot_tr_2.set_title("Total Amount in loanAmnt  \n 0: good user | 1: bad user", fontsize=14)plot_tr_2.set_xlabel("Is fraud by percent", fontsize=16)plot_tr_2.set_ylabel('Total Loan Amount Scalar', fontsize=16)for p in plot_tr_2.patches:    height = p.get_height()    plot_tr_2.text(p.get_x()+p.get_width()/2.,            height + 3,            '{:1.2f}%'.format(height/total_amt * 100),            ha="center", fontsize=15)     
复制代码


2.2.3 时间格式数据处理及查看

#转化成时间格式  issueDateDT特征表示数据日期离数据集中日期最早的日期(2007-06-01)的天数data_train['issueDate'] = pd.to_datetime(data_train['issueDate'],format='%Y-%m-%d')startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')data_train['issueDateDT'] = data_train['issueDate'].apply(lambda x: x-startdate).dt.days
复制代码


#转化成时间格式data_test_a['issueDate'] = pd.to_datetime(data_train['issueDate'],format='%Y-%m-%d')startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')data_test_a['issueDateDT'] = data_test_a['issueDate'].apply(lambda x: x-startdate).dt.days
复制代码


plt.hist(data_train['issueDateDT'], label='train');plt.hist(data_test_a['issueDateDT'], label='test');plt.legend();plt.title('Distribution of issueDateDT dates');#train 和 test issueDateDT 日期有重叠 所以使用基于时间的分割进行验证是不明智的
复制代码


2.3.4 掌握透视图可以让我们更好的了解数据

#透视图 索引可以有多个,“columns(列)”是可选的,聚合函数aggfunc最后是被应用到了变量“values”中你所列举的项目上。pivot = pd.pivot_table(data_train, index=['grade'], columns=['issueDateDT'], values=['loanAmnt'], aggfunc=np.sum)
复制代码


pivot
复制代码

2.3. 用 pandas_profiling 生成数据报告

import pandas_profiling
复制代码


pfr = pandas_profiling.ProfileReport(data_train)pfr.to_file("./example.html")
复制代码

2.4 总结

数据探索性分析是我们初步了解数据,熟悉数据为特征工程做准备的阶段,甚至很多时候 EDA 阶段提取出来的特征可以直接当作规则来用。可见 EDA 的重要性,这个阶段的主要工作还是借助于各个简单的统计量来对数据整体的了解,分析各个类型变量相互之间的关系,以及用合适的图形可视化出来直观观察。希望本节内容能给初学者带来帮助,更期待各位学习者对其中的不足提出建议。

3.特征工程

import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsimport datetimefrom tqdm import tqdmfrom sklearn.preprocessing import LabelEncoderfrom sklearn.feature_selection import SelectKBestfrom sklearn.feature_selection import chi2from sklearn.preprocessing import MinMaxScalerimport xgboost as xgbimport lightgbm as lgbfrom catboost import CatBoostRegressorimport warningsfrom sklearn.model_selection import StratifiedKFold, KFoldfrom sklearn.metrics import accuracy_score, f1_score, roc_auc_score, log_losswarnings.filterwarnings('ignore')
复制代码


data_train =pd.read_csv('../train.csv')data_test_a = pd.read_csv('../testA.csv')
复制代码

3.1 特征预处理

  • 数据 EDA 部分我们已经对数据的大概和某些特征分布有了了解,数据预处理部分一般我们要处理一些 EDA 阶段分析出来的问题,这里介绍了数据缺失值的填充,时间格式特征的转化处理,某些对象类别特征的处理。


首先我们查找出数据中的对象特征和数值特征


numerical_fea = list(data_train.select_dtypes(exclude=['object']).columns)category_fea = list(filter(lambda x: x not in numerical_fea,list(data_train.columns)))label = 'isDefault'numerical_fea.remove(label)
复制代码


在比赛中数据预处理是必不可少的一部分,对于缺失值的填充往往会影响比赛的结果,在比赛中不妨尝试多种填充然后比较结果选择结果最优的一种;比赛数据相比真实场景的数据相对要“干净”一些,但是还是会有一定的“脏”数据存在,清洗一些异常值往往会获得意想不到的效果。


  • 把所有缺失值替换为指定的值 0

  • data_train = data_train.fillna(0)

  • 向用缺失值上面的值替换缺失值

  • data_train = data_train.fillna(axis=0,method='ffill')

  • 纵向用缺失值下面的值替换缺失值,且设置最多只填充两个连续的缺失值

  • data_train = data_train.fillna(axis=0,method='bfill',limit=2)


#查看缺失值情况data_train.isnull().sum()
复制代码


#按照平均数填充数值型特征data_train[numerical_fea] = data_train[numerical_fea].fillna(data_train[numerical_fea].median())data_test_a[numerical_fea] = data_test_a[numerical_fea].fillna(data_train[numerical_fea].median())#按照众数填充类别型特征data_train[category_fea] = data_train[category_fea].fillna(data_train[category_fea].mode())data_test_a[category_fea] = data_test_a[category_fea].fillna(data_train[category_fea].mode())
复制代码


data_train.isnull().sum()
复制代码


#查看类别特征category_fea
复制代码


['grade', 'subGrade', 'employmentLength', 'issueDate', 'earliesCreditLine']
复制代码


  • category_fea:对象型类别特征需要进行预处理,其中['issueDate']为时间格式特征。


#转化成时间格式for data in [data_train, data_test_a]:    data['issueDate'] = pd.to_datetime(data['issueDate'],format='%Y-%m-%d')    startdate = datetime.datetime.strptime('2007-06-01', '%Y-%m-%d')    #构造时间特征    data['issueDateDT'] = data['issueDate'].apply(lambda x: x-startdate).dt.days
复制代码


data_train['employmentLength'].value_counts(dropna=False).sort_index()
复制代码


1 year        5248910+ years    2627532 years       723583 years       641524 years       479855 years       501026 years       372547 years       354078 years       361929 years       30272< 1 year      64237NaN           46799Name: employmentLength, dtype: int64
复制代码


def employmentLength_to_int(s):    if pd.isnull(s):        return s    else:        return np.int8(s.split()[0])for data in [data_train, data_test_a]:    data['employmentLength'].replace(to_replace='10+ years', value='10 years', inplace=True)    data['employmentLength'].replace('< 1 year', '0 years', inplace=True)    data['employmentLength'] = data['employmentLength'].apply(employmentLength_to_int)
复制代码


data['employmentLength'].value_counts(dropna=False).sort_index()
复制代码


0.0     159891.0     131822.0     182073.0     160114.0     118335.0     125436.0      93287.0      88238.0      89769.0      759410.0    65772NaN     11742Name: employmentLength, dtype: int64
复制代码


  • 对 earliesCreditLine 进行预处理


data_train['earliesCreditLine'].sample(5)
复制代码


519915    Sep-2002564368    Dec-1996768209    May-2004453092    Nov-1995763866    Sep-2000Name: earliesCreditLine, dtype: object
复制代码


for data in [data_train, data_test_a]:    data['earliesCreditLine'] = data['earliesCreditLine'].apply(lambda s: int(s[-4:]))
复制代码


# 部分类别特征cate_features = ['grade', 'subGrade', 'employmentTitle', 'homeOwnership', 'verificationStatus', 'purpose', 'postCode', 'regionCode', \                 'applicationType', 'initialListStatus', 'title', 'policyCode']for f in cate_features:    print(f, '类型数:', data[f].nunique())
复制代码


grade 类型数: 7subGrade 类型数: 35employmentTitle 类型数: 79282homeOwnership 类型数: 6verificationStatus 类型数: 3purpose 类型数: 14postCode 类型数: 889regionCode 类型数: 51applicationType 类型数: 2initialListStatus 类型数: 2title 类型数: 12058policyCode 类型数: 1
复制代码


像等级这种类别特征,是有优先级的可以 labelencode 或者自映射


for data in [data_train, data_test_a]:    data['grade'] = data['grade'].map({'A':1,'B':2,'C':3,'D':4,'E':5,'F':6,'G':7})
复制代码


# 类型数在2之上,又不是高维稀疏的,且纯分类特征for data in [data_train, data_test_a]:    data = pd.get_dummies(data, columns=['subGrade', 'homeOwnership', 'verificationStatus', 'purpose', 'regionCode'], drop_first=True)
复制代码

3.2 异常值处理

  • 当你发现异常值后,一定要先分清是什么原因导致的异常值,然后再考虑如何处理。首先,如果这一异常值并不代表一种规律性的,而是极其偶然的现象,或者说你并不想研究这种偶然的现象,这时可以将其删除。其次,如果异常值存在且代表了一种真实存在的现象,那就不能随便删除。在现有的欺诈场景中很多时候欺诈数据本身相对于正常数据勒说就是异常的,我们要把这些异常点纳入,重新拟合模型,研究其规律。能用监督的用监督模型,不能用的还可以考虑用异常检测的算法来做。

  • 注意 test 的数据不能删。

3.2.1 检测异常的方法一:均方差

在统计学中,如果一个数据分布近似正态,那么大约 68% 的数据值会在均值的一个标准差范围内,大约 95% 会在两个标准差范围内,大约 99.7% 会在三个标准差范围内。


def find_outliers_by_3segama(data,fea):    data_std = np.std(data[fea])    data_mean = np.mean(data[fea])    outliers_cut_off = data_std * 3    lower_rule = data_mean - outliers_cut_off    upper_rule = data_mean + outliers_cut_off    data[fea+'_outliers'] = data[fea].apply(lambda x:str('异常值') if x > upper_rule or x < lower_rule else '正常值')    return data
复制代码


  • 得到特征的异常值后可以进一步分析变量异常值和目标变量的关系


data_train = data_train.copy()for fea in numerical_fea:    data_train = find_outliers_by_3segama(data_train,fea)    print(data_train[fea+'_outliers'].value_counts())    print(data_train.groupby(fea+'_outliers')['isDefault'].sum())    print('*'*10)
复制代码


  • 例如可以看到异常值在两个变量上的分布几乎复合整体的分布,如果异常值都属于为 1 的用户数据里面代表什么呢?


#删除异常值for fea in numerical_fea:    data_train = data_train[data_train[fea+'_outliers']=='正常值']    data_train = data_train.reset_index(drop=True) 
复制代码

3.2.1 检测异常的方法二:箱型图

  • 总结一句话:四分位数会将数据分为三个点和四个区间,IQR = Q3 -Q1,下触须=Q1 − 1.5x IQR,上触须=Q3 + 1.5x IQR;

3.3 数据分桶

  • 特征分箱的目的:

  • 从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量和因变量的相关度。从而使模型更加稳定。

  • 数据分桶的对象:

  • 将连续变量离散化

  • 将多状态的离散变量合并成少状态

  • 分箱的原因:

  • 数据的特征内的值跨度可能比较大,对有监督和无监督中如 k-均值聚类它使用欧氏距离作为相似度函数来测量数据点之间的相似度。都会造成大吃小的影响,其中一种解决方法是对计数值进行区间量化即数据分桶也叫做数据分箱,然后使用量化后的结果。

  • 分箱的优点:

  • 处理缺失值:当数据源可能存在缺失值,此时可以把 null 单独作为一个分箱。

  • 处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰能力)。例如,age 若出现 200 这种异常值,可分入“age > 60”这个分箱里,排除影响。

  • 业务解释性:我们习惯于线性判断变量的作用,当 x 越来越大,y 就越来越大。但实际 x 与 y 之间经常存在着非线性关系,此时可经过 WOE 变换。

  • 特别要注意一下分箱的基本原则:

  • (1)最小分箱占比不低于 5%

  • (2)箱内不能全部是好客户

  • (3)连续箱单调


  1. 固定宽度分箱


当数值横跨多个数量级时,最好按照 10 的幂(或任何常数的幂)来进行分组:09、1099、100999、10009999,等等。固定宽度分箱非常容易计算,但如果计数值中有比较大的缺口,就会产生很多没有任何数据的空箱子。


#通过除法映射到间隔均匀的分箱中,每个分箱的取值范围都是loanAmnt/1000data['loanAmnt_bin1'] = np.floor_divide(data['loanAmnt'], 1000)
复制代码


##通过对数函数映射到指数宽度分箱data['loanAmnt_bin2'] = np.floor(np.log10(data['loanAmnt']))
复制代码


  1. 分位数分箱


data['loanAmnt_bin3'] = pd.qcut(data['loanAmnt'], 10, labels=False)
复制代码


  1. 卡方分箱及其他分箱方法的尝试


  • 这一部分属于进阶部分,学有余力的同学可以自行搜索尝试。

3.4 特征交互

  • 交互特征的构造非常简单,使用起来却代价不菲。如果线性模型中包含有交互特征对,那它的训练时间和评分时间就会从 O(n) 增加到 O(n2),其中 n 是单一特征的数量。


for col in ['grade', 'subGrade']:     temp_dict = data_train.groupby([col])['isDefault'].agg(['mean']).reset_index().rename(columns={'mean': col + '_target_mean'})    temp_dict.index = temp_dict[col].values    temp_dict = temp_dict[col + '_target_mean'].to_dict()
data_train[col + '_target_mean'] = data_train[col].map(temp_dict) data_test_a[col + '_target_mean'] = data_test_a[col].map(temp_dict)
复制代码


# 其他衍生变量 mean 和 stdfor df in [data_train, data_test_a]:    for item in ['n0','n1','n2','n2.1','n4','n5','n6','n7','n8','n9','n10','n11','n12','n13','n14']:        df['grade_to_mean_' + item] = df['grade'] / df.groupby([item])['grade'].transform('mean')        df['grade_to_std_' + item] = df['grade'] / df.groupby([item])['grade'].transform('std')
复制代码


这里给出一些特征交互的思路,但特征和特征间的交互衍生出新的特征还远远不止于此,抛砖引玉,希望大家多多探索。请学习者尝试其他的特征交互方法。

3.5 特征编码

3.5.1labelEncode 直接放入树模型中

#label-encode:subGrade,postCode,title#高维类别特征需要进行转换for col in tqdm(['employmentTitle', 'postCode', 'title','subGrade']):    le = LabelEncoder()    le.fit(list(data_train[col].astype(str).values) + list(data_test_a[col].astype(str).values))    data_train[col] = le.transform(list(data_train[col].astype(str).values))    data_test_a[col] = le.transform(list(data_test_a[col].astype(str).values))print('Label Encoding 完成')
复制代码


100%|██████████| 4/4 [00:08<00:00,  2.04s/it]
Label Encoding 完成
复制代码

3.5.2 逻辑回归等模型要单独增加的特征工程

  • 对特征做归一化,去除相关性高的特征

  • 归一化目的是让训练过程更好更快的收敛,避免特征大吃小的问题

  • 去除相关性是增加模型的可解释性,加快预测过程。


# 举例归一化过程#伪代码for fea in [要归一化的特征列表]:    data[fea] = ((data[fea] - np.min(data[fea])) / (np.max(data[fea]) - np.min(data[fea])))
复制代码

3.6 特征选择

  • 特征选择技术可以精简掉无用的特征,以降低最终模型的复杂性,它的最终目的是得到一个简约模型,在不降低预测准确率或对预测准确率影响不大的情况下提高计算速度。特征选择不是为了减少训练时间(实际上,一些技术会增加总体训练时间),而是为了减少模型评分时间。


特征选择的方法:


  • 1 Filter

  • 方差选择法

  • 相关系数法(pearson 相关系数)

  • 卡方检验

  • 互信息法

  • 2 Wrapper (RFE)

  • 递归特征消除法

  • 3 Embedded

  • 基于惩罚项的特征选择法

  • 基于树模型的特征选择

3.6.1Filter(方差选择法、相关系数法)

  • 基于特征间的关系进行筛选


方差选择法


  • 方差选择法中,先要计算各个特征的方差,然后根据设定的阈值,选择方差大于阈值的特征


from sklearn.feature_selection import VarianceThreshold#其中参数threshold为方差的阈值VarianceThreshold(threshold=3).fit_transform(train,target_train)
复制代码


相关系数法


  • Pearson 相关系数皮尔森相关系数是一种最简单的,可以帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性。结果的取值区间为 [-1,1] , -1 表示完全的负相关, +1 表示完全的正相关,0 表示没有线性相关。


from sklearn.feature_selection import SelectKBestfrom scipy.stats import pearsonr#选择K个最好的特征,返回选择特征后的数据#第一个参数为计算评估特征是否好的函数,该函数输入特征矩阵和目标向量,#输出二元组(评分,P值)的数组,数组第i项为第i个特征的评分和P值。在此定义为计算相关系数#参数k为选择的特征个数
SelectKBest(k=5).fit_transform(train,target_train)
复制代码


卡方检验


  • 经典的卡方检验是用于检验自变量对因变量的相关性。 假设自变量有 N 种取值,因变量有 M 种取值,考虑自变量等于 i 且因变量等于 j 的样本频数的观察值与期望的差距。 其统计量如下: χ2=∑(A−T)2T,其中 A 为实际值,T 为理论值

  • (注:卡方只能运用在正定矩阵上,否则会报错 Input X must be non-negative)


from sklearn.feature_selection import SelectKBestfrom sklearn.feature_selection import chi2#参数k为选择的特征个数
SelectKBest(chi2, k=5).fit_transform(train,target_train)
复制代码


互信息法


  • 经典的互信息也是评价自变量对因变量的相关性的。 在 feature_selection 库的 SelectKBest 类结合最大信息系数法可以用于选择特征,相关代码如下:


from sklearn.feature_selection import SelectKBestfrom minepy import MINE#由于MINE的设计不是函数式的,定义mic方法将其为函数式的,#返回一个二元组,二元组的第2项设置成固定的P值0.5def mic(x, y):    m = MINE()    m.compute_score(x, y)    return (m.mic(), 0.5)#参数k为选择的特征个数SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(train,target_train)
复制代码

3.6.2Wrapper (递归特征法)

  • 递归特征消除法 递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。 在 feature_selection 库的 RFE 类可以用于选择特征,相关代码如下(以逻辑回归为例):


from sklearn.feature_selection import RFEfrom sklearn.linear_model import LogisticRegression#递归特征消除法,返回特征选择后的数据#参数estimator为基模型#参数n_features_to_select为选择的特征个数
RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(train,target_train)
复制代码

3.6.3Embedded( 惩罚项的特征选择法、树模型的特征选择)

  • 基于惩罚项的特征选择法 使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。 在 feature_selection 库的 SelectFromModel 类结合逻辑回归模型可以用于选择特征,相关代码如下:


from sklearn.feature_selection import SelectFromModelfrom sklearn.linear_model import LogisticRegression#带L1惩罚项的逻辑回归作为基模型的特征选择
SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(train,target_train)
复制代码


  • 基于树模型的特征选择 树模型中 GBDT 也可用来作为基模型进行特征选择。 在 feature_selection 库的 SelectFromModel 类结合 GBDT 模型可以用于选择特征,相关代码如下:


from sklearn.feature_selection import SelectFromModelfrom sklearn.ensemble import GradientBoostingClassifier#GBDT作为基模型的特征选择SelectFromModel(GradientBoostingClassifier()).fit_transform(train,target_train)
复制代码


本数据集中我们删除非入模特征后,并对缺失值填充,然后用计算协方差的方式看一下特征间相关性,然后进行模型训练


# 删除不需要的数据for data in [data_train, data_test_a]:    data.drop(['issueDate','id'], axis=1,inplace=True)
复制代码


"纵向用缺失值上面的值替换缺失值"data_train = data_train.fillna(axis=0,method='ffill')
复制代码


x_train = data_train.drop(['isDefault','id'], axis=1)#计算协方差data_corr = x_train.corrwith(data_train.isDefault) #计算相关性result = pd.DataFrame(columns=['features', 'corr'])result['features'] = data_corr.indexresult['corr'] = data_corr.values
复制代码


# 当然也可以直接看图data_numeric = data_train[numerical_fea]correlation = data_numeric.corr()
f , ax = plt.subplots(figsize = (7, 7))plt.title('Correlation of Numeric Features with Price',y=1,size=16)sns.heatmap(correlation,square = True, vmax=0.8)
复制代码


<matplotlib.axes._subplots.AxesSubplot at 0x12d88ad10>
复制代码



features = [f for f in data_train.columns if f not in ['id','issueDate','isDefault'] and '_outliers' not in f]x_train = data_train[features]x_test = data_test_a[features]y_train = data_train['isDefault']
复制代码


def cv_model(clf, train_x, train_y, test_x, clf_name):    folds = 5    seed = 2020    kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
train = np.zeros(train_x.shape[0]) test = np.zeros(test_x.shape[0])
cv_scores = []
for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)): print('************************************ {} ************************************'.format(str(i+1))) trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
if clf_name == "lgb": train_matrix = clf.Dataset(trn_x, label=trn_y) valid_matrix = clf.Dataset(val_x, label=val_y)
params = { 'boosting_type': 'gbdt', 'objective': 'binary', 'metric': 'auc', 'min_child_weight': 5, 'num_leaves': 2 ** 5, 'lambda_l2': 10, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 4, 'learning_rate': 0.1, 'seed': 2020, 'nthread': 28, 'n_jobs':24, 'silent': True, 'verbose': -1, }
model = clf.train(params, train_matrix, 50000, valid_sets=[train_matrix, valid_matrix], verbose_eval=200,early_stopping_rounds=200) val_pred = model.predict(val_x, num_iteration=model.best_iteration) test_pred = model.predict(test_x, num_iteration=model.best_iteration) # print(list(sorted(zip(features, model.feature_importance("gain")), key=lambda x: x[1], reverse=True))[:20]) if clf_name == "xgb": train_matrix = clf.DMatrix(trn_x , label=trn_y) valid_matrix = clf.DMatrix(val_x , label=val_y) params = {'booster': 'gbtree', 'objective': 'binary:logistic', 'eval_metric': 'auc', 'gamma': 1, 'min_child_weight': 1.5, 'max_depth': 5, 'lambda': 10, 'subsample': 0.7, 'colsample_bytree': 0.7, 'colsample_bylevel': 0.7, 'eta': 0.04, 'tree_method': 'exact', 'seed': 2020, 'nthread': 36, "silent": True, } watchlist = [(train_matrix, 'train'),(valid_matrix, 'eval')] model = clf.train(params, train_matrix, num_boost_round=50000, evals=watchlist, verbose_eval=200, early_stopping_rounds=200) val_pred = model.predict(valid_matrix, ntree_limit=model.best_ntree_limit) test_pred = model.predict(test_x , ntree_limit=model.best_ntree_limit) if clf_name == "cat": params = {'learning_rate': 0.05, 'depth': 5, 'l2_leaf_reg': 10, 'bootstrap_type': 'Bernoulli', 'od_type': 'Iter', 'od_wait': 50, 'random_seed': 11, 'allow_writing_files': False} model = clf(iterations=20000, **params) model.fit(trn_x, trn_y, eval_set=(val_x, val_y), cat_features=[], use_best_model=True, verbose=500) val_pred = model.predict(val_x) test_pred = model.predict(test_x) train[valid_index] = val_pred test = test_pred / kf.n_splits cv_scores.append(roc_auc_score(val_y, val_pred)) print(cv_scores) print("%s_scotrainre_list:" % clf_name, cv_scores) print("%s_score_mean:" % clf_name, np.mean(cv_scores)) print("%s_score_std:" % clf_name, np.std(cv_scores)) return train, test
复制代码


def lgb_model(x_train, y_train, x_test):    lgb_train, lgb_test = cv_model(lgb, x_train, y_train, x_test, "lgb")    return lgb_train, lgb_test
def xgb_model(x_train, y_train, x_test): xgb_train, xgb_test = cv_model(xgb, x_train, y_train, x_test, "xgb") return xgb_train, xgb_test
def cat_model(x_train, y_train, x_test): cat_train, cat_test = cv_model(CatBoostRegressor, x_train, y_train, x_test, "cat")
复制代码


lgb_train, lgb_test = lgb_model(x_train, y_train, x_test)
复制代码


************************************ 1 ************************************Training until validation scores don't improve for 200 rounds[200]  training's auc: 0.749225  valid_1's auc: 0.729679[400]  training's auc: 0.765075  valid_1's auc: 0.730496[600]  training's auc: 0.778745  valid_1's auc: 0.730435Early stopping, best iteration is:[455]  training's auc: 0.769202  valid_1's auc: 0.730686[0.7306859913754798]************************************ 2 ************************************Training until validation scores don't improve for 200 rounds[200]  training's auc: 0.749221  valid_1's auc: 0.731315[400]  training's auc: 0.765117  valid_1's auc: 0.731658[600]  training's auc: 0.778542  valid_1's auc: 0.731333Early stopping, best iteration is:[407]  training's auc: 0.765671  valid_1's auc: 0.73173[0.7306859913754798, 0.7317304414673989]************************************ 3 ************************************Training until validation scores don't improve for 200 rounds[200]  training's auc: 0.748436  valid_1's auc: 0.732775[400]  training's auc: 0.764216  valid_1's auc: 0.733173Early stopping, best iteration is:[386]  training's auc: 0.763261  valid_1's auc: 0.733261[0.7306859913754798, 0.7317304414673989, 0.7332610441015461]************************************ 4 ************************************Training until validation scores don't improve for 200 rounds[200]  training's auc: 0.749631  valid_1's auc: 0.728327[400]  training's auc: 0.765139  valid_1's auc: 0.728845Early stopping, best iteration is:[286]  training's auc: 0.756978  valid_1's auc: 0.728976[0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912]************************************ 5 ************************************Training until validation scores don't improve for 200 rounds[200]  training's auc: 0.748414  valid_1's auc: 0.732727[400]  training's auc: 0.763727  valid_1's auc: 0.733531[600]  training's auc: 0.777489  valid_1's auc: 0.733566Early stopping, best iteration is:[524]  training's auc: 0.772372  valid_1's auc: 0.733772[0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912, 0.7337723979789789]lgb_scotrainre_list: [0.7306859913754798, 0.7317304414673989, 0.7332610441015461, 0.7289759386807912, 0.7337723979789789]lgb_score_mean: 0.7316851627208389lgb_score_std: 0.0017424259863954693
复制代码


testA_result = pd.read_csv('../testA_result.csv')
复制代码


roc_auc_score(testA_result['isDefault'].values, lgb_test)
复制代码


0.7290917729487896
复制代码

3.8 总结

特征工程是机器学习,甚至是深度学习中最为重要的一部分,在实际应用中往往也是所花费时间最多的一步。各种算法书中对特征工程部分的讲解往往少得可怜,因为特征工程和具体的数据结合的太紧密,很难系统地覆盖所有场景。本章主要是通过一些常用的方法来做介绍,例如缺失值异常值的处理方法详细对任何数据集来说都是适用的。但对于分箱等操作本章给出了具体的几种思路,需要读者自己探索。在特征工程中比赛和具体的应用还是有所不同的,在实际的金融风控评分卡制作过程中,由于强调特征的可解释性,特征分箱尤其重要。

项目链接以及码源

数据挖掘专栏


数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(上篇)


数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(下篇)


发布于: 1 小时前阅读数: 2
用户头像

汀丶

关注

本博客将不定期更新关于NLP等领域相关知识 2022-01-06 加入

本博客将不定期更新关于机器学习、强化学习、数据挖掘以及NLP等领域相关知识,以及分享自己学习到的知识技能,感谢大家关注!

评论

发布
暂无评论
数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(上篇)[xgboots/lightgbm/Catboost等模型]--模型融合:stacking、blending_数据挖掘_汀丶_InfoQ写作社区