美文网首页数据分析类
3 P2P网贷信用评分项目分享1

3 P2P网贷信用评分项目分享1

作者: 7125messi | 来源:发表于2018-10-27 15:24 被阅读10次

    1项目介绍

    此项目为kaggle竞赛平台的give me some credits。其目的是预测银行用户违约概率,以辅助银行判断是否要对用户进行放贷。关于风险控制建模的大致流程可参考以下链接:

    此项目提供样本数量多,但变量特征比较少,相比实际业务的开展肯定是远远不够的。但是作为入门风控建模,了解建模开发流程却是个不错的选择。项目拟使用所提供的数据集建立一个申请评分卡(A卡),并可以对用户自动评分。

    其实在实际建模过程中是要结合业务端的,对于好坏用户如何定义?逾期多少DPD算是坏用户?表现期和观察期又是如何定义的?每个公司的业务不一样,面向客户群体也不一样,这些指标在各个公司都不一定是相同的。比如,好坏用户就需要根据滚动率来观察,几期过后逾期率会达到稳定,又如通过账龄分析来定义表现期窗口的时间长度等。

    本项目仅供学习使用,对于业务指标不进行过多考虑,而侧重于建模的技术方面。

    2数据探索

    和之前的套路一样,建模前的数据探索十分重要,发现数据分布特征,数据联系和内在规律等。首先导入数据后观察数据缺失值,异常值,分布规律等。

    # 导入数据集,合并训练数据和测试数据
    data_train = pd.read_csv('cs-training.csv')
    data_test = pd.read_csv('cs-test.csv')
    # df = data_train.append(data_test)
    

    通过观察,含有缺失值的特征有:MonthlyIncomeNumberOfDependents两个。为了方便后面的使用,将特征名称修改成短名称。

    columns = ({'SeriousDlqin2yrs':'IsDlq',
                'RevolvingUtilizationOfUnsecuredLines':'Revol',
               'NumberOfOpenCreditLinesAndLoans':'NumOpen',
               'NumberOfTimes90DaysLate':'Num90late',
               'NumberRealEstateLoansOrLines':'NumEstate',
               'NumberOfTime60-89DaysPastDueNotWorse':'Num60-89late',
               'NumberOfDependents':'NumDependents',
               'NumberOfTime30-59DaysPastDueNotWorse':'Num30-59late'}
              )
    

    这样,非常长的特征名称就便于我们后续操作了。

    好坏比

    plt.figure(figsize=(8,5))
    sns.countplot("IsDlq", data=data_train)
    plt.show()
    
    badNum = data_train.loc[data_train['IsDlq']==1].shape[0]
    goodNum = data_train.loc[data_train['IsDlq']==0].shape[0]
    print('训练集中客户好坏比为:{0}%'.format(round(badNum*100/(goodNum+badNum),2)))
    

    很明显,数据不均衡,坏用户只占了6.68%,后续建模部分进行处理。

    age特征分布

    f,[ax1,ax2]=plt.subplots(1,2,figsize=(20,8))
    sns.distplot(data_train['age'],ax=ax1)
    sns.boxplot(y='age',data=data_train,ax=ax2)
    plt.show()
    

    虽然后续会使用分箱以及woe方法(增加鲁棒性,增强了对异常值干扰),还是常规性的检查一下异常值。

    # 3倍标准差定义异常值
    ageMean = np.mean(data_train['age'])
    ageStd = np.std(data_train['age'])
    ageUpLimit = round((ageMean + 3*ageStd),2)
    ageDownLimit = round((ageMean - 3*ageStd),2)
    print('年龄异常值上限为:{0}, 下限为:{1}'.format(ageUpLimit,ageDownLimit))
    

    年龄异常值上限为:96.61, 下限为:7.98。

    # 四分位距观察异常值
    agePercentile = np.percentile(data_train['age'],[0,25,50,75,100])
    ageIQR = agePercentile[3] - agePercentile[1]
    ageUpLimit = agePercentile[3]+ageIQR*1.5
    ageDownLimit = agePercentile[1]-ageIQR*1.5
    print('年龄异常值上限为:{0}, 下限为:{1}'.format(ageUpLimit,ageDownLimit))
    print('上届异常值占比:{0} %'.format(data_train[data_train['age']>96].shape[0]*100/data_train.shape[0]))
    print('下届异常值占比:{0} %'.format(data_train[data_train['age']<8].shape[0]*100/data_train.shape[0]))
    

    年龄异常值上限为:96.0, 下限为:8.0,上届异常值占比:0.03 %,下届异常值占比:0.00067 %。

    结论

    • 明显观察到有个0岁的客户,这实际上不可能,至少要大于18岁成年以后才可以贷款,故将之移除。

    • 而年龄大于96岁是有可能的,判断是噪声,并不是异常值,因为大于等于96岁的客户有98人,其中最大的年龄为109。

    再看一下age特征对目标变量的影响,将age划分为几个年龄段,然后绘制出各个年龄段的违约率。

    结论:可以看到年龄越大,好坏比越大,说明随着年龄增大,违约的比例逐渐减少。这为我们后面woe分箱提供了参考,呈现了单调性。

    Revol特征

    f,ax=plt.subplots(figsize=(10,5))
    plt.scatter(data_train['Revol'], data_train['age'],color='g')
    plt.show()
    

    结论:这个特征值是百分比。含义是:除了房贷车贷之外的信用卡账面金额(即贷款金额)/信用卡总额度。实际上,这个特征值大部分情况是小于1的,因为超出额度属于透支。但是我们发现有很多特征值已经达到了几万,这在实际中是不可能的。推测很有可能是没有除以分母信用卡额度,而是分子的纯信用卡账面贷款金额。

    我们需要确定的是透支的最大值是什么?即透支多少算是正常值?数值多大可以确认它是没除以分母的异常值?

    观察一下Revol特征各个分段下的分布情况。

    观察到现象:

    • 小于1的分布中,大部分客户都处于0.1的位置,而随着Revol特征值变大,数量成递减趋势。

    • 对于其它大于1的数值分布,也都明显的呈现了递减趋势。

    • 小于1的特征值占总数量的97%,大于1的数量为5531。

    下面来深入研究一下大于1的特征值对坏账率有什么影响,以及找到透支的阈值

    通过上面观察:Revol特征值在10到100之间中,坏账客户的值多在10到20之间,并且其相应的DebtRatio也很高。而其他Revol特征值高(>20)的但DebtRadio低的并不是坏账客户。因此,推测可能的异常值阈值(即透支的上限)在20-30左右。

    下面我们通过具体数据来确定具体的阈值在哪。

    根据观察的现象,我们可以看到:

    0-1之间的坏账率为5.99%。按理说,随着比例升高,坏账率也应该升高,尤其是在透支的情况下。在1-30区间内,已经属于透支状态,坏账率39%,达到了最高。但是透支是不可能无限升高的,会有个阈值。 从30到100区间,坏账率开始下降,坏账率开始下降恢复正常,说明30左右的值(即3000%左右)可能就是正常透支的阈值。

    因此,将数值超过30的都定义为异常值,并将大于30的值与0-1之间合并。

    NumDependents特征

    f,ax=plt.subplots(figsize=(10,5))
    sns.countplot(x='NumDependents',hue='IsDlq',data=data_train,ax=ax)
    plt.show()
    print('Dependents为0的概率为:{0}%'.format(round(data_train[data_train['NumDependents']==0].shape[0]*100/data_train.shape[0],2)))
    

    发现:NumDependents的缺失值为6550个,而NumDependents和MonthlyIncome同时缺失的数量也是6550个。

    结论:

    • 说明NumDependents缺失的样本MonthlyIncome也缺失。

    我们想要通过找相似的方法来填补缺失的Dependents,因为有以上结论,所以我们观察一下MonthlyIncome缺失,但NumDependents不缺失的样本是如何的。

    MonthlyIncome特征

    f,[ax1,ax2]=plt.subplots(1,2,figsize=(20,5))
    sns.kdeplot(data_train['MonthlyIncome'],ax=ax1)
    sns.boxplot(y='MonthlyIncome',data=data_train,ax=ax2)
    plt.show()
    

    Dependents缺失的样本坏账率为:4.56%,Dependents不缺失的样本坏账率为:6.74%。

    由于缺失值占比达到近20%,直接删除会损失数据信息,中位数/平均数进行大量填补效果并不好,这里选择随机森林建模预测缺失值。

    Num30-59 | 60-89 | 90 late特征

    f,[ax,ax1,ax2]=plt.subplots(1,3,figsize=(20,5))
    # sns.boxplot(y=['Num30-59late','Num90late'],data=df,ax=ax)
    ax.boxplot(x=data_train['Num30-59late'])
    ax1.boxplot(x=data_train['Num60-89late'])
    ax2.boxplot(x=data_train['Num90late'])
    ax.set_xlabel('Num30-59late')
    ax1.set_xlabel('Num60-89late')
    ax2.set_xlabel('Num90late')
    plt.show()
    
    data_train.loc[(data_train['Num30-59late']>=8), 'Num30-59late'] = 8
    Num30_59lateDlq = data_train.groupby(['Num30-59late'])['IsDlq'].sum()
    Num30_59lateAll = data_train.groupby(['Num30-59late'])['IsDlq'].count()
    Num30_59lateGroup = Num30_59lateDlq/Num30_59lateAll
    Num30_59lateGroup.plot(kind='bar',figsize=(10,5))
    
    Num30_59lateDf = pd.DataFrame(Num30_59lateDlq)
    Num30_59lateDf['All'] = Num30_59lateAll
    Num30_59lateDf['BadRate'] = Num30_59lateGroup
    Num30_59lateDf
    

    DebtRatio

    同Revol使用的方法一样,由于存在大量的异常值,固也对其进行了分段来分析坏账率的特点。这部分分箱的观察分布对于后续的woe计算转化很有帮助,当然这些特征指标的分箱也要结合实际业务理解来划分。

    结论:将debtratio>2的都视为异常值,并将这些异常值与0-1之间的debtratio分为一组。

    NumEstate特征

    f,[ax1,ax2]=plt.subplots(1,2,figsize=(20,5))
    sns.kdeplot(data_train['NumEstate'],ax=ax1)
    sns.boxplot(y='NumEstate',data=data_train,ax=ax2)
    plt.show()
    

    看到大于50的值为明显异常值。

    data_train.loc[(data_train['NumEstate']>=8), 'NumEstate'] = 8
    NumEstateDlq = data_train.groupby(['NumEstate'])['IsDlq'].sum()
    NumEstateAll = data_train.groupby(['NumEstate'])['IsDlq'].count()
    NumEstateGroup = NumEstateDlq/NumEstateAll
    NumEstateGroup.plot(kind='bar',figsize=(10,5))
    
    NumEstateDf = pd.DataFrame(NumEstateDlq)
    NumEstateDf['All'] = NumEstateAll
    NumEstateDf['BadRate'] = NumEstateGroup
    NumEstateDf
    

    NumOpen特征

    data_train.loc[(data_train['NumOpen']>=36), 'NumOpen'] = 36
    NumOpenDlq = data_train.groupby(['NumOpen'])['IsDlq'].sum()
    NumOpenAll = data_train.groupby(['NumOpen'])['IsDlq'].count()
    NumOpenGroup = NumOpenDlq/NumOpenAll
    NumOpenGroup.plot(kind='bar',figsize=(10,5))
    
    NumOpenDf = pd.DataFrame(NumOpenDlq)
    NumOpenDf['All'] = NumOpenAll
    NumOpenDf['BadRate'] = NumOpenGroup
    NumOpenDf
    

    3总结

    由于特征数量比较少,所以对每个特征都进行了简单的探索。当然这些这些都只是单变量分析,旨在初步了解特征分布特点和一些通用的规律。由于内容较多固设置为一篇介绍。

    下一篇将介绍如何进行介绍:

      1. 如何从做woe转化
      1. 利用iv值进行筛选变量
      1. 变量是如何衍生的
      1. 如何使用auc评估模型
      1. 建模参数调节和样本不均衡处理
      1. 最后又是如何生成相应的评分卡

    相关文章

      网友评论

        本文标题:3 P2P网贷信用评分项目分享1

        本文链接:https://www.haomeiwen.com/subject/yydizftx.html