失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 信贷违约风险评估模型(上篇):探索性数据分析

信贷违约风险评估模型(上篇):探索性数据分析

时间:2020-04-21 13:14:38

相关推荐

信贷违约风险评估模型(上篇):探索性数据分析

机器学习训练营——机器学习爱好者的自由交流空间(入群联系qq:2279055353)

案例介绍

由于不良或缺少信用记录,很多人难以获得贷款。Home Credit是一家金融服务机构,致力于向无银行账户的人群提供积极且安全的借贷经历。Home Credit利用多种来源的数据,例如,电话费、消费交易信息,预测客户的偿还能力。本案例使用Home Credit提供的客户借贷历史数据,预测该申请人是否有能力还贷,从而决定是否向他发放贷款。这是一个标准的有监督分类问题。

有监督:训练数据带有类标签,目标任务是训练一个模型预测特征的标签。

分类:标签是0-1二值变量。1代表重新放贷有困难;0代表按时重新放贷。

数据描述

该案例包括7个不同来源的数据文件:

application_train/application_test: 包括每一笔贷款的信息,每行包括一笔贷款,由特征SK_ID_CURR识别。训练集的TARGET是二值变量,1表示不能放贷;0表示能放贷。

bureau: 客户以前的信用情况。每一行表示以前的一次贷款,在application数据里的每一个贷款申请可能有多个以前的信贷记录。

bureau_balance:bureau里以前贷款的月数据。每行表示以前贷款的月记录,所以一笔以前的贷款可能有多行记录。

previous_application:Home Credit客户以前的贷款申请记录。每一笔当前的申请都有多个以前的贷款。每一笔以前的申请由特征SK_ID_PREV标识。

POS_CASH_BALANCE: 以前的销售或现金贷款客户的月数据。

credit_card_balance: 以前的信用卡客户月余额。

installments_payment: Home Credit客户以前贷款的支付历史,每一行对应一笔支付记录。

我们用一张图表示这些数据集的关系。

首先,我们导入一个数据科学任务必需的4个Python库:numpy,pandas,sklearn,matplotlib.

# numpy and pandas for data manipulationimport numpy as npimport pandas as pd # sklearn preprocessing for dealing with categorical variablesfrom sklearn.preprocessing import LabelEncoder# File system manangementimport os# Suppress warnings import warningswarnings.filterwarnings('ignore')# matplotlib and seaborn for plottingimport matplotlib.pyplot as pltimport seaborn as sns

我们列出所有可用的9个数据文件:一个训练集(with target), 一个检验集(without target), 一个例子提交文件,6个贷款信息文件。

# List files availableprint(os.listdir("../input/"))

# Training dataapp_train = pd.read_csv('../input/application_train.csv')print('Training data shape: ', app_train.shape)app_train.head()

训练集有307511行(每行对应一笔贷款),122个特征(包括预测变量target)。再看看检验集。

# Testing data featuresapp_test = pd.read_csv('../input/application_test.csv')print('Testing data shape: ', app_test.shape)app_test.head()

Testing data shape: (48744, 121)

探索性数据分析

在这个阶段,我们计算统计量、作图显示数据的结构、趋势、变量间的关系。EDA对下面的建模有指示性的作用,例如,提示我们选用哪些变量。

Target的分布

预测变量target取0, 1两个值。0表示按时放贷,1表示客户有还款困难。我们先看看这两类的个数,并用频数直方图表示。

print(app_train['TARGET'].value_counts())

0 282686

1 24825

Name: TARGET, dtype: int64

app_train['TARGET'].astype(int).plot.hist()plt.show()

1值明显少于0值,这是一个不平衡类问题。我们将考虑复杂的机器学习模型,给类赋予权值反映这种不平衡性。

检查缺失值

我们检查数据的各列缺失值数量和比例。

# Function to calculate missing values by column# Funct def missing_values_table(df):# Total missing valuesmis_val = df.isnull().sum()# Percentage of missing valuesmis_val_percent = 100 * df.isnull().sum() / len(df)# Make a table with the resultsmis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)# Rename the columnsmis_val_table_ren_columns = mis_val_table.rename(columns = {0 : 'Missing Values', 1 : '% of Total Values'})# Sort the table by percentage of missing descendingmis_val_table_ren_columns = mis_val_table_ren_columns[mis_val_table_ren_columns.iloc[:,1] != 0].sort_values('% of Total Values', ascending=False).round(1)# Print some summary informationprint ("Your selected dataframe has " + str(df.shape[1]) + " columns.\n""There are " + str(mis_val_table_ren_columns.shape[0]) +" columns that have missing values.")# Return the dataframe with missing informationreturn mis_val_table_ren_columns

# Missing values statisticsmissing_values = missing_values_table(app_train)print(missing_values.head(20))

Your selected dataframe has 122 columns.

There are 67 columns that have missing values.

建模时,我们将使用XGBoost模型处理这些缺失值。另一种处理方式是,对缺失比例高的列直接从数据里删除,但不知道该列是否对建模有用。因此,我们暂时保留所有列。

列类型

我们统计列变量的类型的数量。其中,int64,float64型是数值变量,object包括字符串,是类别变量。

# Number of each type of columnprint(app_train.dtypes.value_counts())

float64 65

int64 41

object 16

dtype: int64

我们再看看类别变量包含的不同的类别数。

# Number of unique classes in each object columnprint(app_train.select_dtypes('object').apply(pd.Series.nunique, axis = 0))

NAME_CONTRACT_TYPE 2

CODE_GENDER 3

FLAG_OWN_CAR 2

FLAG_OWN_REALTY 2

NAME_TYPE_SUITE 7

NAME_INCOME_TYPE 8

NAME_EDUCATION_TYPE 5

NAME_FAMILY_STATUS 6

NAME_HOUSING_TYPE 6

OCCUPATION_TYPE 18

WEEKDAY_APPR_PROCESS_START 7

ORGANIZATION_TYPE 58

FONDKAPREMONT_MODE 4

HOUSETYPE_MODE 3

WALLSMATERIAL_MODE 7

EMERGENCYSTATE_MODE 2

dtype: int64

大多数类别变量具有很少的类别值,需要类别变量编码成数值表示。

编码类别变量

在继续之前,我们需要处理类别变量。通常,一个机器学习模型不能处理类别变量,但也有例外,象LightGBM. 因此,我们必须编码这些类别变量为数值型。主要有两种方法:

Label encoding: 给类别变量的每个类别值分配一个整数,不产生新列。例如,

如果类别变量取二值,适合用这种编码方式;如果类别变量取多值,下面的one-hot编码更合适。

One-hot encoding: 为每一个类别值产生一个新列。每一个观测在它对应的类别列里接收1,否则接收0.

Label Encoding and One-Hot Encoding

在这里,对于两类变量我们使用Label Encoding, scikit-learn函数LabelEncoder实现;对于多类变量使用one-hot Encoding, pandas函数get_dummies实现。

# Create a label encoder objectle = LabelEncoder()le_count = 0# Iterate through the columnsfor col in app_train:if app_train[col].dtype == 'object':# If 2 or fewer unique categoriesif len(list(app_train[col].unique())) <= 2:# Train on the training datale.fit(app_train[col])# Transform both training and testing dataapp_train[col] = le.transform(app_train[col])app_test[col] = le.transform(app_test[col])# Keep track of how many columns were label encodedle_count += 1print('%d columns were label encoded.' % le_count)

3 columns were label encoded.

# one-hot encoding of categorical variablesapp_train = pd.get_dummies(app_train)app_test = pd.get_dummies(app_test)print('Training Features shape: ', app_train.shape)print('Testing Features shape: ', app_test.shape)

Training Features shape: (307511, 243)

Testing Features shape: (48744, 239)

比对训练集和检验集

检验集和训练集需要相同的特征。One-hot Encoding在训练集里产生了更多的列,因为有一些类别变量的类没有出现在检验集里。为了删除在训练集而不在检验集的列,我们需要比对这两个数据框。首先,从训练集里提取target列,这个量检验集里没有。

train_labels = app_train['TARGET']# Align the training and testing data, keep only columns present in both dataframesapp_train, app_test = app_train.align(app_test, join = 'inner', axis = 1)# Add the target back inapp_train['TARGET'] = train_labelsprint('Training Features shape: ', app_train.shape)print('Testing Features shape: ', app_test.shape)

Training Features shape: (307511, 240)

Testing Features shape: (48744, 239)

异常变量

变量的观测异常可能有很多种原因,例如,测量误差、极端值。DAYS_BIRTH列是负值,这是因为该值是相对于当前贷款申请日推算的。我们通过乘-1, 再除以365, 把它转换成年的表示。

print((app_train['DAYS_BIRTH'] / -365).describe())

count 307511.000000

mean 43.936973

std 11.956133

min 20.517808

25% 34.008219

50% 43.150685

75% 53.923288

max 69.120548

Name: DAYS_BIRTH, dtype: float64

从结果看,这些贷款“年龄”是合理的,没有异常值。再来看看’DAYS_EMPLOYED’,

print(app_train['DAYS_EMPLOYED'].describe())

count 307511.000000

mean 63815.045904

std 141275.766519

min -17912.000000

25% -2760.000000

50% -1213.000000

75% -289.000000

max 365243.000000

Name: DAYS_EMPLOYED, dtype: float64

最大值是差不多1000年,太夸张了!让我们提取异常客户集,看看他们是否比其他客户有更高或更低的违约率。

anom = app_train[app_train['DAYS_EMPLOYED'] == 365243]non_anom = app_train[app_train['DAYS_EMPLOYED'] != 365243]print('The non-anomalies default on %0.2f%% of loans' % (100 * non_anom['TARGET'].mean()))print('The anomalies default on %0.2f%% of loans' % (100 * anom['TARGET'].mean()))print('There are %d anomalous days of employment' % len(anom))

The non-anomalies default on 8.66% of loans

The anomalies default on 5.40% of loans

There are 55374 anomalous days of employment

我们看到,异常客户有更低的违约率。异常值似乎有一些重要作用,因此,我们用not a numuber(np.nan)填充它们,然后产生一个新的布尔列,表示该值是否异常。

# Create an anomalous flag columnapp_train['DAYS_EMPLOYED_ANOM'] = app_train["DAYS_EMPLOYED"] == 365243# Replace the anomalous values with nanapp_train['DAYS_EMPLOYED'].replace({365243: np.nan}, inplace = True)app_train['DAYS_EMPLOYED'].plot.hist(title = 'Days Employment Histogram');plt.xlabel('Days Employment');plt.show()

在检验集上重复相同的操作。

app_test['DAYS_EMPLOYED_ANOM'] = app_test["DAYS_EMPLOYED"] == 365243app_test["DAYS_EMPLOYED"].replace({365243: np.nan}, inplace = True)print('There are %d anomalies in the test data out of %d entries' % (app_test["DAYS_EMPLOYED_ANOM"].sum(), len(app_test)))

There are 9274 anomalies in the test data out of 48744 entries

相关性分析

在处理了类别变量和异常变量后,我们继续进行探索性数据分析。下面,我们将要探索特征与target变量的相关性。

# Find correlations with the target and sortcorrelations = app_train.corr()['TARGET'].sort_values()# Display correlationsprint('Most Positive Correlations:\n', correlations.tail(15))print('\nMost Negative Correlations:\n', correlations.head(15))

Most Positive Correlations:

OCCUPATION_TYPE_Laborers 0.043019

FLAG_DOCUMENT_3 0.044346

REG_CITY_NOT_LIVE_CITY 0.044395

FLAG_EMP_PHONE 0.045982

NAME_EDUCATION_TYPE_Secondary / secondary special 0.049824

REG_CITY_NOT_WORK_CITY 0.050994

DAYS_ID_PUBLISH 0.051457

CODE_GENDER_M 0.054713

DAYS_LAST_PHONE_CHANGE 0.055218

NAME_INCOME_TYPE_Working 0.057481

REGION_RATING_CLIENT 0.058899

REGION_RATING_CLIENT_W_CITY 0.060893

DAYS_EMPLOYED 0.074958

DAYS_BIRTH 0.078239

TARGET 1.000000

Name: TARGET, dtype: float64

Most Negative Correlations:

EXT_SOURCE_3 -0.178919

EXT_SOURCE_2 -0.160472

EXT_SOURCE_1 -0.155317

NAME_EDUCATION_TYPE_Higher education -0.056593

CODE_GENDER_F -0.054704

NAME_INCOME_TYPE_Pensioner -0.046209

DAYS_EMPLOYED_ANOM -0.045987

ORGANIZATION_TYPE_XNA -0.045987

FLOORSMAX_AVG -0.044003

FLOORSMAX_MEDI -0.043768

FLOORSMAX_MODE -0.043226

EMERGENCYSTATE_MODE_No -0.042201

HOUSETYPE_MODE_block of flats -0.040594

AMT_GOODS_PRICE -0.039645

REGION_POPULATION_RELATIVE -0.037227

Name: TARGET, dtype: float64

表示客户贷款年龄的DAYS_BIRTH有最大的正相关系数,但是,这个特征的值是负的,意味着客户年龄越大,越不可能违约。为了方便理解,我们使用DAYS_BIRTH的绝对值,那么相关系数将是负的。

# Find the correlation of the positive days since birth and targetapp_train['DAYS_BIRTH'] = abs(app_train['DAYS_BIRTH'])app_train['DAYS_BIRTH'].corr(app_train['TARGET'])

-0.07823930830982694

负线性关系表示:客户年龄越大,越倾向按时还贷。我们作客户年龄的直方图。

# Set the style of plotsplt.style.use('fivethirtyeight')# Plot the distribution of ages in yearsplt.hist(app_train['DAYS_BIRTH'] / 365, edgecolor = 'k', bins = 25)plt.title('Age of Client'); plt.xlabel('Age (years)'); plt.ylabel('Count');plt.show()

为了可视化年龄对target的影响,我们作对应不同target值的年龄核密度估计图。

plt.figure(figsize = (10, 8))# KDE plot of loans that were repaid on timesns.kdeplot(app_train.loc[app_train['TARGET'] == 0, 'DAYS_BIRTH'] / 365, label = 'target == 0')# KDE plot of loans which were not repaid on timesns.kdeplot(app_train.loc[app_train['TARGET'] == 1, 'DAYS_BIRTH'] / 365, label = 'target == 1')# Labeling of plotplt.xlabel('Age (years)'); plt.ylabel('Density'); plt.title('Distribution of Ages');plt.show()

尽管并不显著相关(相关系数-0.07),但是Age的确对target有影响,它可能在机器学习模型里有用。

Exterior Sources

与target负相关性最强的3个变量是EXT_SOURCE_1,EXT_SOURCE_2, andEXT_SOURCE_3. 这三个特征表示来自外部数据源的标准化分数,代表某种累积的信用评级。我们看看它们之间的相关系数,并画出热图。

# Extract the EXT_SOURCE variables and show correlationsext_data = app_train[['TARGET', 'EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH']]ext_data_corrs = ext_data.corr()ext_data_corrs

plt.figure(figsize = (8, 6))# Heatmap of correlationssns.heatmap(ext_data_corrs, cmap = plt.cm.RdYlBu_r, vmin = -0.25, annot = True, vmax = 0.6)plt.title('Correlation Heatmap');plt.show()

所有3个EXT_SOURCE与target存在负相关,表示随着EXT_SOURCE增大,客户更容易贷到款。DAYS_BIRTHEXT_SOURCE_1正相关(0.6), 这表明分数的因素之一是客户年龄。下面我们看看这3个特征的分布。

plt.figure(figsize = (10, 12))# iterate through the sourcesfor i, source in enumerate(['EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3']):# create a new subplot for each sourceplt.subplot(3, 1, i + 1)# plot repaid loanssns.kdeplot(app_train.loc[app_train['TARGET'] == 0, source], label = 'target == 0')# plot loans that were not repaidsns.kdeplot(app_train.loc[app_train['TARGET'] == 1, source], label = 'target == 1')# Label the plotsplt.title('Distribution of %s by Target Value' % source)plt.xlabel('%s' % source); plt.ylabel('Density');plt.tight_layout(h_pad = 2.5)

EXT_SOURCE_3显示了最不同的分布,该变量对预测一个申请者是否能按时偿还贷款是有用的。

未完待续

如果觉得《信贷违约风险评估模型(上篇):探索性数据分析》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。