Home Credit:违约风险展望模型浅析
一、 背景介绍
今朝的时代是互联网时代,也是消休时代,各走各业越来越意识到大数据的严重性。而在高速发展的金融市场,银走等贷款机构也越来越重视客户的信誉消休。但是很多人由于异国有余的或不存在的信誉记录而难以获得贷款。
Home Credit议定挑供积极和敦睦的借贷体验,竭力为无银走信誉背景的人群扩大金融容纳性。为了确保这些信誉记录不克的人群拥有良益的贷款体验,答用各栽替代数据——包括电信和交易消休——来展望客户的还款能力。
该篇文章中,吾们将答用Home Credit挑供的客户历史贷款数据,展望申请人是否有能力了偿贷款。这是一个标准的有监督分类负担,下面会答用各栽统计和机器学习手法进走浅近模型构建。
二、数据源
这些数据是由Home Credit机构挑供的,Home Credit是一家特意为未开户人群挑供信誉额度(贷款)的服务机构。展望客户是否会了偿贷款或遇到可贵是一个关键的交易需求,数据源为
数据分为7个区别的版块:

由于数据量过大,电脑内存无法匹配,故只答用application_{train|test}.csv,且对其随机抽取其中10%的数据进走分析。天然,伪如愿看模型拟相符尽不妨益的话,最益答用完益数据进走训练模型。
三、构建模型
1、导入第三方包
#导入第三方包 import pandas as pd import numpy as np #sklearn 预处理分类变量 from sklearn.preprocessing import LabelEncoder #文件编制管理 os import os #渺视告警消休 import warnings warnings.filterwarnings('ignore') #导入绘图包matplotlib/seaborn import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns
2、读取数据
#可用的文件列外 print (os.listdir('C:\mengyun\home credit default risk')) #导入训练数据 app_train=pd.read_csv('C:/mengyun/home credit default risk/application_train.csv') print('训练数据联合构:',app_train.shape)
app_train数据联合构: (30751, 122)
下买对数据随机抽取10%进走分析:
#由于数据较大,电脑内存不足,故进走随机抽取10%数据 source_X=app_train.drop(columns = ['TARGET']) source_y=app_train['TARGET'] from sklearn.cross_validation import train_test_split train_X,test_X,train_y,test_y=train_test_split(source_X,source_y,train_size=0.1) app_train=train_X app_train['TARGET']=train_y app_train=app_train.reset_index(drop=True) print('app_train数据联合构:',app_train.shape)
新app_train数据联合构: (30751, 122)
3、寻求性数据分析(EDA)
EDA是一个怒放式的过程,在这个过程中,吾们计算统计数据并绘制图外,以发现数据中的趋势、异常、模式或干系。目的是知道吾们的数据不妨通告吾们什么。它平日从一个高层次的概述开起,然后随着吾们发现数据中趣味的范围而紧缩到特定的范围。它们不妨用来告知吾们的建模选择,例如协助吾们决定答用哪些特性。
3.1 检查目的列分布
#检查目的列target的分布(0和1的分布) app_train['TARGET'].value_counts()
0 282441 2507Name: TARGET, dtype: int64
app_train['TARGET'].astype(int).plot.hist()

从上图看出,准时了偿的贷款相比违约的贷款多很多,吾们不妨看出这是一个不均衡的类题目。
3.2 检查每列缺失值个数及占比
#检查缺失值占比 #计算每列缺失值的个数 def missing_values_table(df): #缺失值总数 mis_val=df.isnull().sum() #缺失值占比 mis_val_percent=100*df.isnull().sum()/len(df) #用造就制作外 mis_val_table=pd.concat([mis_val,mis_val_percent],axis=1) #对外格列名重命名 mis_val_table_rename_columns=mis_val_table.rename(columns={0:'缺失值数目',1:'缺失值占比'}) #排序 mis_val_table_rename_columns=mis_val_table_rename_columns[mis_val_table_rename_columns.iloc[:,1]!=0].sort_values('缺失值占比',ascending=False).round(1) print ('相符计'+str(df.shape[1])+'列,其中'+str(mis_val_table_rename_columns.shape[0])+'列具有缺失值') return mis_val_table_rename_columns missing_value=missing_values_table(app_train) missing_value.head(15)
相符计122列,其中65列具有缺失值。此处可对缺失值占比超过50%的列进走删除,其他不妨答用常用的均值、中位数、多数等填充,复杂的不妨答用拉格朗插值法,因电脑内存因为,此处暂不进走填补缺失值。

3.3 检查异常值
3.3.1 描述统计消休
#检查异常值 app_train.describe()

由上图所示,发现DAYS_BIRTH、DAYS_EMPLOYED两个变量值均是负值,且发现DAYS_EMPLOYED含有异常值(最大值为365243,即职业1000多年,明明这是弗成能的),下面顺次分析:
# 设立图外类型 plt.style.use('fivethirtyeight') # 顺服出生时间年数进走作图 plt.hist(app_train['DAYS_BIRTH']/-365,edgecolor='k',bins=25) plt.title('客户出生年限'),plt.xlabel('年龄'),plt.ylabel('计数')

从上图来看,客户年龄分布比较相符理,且异国清楚异常值。
3.3.2 检查异常值
app_train['DAYS_EMPLOYED'].plot.hist(title='职业天数直方图') plt.xlabel('职业天数')

如上图所示,存在清楚的异常值365243,以下处理异常值手法为:先替换为空值NA,后进走缺失值填充。
app_train['DAYS_EMPLOYED'].replace({365243:np.nan},inplace=True) #把DAYS_BIRTH列转换为正值 app_train['DAYS_BIRTH']=abs(app_train['DAYS_BIRTH']) app_train['DAYS_EMPLOYED'].plot.hist(title='职业天数直方图') plt.xlabel('职业天数')

上图不妨看出,处理后的DAYS_EMPLOYED列分布较寻常。
4、分类变量进走编码处理
由于机器学习模型无法识别分类变量(除了LightGBM模型),因此需求事先对分类变量进走处理,有数字来代外,有两栽严重手法来处理这个步骤:
a. 标签编码:用一个整数对分类变量的每个单一的类别赋值,不需求增进新的列。比如一分类变量中有3个微妙的类别,离别是厂长、主任、技术工,离别对答0、1、2
b. one-hot编码:为每个微妙的类别创建1个新的列,每个不美观察值在反映类别的列中得到1,在总计其他新列中得到0。
标签编码的题目是它给了每个类别一个恣意的顺次。分配给每个类别的值是随机的,并不逆映类别的任何内在方面。当一个分类变量只有2个微妙值,答用标签编码是不妨的,但是伪如超过2个及以上,最益答用one-hot编码。one-hot编码唯一的弊端是特征的数目不妨会随着具有多个类别的分类变量而激增。为知道决这个题目,吾们不妨先实施一栽one-hot编码,然后答用PCA或其他降维手法来缩小维度的数目(同时如故试图保留消休)。
#查看每列数据类型 app_train.dtypes.value_counts()
float64 65int64 41object 16dtype: int64
以上显现浮点型数占领65列,整型数占领41列,分类类别有16列。
#查看数据类别为字符串的列,每列不重复的值有多少 app_train.select_dtypes('object').apply(pd.Series.nunique,axis=0)

下面进走分类变量编码,类别小于等于2的答用标签编码,类别大于2的答用one-hot编码。对于标签编码,答用Scikit-Learn LabelEncoder;对于one-hot编码,答用get-dummies(df)。
#创建一个Label编码器 le=LabelEncoder() le_count=0 #循环访问列 for col in app_train: if app_train[col].dtype=='object': if len(list(app_train[col].unique()))<=2: le.fit(app_train[col]) app_train[col]=le.transform(app_train[col]) le_count=le_count+1 print('%d列被标签编码了'%le_count) #one-hot编码分类变量 app_train=pd.get_dummies(app_train) print('训练数据结构:',app_train.shape)
4列被标签编码了
训练数据结构: (30751, 239)
分类变量重新编码后,数据走数未变,列数补充为239列。
5、各变量与目的变量关连干系
#各特征变量及标签相干系数矩阵 corr_df=app_train.corr()['TARGET'].sort_values() print('正关连性前15变量:\n',corr_df.tail(10)) print('\n负关连性前15变量:\n',corr_df.head(10))

由上图可见,正关连性最强的变量为DAYS_EMPLOYED,相干系数为0.075074,此变量值为负,当职业年限越久时,发生信誉违约的概率随之降矬;负关连性最强的变量为EXT_SOURCE_3、EXT_SOURCE_2、EXT_SOURCE_1、DAYS_BIRTH,EXT_SOURCE系列变量为外部第三方数据源,变量值均为小于1的正值,与目的变量呈负关连;DAYS_BIRTH为出生天数,已转换成正值,外示年龄越大,违约概率随之降矬。
6、可视化分析-KDE核密度揣摸图
针对上述5个关连性较强变量进走核密度揣摸图(KDE)分析,在确定目的值下,显现了单个变量的分布,答用seaborn.kdeplot模块。
6.1 DAYS_BIRTH 变量
plt.figure(figsize=(10,6)) # 准时还款时kde分布图 sns.kdeplot(app_train.loc[app_train['TARGET']== 0,'DAYS_BIRTH']/365,label ='target == 0') # 违约时kde分布图 sns.kdeplot(app_train.loc[app_train['TARGET']== 1,'DAYS_BIRTH']/365,label ='target == 1') # 标签设立 plt.xlabel('年龄'); plt.ylabel('占比'); plt.title('年龄分布');

6.2 DAYS_EMPLOYED 变量
#起头移除DAYS_EMPLOYED列有缺失值的走 day_emp_df=app_train.dropna(subset=['DAYS_EMPLOYED'],how='any') plt.figure(figsize=(12,6)) # 准时还款时kde分布图 sns.kdeplot(day_emp_df.loc[day_emp_df['TARGET']== 0,'DAYS_EMPLOYED']/-365,label ='target == 0') # 违约时kde分布图 sns.kdeplot(day_emp_df.loc[day_emp_df['TARGET']==1,'DAYS_EMPLOYED']/-365,label ='target == 1') # 标签设立 plt.xlabel('职业年限'); plt.ylabel('占比'); plt.title('职业年限分布');

6.3 EXT_SOURCE系列变量
ex_source1_df=app_train.dropna(subset=['EXT_SOURCE_1'],how='any') plt.figure(figsize=(12,6)) # 准时还款时kde分布图 sns.kdeplot(ex_source1_df.loc[ex_source1_df['TARGET']== 0,'EXT_SOURCE_1'],label ='target == 0') # 违约时kde分布图 sns.kdeplot(ex_source1_df.loc[ex_source1_df['TARGET']== 1,'EXT_SOURCE_1'],label ='target == 1') # 标签设立 plt.xlabel('外部数据源1'); plt.ylabel('占比'); plt.title('外部数据源1分布');
ex_source2_df=app_train.dropna(subset=['EXT_SOURCE_2'],how='any') plt.figure(figsize=(12,6)) # 准时还款时kde分布图 sns.kdeplot(ex_source2_df.loc[ex_source2_df['TARGET']== 0,'EXT_SOURCE_2'],label ='target == 0') # 违约时kde分布图 sns.kdeplot(ex_source2_df.loc[ex_source2_df['TARGET']== 1,'EXT_SOURCE_2'],label ='target == 1') # 标签设立 plt.xlabel('外部数据源2'); plt.ylabel('占比'); plt.title('外部数据源2分布');
ex_source3_df=app_train.dropna(subset=['EXT_SOURCE_3'],how='any') plt.figure(figsize=(12,6)) # 准时还款时kde分布图 sns.kdeplot(ex_source3_df.loc[ex_source3_df['TARGET']== 0,'EXT_SOURCE_3'],label ='target == 0') # 违约时kde分布图 sns.kdeplot(ex_source3_df.loc[ex_source3_df['TARGET']== 1,'EXT_SOURCE_3'],label ='target == 1') # 标签设立 plt.xlabel('外部数据源3'); plt.ylabel('占比'); plt.title('外部数据源3分布');



总结,由上述KDE图显现,EXT_SOURCE_3显现了目的值之间最大的迥异。吾们不妨明了地看到,这一特征与申请人了偿贷款的不妨性相关。其次为EXT_SOURCE_1、DAYS_BIRTH有清楚目的值迥异。这栽干系不是很强,究竟上这些特征变量的关连性都比较弱,但是这些变量对于机器学习模型如故是有用的,它不妨展望申请人是否会准时了偿贷款。
7、特征选取
因特征变量较多,电脑无法负荷,故只选取关连性较强的5个变量构建模型,离别是'DAYS_BIRTH','DAYS_EMPLOYED','EXT_SOURCE_1','EXT_SOURCE_2','EXT_SOURCE_3'.后续也不妨进走特征变量组织等,此处暂不进走此项操作。
以下对此5个变量及目的变量再次进走关连性分析:
#选取上述5个变量动作建模变量,并且再次进走关连性分析 tz_Df=app_train[['TARGET','DAYS_BIRTH','DAYS_EMPLOYED','EXT_SOURCE_1','EXT_SOURCE_2','EXT_SOURCE_3']] tz_corr=tz_Df.corr() tz_corr plt.figure(figsize=(12,8)) sns.heatmap(tz_corr,square=True,annot=True)


上述炎图不妨看出,EXT_SOURCE的三个特性都与目的具有相对较高的负关连性,这外明随着EXT_SOURCE的值补充,客户更有不妨了偿贷款。
吾们还不妨看到DAYS_BIRTH与EXT_SOURCE_1呈正关连,这外明该评分中的一个因素不妨是客户的年龄。
8、模型构建
8.1 对数据进走处理,分为训练集、测试集
#对缺失值答用中位数填充 tz_Df['DAYS_BIRTH']=tz_Df['DAYS_BIRTH'].fillna(tz_Df['DAYS_BIRTH'].median()) tz_Df['DAYS_EMPLOYED']=tz_Df['DAYS_EMPLOYED'].fillna(tz_Df['DAYS_EMPLOYED'].median()) tz_Df['EXT_SOURCE_1']=tz_Df['EXT_SOURCE_1'].fillna(tz_Df['EXT_SOURCE_1'].median()) tz_Df['EXT_SOURCE_2']=tz_Df['EXT_SOURCE_2'].fillna(tz_Df['EXT_SOURCE_2'].median()) tz_Df['EXT_SOURCE_3']=tz_Df['EXT_SOURCE_3'].fillna(tz_Df['EXT_SOURCE_3'].median()) sourceX_df=tz_Df.drop(columns=['TARGET']) sourceY_df=tz_Df['TARGET'] from sklearn.cross_validation import train_test_split train_x,test_x,train_Y,test_Y=train_test_split(sourceX_df,sourceY_df,train_size=0.8) print(sourceX_df.shape,train_x.shape,test_x.shape) print(sourceY_df.shape,train_Y.shape,test_Y.shape)
(30751, 5) (24600, 5) (6151, 5)(30751,) (24600,) (6151,)
#构建模型,导入浅近的逻辑回归模型 from sklearn.linear_model import LogisticRegression model=LogisticRegression() model.fit(train_x,train_Y) model.score(test_x,test_Y)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1, penalty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False)
模型逼真率=0.9193627052511787。
四、总结
上述的分析过程、特征选取及模型选取等,均采用相对浅近的分析手法,模型仍存在漏洞,后续深入理解特征工程、各栽机器学习模型(如随机森林、xgboost等)后,平素改进。