一、摘要
本文详述了如何通过数据预览,基本数据分析、探索式数据分析,缺失数据填补等方法,实现对kaggle上Prosper借贷平台贷款者还款与否这一分类问题如何进行数据分析的具体探索式实践。并分别对2009.07.01前后的模型进行建模分析对比,得出两个模型的预测准确率和变量对模型的重要性对比分析,明确看出2009.07.01前后平台的模型明显有很大的不同。
二、项目内容介绍
Prosper LoanData是由Joshua Schnessl从Udacity Data Analyst Nanodegree上把数据放到kaggle的上供感兴趣的人分析的一个实例项目,这并非一个竞赛项目。本人试图通过训练数据集分析出什么类型的借款人更可能不违约,并预测出测试数据集中的每笔贷款是否违约。
三、数据变量
四、数据预处理
在加载数据之前,先通过如下代码加载之后会用到的相应的库。
读入数据
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import random
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_curve, auc
loanData=pd.read_csv("D:\\打包7.2\\prosperLoanData.csv")
4.0数据预览
先观察数据
loanData.head()
loanData.info()
loanData.shape
查看数据的分布情况
可返回变量和观测的数量、缺失值和唯一值的数目、平均值、分位数等相关信息
loanData.describe()
从上可见,数据集包含81个变量,113,937条数据,由于数据变量太多,本文只对重要变量作解释说明,若需详细了解变量含义,请点击变量词典。
ListingCreationDate:表创建时间(可能是交易开始计息时间)
LoanStatus:贷款状态(Completed、Current、Defaulted、Chargedoff等)
EmploymentStatus:受雇佣状态(Self-employed、Employed等)
EmploymentStatusDuration:受雇佣状态持续时间(以月为计算单位)
IsBorrowerHomeowner:借款人是否拥有房屋
CreditScoreRangeLower/CreditScoreRangeUpper:消费信用最低/最高分
InquiriesLast6Months:最近6个月查过多少次征信记录
BorrowerRate:借款标利率,作为P2P平台资金借贷价格的代理变量,BorrowerRate不包含其他费用,是筹资者付给投资人的报酬,也是融资最直接和最重要的成本,其体现了资金供求双方在综合考虑各种因素情况下所认可的资金使用成本.
Term:期限,筹资者通过网贷平台进行借款时所承诺的最终偿还期限,借款期限体现该资产的流动性,期限较长的资产应存在着流动性溢价(利率上涨).
CreditGrade/ProsperRating(Alpha):信用等级,前者反映的是2009年7月1日前客户的信用等级,后者反映的是2009年7月1日后的信用等级.信用等级越高,其偿债能力越强.
CreditScore:由消费信用公司提供的消费信用评分,类似于国内的芝麻信用分。
StatedMonthlyIncome:客户月收入,月收入越高,投资者对该借款本息按时回流越有信心.
DelinquenciesLast7Years:信用资料提交时借款人过去7年违约次数,该指标在一定程度上可以体现借款标的发布者的信用状况
BankCarduse:信用资料提交时借款人信用卡使用额度和信用卡总透支额度的百分比,本文将这个数据分成四组(mild use;medium use;heavy use;super use)
LoanOriginalAmount:借款人在借款时已经向prosper借入的资金,如果没有历史记录则为0,显然,借入本金越多,其还款压力越大,但是这项指标大的话也可能说明该客户对prosper依赖性较强.
DebtToIncomeRatio:借款人的债务收入比,债务收入比越高说明筹资者财务状况越差,还款能力较低.其向P2P平台借款时,投资者应要求有更高的回报.
Occupation:贷款人职业
IncomeRange:贷款人年收入范围
BorrowerState:贷款人所在州
Customer_clarify:0或NA是未在prosper贷过款的客户,反之是贷过款的。
4.1 LoanStatus数据变换
平台把借款状态分为12种:Cancelled(取消)、Chargedoff(冲销,投资人有损失)、Completed(正常完成,投资人无损失)、Current(贷款还款中)、Defaulted(坏账,投资人有损失)、FinalPaymentInProgress(最后还款中,投资人无损失)、Past Due(逾期还款,投资人无损失),Cancelled的只有5笔,所以直接去掉。
本文依据交易是在进行正常还款期内还是已关闭将LoanStatus分成两组,并根据投资人有无损失将已关闭的交易分成Completed和Defaulted:Current(贷款还款中)、Defaulted(包含Defaulted、Chargedoff)、Completed(包含Completed、FinalPaymentInProgress、Past Due)三组。代码定义函数如下:
贷款状态变换
def loan_status(s):#该函数主要功能把状态最终分为4种情况包括坏账,取消,贷款还款中,正常还款(正常完成,最后还款中,逾期还款)
if s=='Chargedoff':#Chargedoff(冲销,投资人有损失)
a='Defaulted' #Defaulted(坏账,投资人有损失)
elif s=='Defaulted':#Defaulted(坏账,投资人有损失)
a = 'Defaulted'#Defaulted(坏账,投资人有损失)
elif s=='Cancelled':#Cancelled(取消)
a='Cancelled' #Cancelled(取消)
elif s == 'Current':#Current(贷款还款中)
a = 'Current'#Current(贷款还款中)
else:
a='Completed'#Completed(正常完成,投资人无损失)
return a
将数据进行转换,设置为Status变量:
LoanStatus数据字段转换为Status(来自loan_status函数转换了贷款状态)
#'Cancelled状态的样本个数只有5个,直接删除
loanData=loanData[loanData['Status']!='Cancelled']
loanData.info()
print(loanData.head())
loanData.columns
4.2 BankcardUtilization数据变换
信用资料提交时借款人信用卡使用额度和信用卡总透支额度的百分比,本文将这个数据分成五组(mild use、medium use、heavy use、super use、no use)。对之前未在prosper的客户建立库,即其0或NA是未使用过prosper的客户,用no use代替。使用的定义函数如下:
BankcardUtilization数据转换
bank_card_use函数只要将使用额度和信用卡总透支额度的百分比分为五种情况Mild Use(<=0.31);Medium Use(>0.31<=0.6);Heavy Use(>0.6<=1);Super Use(>1),No Use(没有消费)
def bank_card_use(s,oneForth = 0.31,twoForth = 0.6):#根据业务经验认为设置两个阀值
if s<=oneForth:
b='Mild Use'
elif (s>oneForth) & (s<=twoForth):
b='Medium Use'
elif (s>twoForth) & (s<=1):
b='Heavy Use'
elif s>1:
b='Super Use'
else:
b='No Use'
return b
将数据进行转换,设置为BankCardUse变量:
BankcardUtilization的数据转换 quantile--分位数
oneFourth=loanData['BankcardUtilization'].quantile(0.25)#loanData['BankcardUtilization']的四位数是0.31
twoForth=loanData['BankcardUtilization'].quantile(0.5)#loanData['BankcardUtilization']的中位数是0.6
loanData['BankCardUse']=loanData['BankcardUtilization'].apply(bank_card_use)
apply当一个函数的参数存在于一个元组或者一个字典中时,用来间接的调用这个函数,并肩元组或者字典中的参数按照顺序传递给参数
BankcardUtilization的数据转换为BankCardUse字段(来自bank_card_use函数转换逻辑)
4.3 LoanOriginationDate数据转换
因为2009年7月1日是一个数据截点,因此将数据分成两段处理,定义函数如下:
将时间分段
def date_phase(s):
if s>='2009-07-01':
c='After Jul.2009'
else:
c='Before Jul.2009'
return c
将数据进行转换,设置为DatePhase变量:
LoanOriginationDate的数据转换为DatePhase字段(来自date_phase函数转换逻辑)
loanData['DatePhase']=loanData['LoanOriginationDate'].apply(date_phase)
4.4 信用分数转换
在变量中有信用分数两个变量CreditScoreRangeUpper和CreditScoreRangeLower,可以通过信用分数高低范围,将这两个数值取平均值做计算:
对客户的消费信用分数,数据中有高低范围,将这两个数值取平均值做计算
loanData['CreditScore']=((loanData.CreditScoreRangeUpper+loanData.CreditScoreRangeLower)/2).round(0)#.round(0)表示去四舍五入
4.5 TotalProsperLoans
对于使用Prosper平台使用贷款的次数变量TotalProsperLoans,我们可以根据数量区分新老用户,0或NA是未使用过prosper的客户,反之是使用过的,定义的区分函数如下:
0或NA是未使用过prosper的客户,反之是使用过的
def customer_clarify(s):
if s>0:
d='Previous Borrower'
else:
d='New Borrower'
return d
#将数据进行转换,设置为CustomerClarify变量:
#TotalProsperLoans的数据转换为CustomerClarify字段(来自#customer_clarify函数逻辑转换)
loanData['CustomerClarify']=loanData['TotalProsperLoans'].apply(customer_clarify)
五、探索性分析
5.1 年收入越高,违约率越低
通过统计出每个年收入段贷款如期还款笔数和违约笔数,并计算出每段贷款如期还款笔数和违约笔数所占的比例。代码如下:
输出loanStatus和收入的情况分配
loanData[['Status']]incomeRage = loanData.groupby(['Status', 'IncomeRange'])['Status'].count().unstack(0)
在用pandas进行数据重排时,经常用到stack和unstack两个函数。stack的意思是堆叠,堆积,unstack即“不要堆叠”,我对两个函数是这样理解和区分的。
常见的数据的层次化结构有两种,一种是表格,一种是“花括号”,即下面这样的l两种形式:
常见的数据的层次化结构有两种,一种是表格,一种是“花括号”,即下面这样的l两种形式:
index = ['Not displayed', 'Not employed', '$1-24,999', '$25,000-49,999', '$50,000-74,999', '$75,000-99,999', '$100,000+']
incomeRage = incomeRage[['Completed','Defaulted']].reindex(index)#配置索引
#坏账/坏账+正常
defaultRate = (incomeRage['Defaulted'] / (incomeRage['Defaulted'] + incomeRage['Completed'])).reindex(index)#print(defaultRate)#坏账百分之
defaultRate1=defaultRate[['Not displayed','Not employed','$1-24,999','$25,000-49,999','$50,000-74,999','$75,000-99,999','$100,000+',]]#剔除$0为空NaN的值
print(defaultRate1)#坏账百分之
# 违约率情况
y = list(defaultRate1.values)
fig1 = plt.figure(1)
# fig1.set_size_inches(15.5, 7.5)
ax1 = fig1.add_subplot(2, 1, 2)
x = np.arange(len(index)) + 1
ax1.bar(x, y, width=0.4)
ax1.set_xticks(x)
ax1.set_xticklabels(index, rotation=0, fontsize=12)
ax1.set_ylabel('违约百分率(%)', fontsize=14)
for a, b in zip(x, y):
plt.text(a, b + 0.001, '%.2f%%' % (b * 100), ha='center', va='bottom', fontsize=10)
ax2 = fig1.add_subplot(2, 1, 1)
incomeRage.plot(kind='bar', ax=ax2)
ax2.set_xticklabels(index, rotation=0, fontsize=12)
ax2.set_ylabel('数量', fontsize=14)
plt.show()
如图5-1所示,随着年收入的增加,违约比例在逐渐下降。
5.2 负债水平低的借款人违约率低于负债水平高的人
按照常识来说债务收入比(DebtToIncomeRatio)低的人更具备还款能力,违约可能性应该低于债务收入比高的人。
DefaultedRatio=loanData[loanData['Status']=='Defaulted']['DebtToIncomeRatio']
CompletedRatio=loanData[loanData['Status']=='Completed']['DebtToIncomeRatio']
print(DefaultedRatio.shape)
print(CompletedRatio.shape)
ax=CompletedRatio.hist(bins=1000,color='g',label='Compeleted')
ax=DefaultedRatio.hist(bins=1000,color='b',label='Defaulted')
ax.set_xlim([0,1])
plt.xlabel('DebtToIncomeRatio',fontsize=14)
plt.ylabel('数量',fontsize=14)
plt.legend(loc='best')
plt.show()
如图5-2所示,DebtToIncomeRatio < 0.6的借款人中,违约笔数小于未违约的笔数。从下图也可看出大部分借款人的债务收入比低于0.25,说明平台违约的整体风险可控。
5.3 透支比例非常高的人违约概率大
根据BankCardUse与Status,获取信用卡使用情况和违约情况的分布情况,代码实现如下:
# 输出loanStatus和收入的情况分配
bankCardUse= loanData.groupby(['Status','BankCardUse'])['Status'].count().unstack(0)
index=['Mild Use','Medium Use', 'Heavy Use', 'Super Use', 'No Use',]
bankCardUse=bankCardUse[['Completed','Defaulted']].reindex(index)
defaultRate = (bankCardUse['Defaulted'] / (bankCardUse['Defaulted'] + bankCardUse['Completed'])).reindex(index)
print(defaultRate)
# 违约率情况
y = list(defaultRate.values)
fig1 = plt.figure(1)
# fig1.set_size_inches(15.5, 7.5)
ax1 = fig1.add_subplot(2, 1, 2)
x = np.arange(len(index)) + 1
ax1.bar(x, y, width=0.4)
ax1.set_xticks(x)
ax1.set_xticklabels(index, rotation=0, fontsize=12)
ax1.set_ylabel('违约百分率(%)', fontsize=14)
for a, b in zip(x, y):
plt.text(a, b + 0.001, '%.2f%%' % (b * 100), ha='center', va='bottom', fontsize=10)
ax2 = fig1.add_subplot(2, 1, 1)
bankCardUse.plot(kind='bar', ax=ax2)
ax2.set_xticklabels(index, rotation=0, fontsize=12)
ax2.set_xlabel('',fontsize=0)
ax2.set_ylabel('数量', fontsize=14)
plt.show()
如图5-3所示,BankCardUse为Mild Use、Medium Use、Heavy Use时违约率在25%左右,而Super Use、No Use违约率在50%左右,所以,对符合这两种情况的借款人要加强风险管控。
5.4 消费信用分低的借款人违约概率大
消费信用分(CreditScore)是衡量一个人在消费中的经济能力,分值高的人交易更活跃、交易活动违约率更低,所以消费信用分高的人更具备还款能力,违约可能性应该低于消费信用分低的人。
creditScore=pd.DataFrame(loanData.groupby(['Status', 'CreditScore'])['Status'].count().unstack(0))
score=pd.DataFrame(creditScore[['Completed','Defaulted']].values[2:],index=list(creditScore[['Completed','Defaulted']].index)[2:],columns=list(creditScore[['Completed','Defaulted']].columns))
index=list(creditScore[['Completed','Defaulted']].index)[2:]
defaultRate = (score['Defaulted'] / (score['Defaulted'] + score['Completed'])).reindex(index)
print(defaultRate)
ax=score.plot(kind='line',grid=True)
ax.set_xlabel('CreditScore',fontsize=14)
ax.set_ylabel('数量', fontsize=14)
plt.legend(loc='best')
plt.show()
如图5-4所示,CreditScore < 560的借款人中,违约笔数大于未违约的笔数。从下图也可看出大部分借款人的消费信用分高于600分,说明消费信用分低的人在平台上不容易借到钱。
5.5 信用评级高(2009.07.01之前)的人违约概率小
根据CreditGrade与Status,获取2009年7月之前信用等级情况和违约情况的分布情况,代码实现如下:
# 输出loanStatus和收入的情况分配
creditGrade = loanData.groupby(['Status', 'CreditGrade'])['Status'].count().unstack(0)
index=[ 'NC','HR','E','D', 'C','B', 'A', 'AA']
creditGrade=creditGrade[['Completed','Defaulted']].reindex(index)
defaultRate = (creditGrade['Defaulted'] / (creditGrade['Defaulted'] + creditGrade['Completed'])).reindex(index)
# 违约率情况
y = list(defaultRate.values)
fig1 = plt.figure(1)
# fig1.set_size_inches(15.5, 7.5)
ax1 = fig1.add_subplot(2, 1, 2)
x = np.arange(len(index)) + 1
ax1.bar(x, y, width=0.4)
ax1.set_xticks(x)
ax1.set_xticklabels(index, rotation=0, fontsize=12)
ax1.set_xlabel('CreditGrade', fontsize=14)
ax1.set_ylabel('违约百分率(%)', fontsize=14)
for a, b in zip(x, y):
plt.text(a, b + 0.001, '%.2f%%' % (b * 100), ha='center', va='bottom', fontsize=10)
ax2 = fig1.add_subplot(2, 1, 1)
creditGrade.plot(kind='bar', ax=ax2)
ax2.set_xticklabels(index, rotation=0, fontsize=12)
ax2.set_ylabel('数量', fontsize=14)
ax2.set_xlabel('',fontsize=14)
plt.show()
如图5-5所示,CreditGrade评级越高的人违约率越低,大部分借款人的评级都在D级以上。
5.6 信用评级高(2009.07.01之后)的人违约概率小
ProsperRating (Alpha)是2009年之后的信用等级变量,根据ProsperRating (Alpha)与Status,获取2009年7月之后信用等级情况和违约情况的分布情况,代码实现如下:
# 输出CreditGrade','ProsperRating (Alpha)和收入的情况分配
prosperRating= loanData.groupby(['Status', 'ProsperRating (Alpha)'])['Status'].count().unstack(0)
index=[ 'HR','E','D','C','B', 'A', 'AA']
prosperRating=prosperRating[['Completed','Defaulted']].reindex(index)
defaultRate = (prosperRating['Defaulted'] / (prosperRating['Defaulted'] + prosperRating['Completed'])).reindex(index)
# 违约率情况
y = list(defaultRate.values)
fig1 = plt.figure(1)
# fig1.set_size_inches(15.5, 7.5)
ax1 = fig1.add_subplot(2, 1, 2)
x = np.arange(len(index)) + 1
ax1.bar(x, y, width=0.4)
ax1.set_xticks(x)
ax1.set_xticklabels(index, rotation=0, fontsize=12)
ax1.set_xlabel('ProsperRating', fontsize=14)
ax1.set_ylabel('违约百分率(%)', fontsize=14)
for a, b in zip(x, y):
plt.text(a, b + 0.001, '%.2f%%' % (b * 100), ha='center', va='bottom', fontsize=10)
ax2 = fig1.add_subplot(2, 1, 1)
prosperRating.plot(kind='bar', ax=ax2)
ax2.set_xticklabels(index, rotation=0, fontsize=12)
ax2.set_ylabel('数量', fontsize=14)
ax2.set_xlabel('', fontsize=14)
plt.show()
如图5-6所示。
ProsperRating (Alpha)评级越高的人违约率越低,大部分借款人的评级都在D级以上。与2009.07.01之前的信用评级对比去掉了NC级,且整体违约率比之前更低,说明平台的风控模型进行了非常有成效的调整。
5.7 过去七年违约次数多的借款人违约概率大
过去七年违约次数(DelinquenciesLast7Years)能够衡量一个人在过去七年中征信情况,违约一次或以上的人在借款时违约概率更大。
#loanData['CurrentDelinquencies']
#yes=pd.DataFrame(loanData.groupby(['Status', 'CurrentDelinquencies'])['Status'].count().unstack(0))[1:]
def Delinquencies(h):#定义函数目的把连续数据0转为非违约,>=1以上的数据为违约
if h>0:
d='Delinquencies'
else:
d='N0Delinquencies'
return d
#将数据进行转换,设置为CustomerClarify变量:
#CurrentDelinquencies的数据转换为Current_Delinquencies字段(来自Delinquencies函数逻辑转换)
loanData['Current_Delinquencies']=loanData['CurrentDelinquencies'].apply(Delinquencies)
yes_no=pd.DataFrame(loanData.groupby(['Status', 'Current_Delinquencies'])['Status'].count().unstack(0))
yes_no1=yes_no[['Completed','Defaulted']]
#yesno=pd.DataFrame(loanData.groupby(['Status', 'Delinquencies'])['Status'].count().unstack(0))[1:]
delinQuenciest=pd.DataFrame(loanData.groupby(['Status', 'DelinquenciesLast7Years'])['Status'].count().unstack(0))
rating= pd.DataFrame(delinQuenciest[['Completed','Defaulted']].values[0:26], index=list(delinQuenciest[['Completed','Defaulted']].index)[0:26], columns=list(delinQuenciest[['Completed','Defaulted']].columns))
fig1 = plt.figure(1)
ax1 = fig1.add_subplot(2, 1, 1)
rating.plot(kind='line',ax=ax1, grid=True)
ax1.set_ylabel('数量', fontsize=14)
ax1.set_xlabel('7年内的违约次数', fontsize=14)
ax2 = fig1.add_subplot(2, 1, 2)
yes_no1.plot(kind='bar',ax=ax2)
ax2.set_xticklabels(list(yes_no1.index),rotation=0,fontsize=12)
ax2.set_xlabel('7年内有无违约', fontsize=14)
ax2.set_ylabel('数量', fontsize=14)
plt.legend(loc='best')
plt.show()
如图5-7所示,DelinquenciesLast7Years的借款人中,违约笔数与未违约的笔数线贴合。从下图也可看出大部分借款人的DelinquenciesLast7Years 在1次以下,说明整个平台的风险可控。
5.8 受雇佣状态持续时间长的借款人违约概率小
受雇佣状态持续时间(EmploymentStatusDuration)可够衡量一个人工作生活的稳定情况,受雇佣状态持续时间长的违约的概率小。
employmentStatus = pd.DataFrame(loanData.groupby(['Status', 'EmploymentStatusDuration'])['Status'].count().unstack(0))
employmentStatus = pd.DataFrame(employmentStatus[['Completed','Defaulted']].values[0:120], index=list(employmentStatus[['Completed','Defaulted']].index)[0:120],columns=list(employmentStatus[['Completed','Defaulted']].columns))
defaultRate=employmentStatus['Defaulted'] / (employmentStatus['Defaulted'] + employmentStatus['Completed'])
fig1 = plt.figure(1)
ax1 = fig1.add_subplot(2, 1, 1)
employmentStatus.plot(kind='line', grid=True,ax=ax1)
#ax1.set_xlabel('受雇佣状态持续时间(月)', fontsize=14)
ax1.set_ylabel('数量', fontsize=14)
ax2 = fig1.add_subplot(2, 1, 2)
ax2.plot(list(defaultRate.index),list(defaultRate.values),'ko--')
ax2.set_xlabel('受雇佣状态持续时间(月)', fontsize=14)
ax2.set_ylabel('违约百分率(%)', fontsize=14)
plt.legend(loc='best')
plt.show()
如图5-8所示,随着EmploymentStatusDuration的增长,违约概率逐渐减小。
小结
本文主要介绍数据的变量,了解主要变量的意义;根据现有数据变量,对其中几个变量进行转换,便于接下来的处理;进行了描述性分析,初步了解相关变量和违约率的分布情况。
接下来,在网贷平台Prosper2005~2014贷款数据分析(二)中,进行缺失值的处理,2009年前后模型中个变量的重要程度,以及基于2009年后模型对处于借贷状态的还款进行预测。