zoukankan      html  css  js  c++  java
  • 基于Python的信用评分卡模型分析(一)

    信用风险计量体系包括主体评级模型和债项评级两部分。主体评级和债项评级均有一系列评级模型组成,其中主体评级模型可用“四张卡”来表示,分别是A卡、B卡、C卡和F卡;债项评级模型通常按照主体的融资用途,分为企业融资模型、现金流融资模型和项目融资模型等。 我们主要讨论主体评级模型的开发过程。

    一、项目流程

    典型的信用评分模型如图1-1所示。信用风险评级模型的主要开发流程如下:
    (1) 数据获取,包括获取存量客户及潜在客户的数据。存量客户是指已经在证券公司开展相关融资类业务的客户,包括个人客户和机构客户;潜在客户是指未来拟在证券公司开展相关融资类业务的客户,主要包括机构客户,这也是解决证券业样本较少的常用方法,这些潜在机构客户包括上市公司、公开发行债券的发债主体、新三板上市公司、区域股权交易中心挂牌公司、非标融资机构等。
    (2) 数据预处理,主要工作包括数据清洗、缺失值处理、异常值处理,主要是为了将获取的原始数据转化为可用作模型开发的格式化数据。
    (3) 探索性数据分析,该步骤主要是获取样本总体的大概情况,描述样本总体情况的指标主要有直方图、箱形图等。
    (4) 变量选择,该步骤主要是通过统计学的方法,筛选出对违约状态影响最显著的指标。主要有单变量特征选择方法和基于机器学习模型的方法 。
    (5) 模型开发,该步骤主要包括变量分段、变量的WOE(证据权重)变换和逻辑回归估算三部分。
    (6) 模型评估,该步骤主要是评估模型的区分能力、预测能力、稳定性,并形成模型评估报告,得出模型是否可以使用的结论。
    (7) 信用评分,根据逻辑回归的系数和WOE等确定信用评分的方法。将Logistic模型转换为标准评分的形式。
    (8) 建立评分系统,根据信用评分方法,建立自动信用评分系统。


     
    图1-1 信用评分模型开发流程

    PS:有些时候为了便于命名,相应的变量用标号代替

    二、数据获取

    数据来自Kaggle的Give Me Some Credit,有15万条的样本数据,下图可以看到这份数据的大致情况。
    数据属于个人消费类贷款,只考虑信用评分最终实施时能够使用到的数据应从如下一些方面获取数据:
    – 基本属性:包括了借款人当时的年龄。
    – 偿债能力:包括了借款人的月收入、负债比率。
    – 信用往来:两年内35-59天逾期次数、两年内60-89天逾期次数、两年内90
    天或高于90天逾期的次数。
    – 财产状况:包括了开放式信贷和贷款数量、不动产贷款或额度数量。
    – 贷款属性:暂无。
    – 其他因素:包括了借款人的家属数量(不包括本人在内)。
    – 时间窗口:自变量的观察窗口为过去两年,因变量表现窗口为未来两年。

     
    图2-1 原始数据的变量

    三、数据预处理

    在对数据处理之前,需要对数据的缺失值和异常值情况进行了解。Python内有describe()函数,可以了解数据集的缺失值、均值和中位数等。

        #载入数据
        data = pd.read_csv('cs-training.csv')
        #数据集确实和分布情况
        data.describe().to_csv('DataDescribe.csv')
    

    数据集的详细情况:


     
    图3-1 变量详细情况

    从上图可知,变量MonthlyIncome和NumberOfDependents存在缺失,变量MonthlyIncome共有缺失值29731个,NumberOfDependents有3924个缺失值。

    3.1 缺失值处理

    这种情况在现实问题中非常普遍,这会导致一些不能处理缺失值的分析方法无法应用,因此,在信用风险评级模型开发的第一步我们就要进行缺失值处理。缺失值处理的方法,包括如下几种。
    (1) 直接删除含有缺失值的样本。
    (2) 根据样本之间的相似性填补缺失值。
    (3) 根据变量之间的相关关系填补缺失值。
    变量MonthlyIncome缺失率比较大,所以我们根据变量之间的相关关系填补缺失值,我们采用随机森林法:

    # 用随机森林对缺失值预测填充函数
    def set_missing(df):
        # 把已有的数值型特征取出来
        process_df = df.ix[:,[5,0,1,2,3,4,6,7,8,9]]
        # 分成已知该特征和未知该特征两部分
        known = process_df[process_df.MonthlyIncome.notnull()].as_matrix()
        unknown = process_df[process_df.MonthlyIncome.isnull()].as_matrix()
        # X为特征属性值
        X = known[:, 1:]
        # y为结果标签值
        y = known[:, 0]
        # fit到RandomForestRegressor之中
        rfr = RandomForestRegressor(random_state=0, 
        n_estimators=200,max_depth=3,n_jobs=-1)
        rfr.fit(X,y)
        # 用得到的模型进行未知特征值预测
        predicted = rfr.predict(unknown[:, 1:]).round(0)
        print(predicted)
        # 用得到的预测结果填补原缺失数据
        df.loc[(df.MonthlyIncome.isnull()), 'MonthlyIncome'] = predicted
        return df
    

    NumberOfDependents变量缺失值比较少,直接删除,对总体模型不会造成太大影响。对缺失值处理完之后,删除重复项。

        data=set_missing(data)#用随机森林填补比较多的缺失值
        data=data.dropna()#删除比较少的缺失值
        data = data.drop_duplicates()#删除重复项
        data.to_csv('MissingData.csv',index=False)
    

    3.2 异常值处理

    缺失值处理完毕后,我们还需要进行异常值处理。异常值是指明显偏离大多数抽样数据的数值,比如个人客户的年龄为0时,通常认为该值为异常值。找出样本总体中的异常值,通常采用离群值检测的方法。
    首先,我们发现变量age中存在0,显然是异常值,直接剔除:

        # 年龄等于0的异常值进行剔除
        data = data[data['age'] > 0]
    

    对于变量NumberOfTime30-59DaysPastDueNotWorse、NumberOfTimes90DaysLate、NumberOfTime60-89DaysPastDueNotWorse这三个变量,由下面的箱线图图3-2可以看出,均存在异常值,且由unique函数可以得知均存在96、98两个异常值,因此予以剔除。同时会发现剔除其中一个变量的96、98值,其他变量的96、98两个值也会相应被剔除。


     
    图3-2 箱形图

    剔除变量NumberOfTime30-59DaysPastDueNotWorse、NumberOfTimes90DaysLate、NumberOfTime60-89DaysPastDueNotWorse的异常值。另外,数据集中好客户为0,违约客户为1,考虑到正常的理解,能正常履约并支付利息的客户为1,所以我们将其取反。

        #剔除异常值
        data = data[data['NumberOfTime30-59DaysPastDueNotWorse'] < 90]
        #变量SeriousDlqin2yrs取反
        data['SeriousDlqin2yrs']=1-data['SeriousDlqin2yrs']
    

    3.3 数据切分

    为了验证模型的拟合效果,我们需要对数据集进行切分,分成训练集和测试集。

    from sklearn.cross_validation import train_test_split
    
        Y = data['SeriousDlqin2yrs']
        X = data.ix[:, 1:]
        #测试集占比30%
        X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3, random_state=0)
        # print(Y_train)
        train = pd.concat([Y_train, X_train], axis=1)
        test = pd.concat([Y_test, X_test], axis=1)
        clasTest = test.groupby('SeriousDlqin2yrs')['SeriousDlqin2yrs'].count()
        train.to_csv('TrainData.csv',index=False)
        test.to_csv('TestData.csv',index=False)
    

    四、探索性分析

    在建立模型之前,我们一般会对现有的数据进行 探索性数据分析(Exploratory Data Analysis) 。 EDA是指对已有的数据(特别是调查或观察得来的原始数据)在尽量少的先验假定下进行探索。常用的探索性数据分析方法有:直方图、散点图和箱线图等。
    客户年龄分布如图4-1所示,可以看到年龄变量大致呈正态分布,符合统计分析的假设。


     
    图4-1 客户年龄分布

    客户年收入分布如图4-2所示,月收入也大致呈正态分布,符合统计分析的需要。


     
    图4-2 客户收入分布

    五、变量选择

    特征变量选择(排序)对于数据分析、机器学习从业者来说非常重要。好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点、底层结构,这对进一步改善模型、算法都有着重要作用。至于Python的变量选择代码实现可以参考结合Scikit-learn介绍几种常用的特征选择方法
    在本文中,我们采用信用评分模型的变量选择方法,通过WOE分析方法,即是通过比较指标分箱和对应分箱的违约概率来确定指标是否符合经济意义。首先我们对变量进行离散化(分箱)处理。

    5.1 分箱处理

    变量分箱(binning)是对连续变量离散化(discretization)的一种称呼。信用评分卡开发中一般有常用的等距分段、等深分段、最优分段。其中等距分段(Equval length intervals)是指分段的区间是一致的,比如年龄以十年作为一个分段;等深分段(Equal frequency intervals)是先确定分段数量,然后令每个分段中数据数量大致相等;最优分段(Optimal Binning)又叫监督离散化(supervised discretizaion),使用递归划分(Recursive Partitioning)将连续变量分为分段,背后是一种基于条件推断查找较佳分组的算法。
    我们首先选择对连续变量进行最优分段,在连续变量的分布不满足最优分段的要求时,再考虑对连续变量进行等距分段。最优分箱的代码如下:

    # 定义自动分箱函数
    def mono_bin(Y, X, n = 20):
        r = 0
        good=Y.sum()
        bad=Y.count()-good
        while np.abs(r) < 1:
            d1 = pd.DataFrame({"X": X, "Y": Y, "Bucket": pd.qcut(X, n)})
            d2 = d1.groupby('Bucket', as_index = True)
            r, p = stats.spearmanr(d2.mean().X, d2.mean().Y)
            n = n - 1
        d3 = pd.DataFrame(d2.X.min(), columns = ['min'])
        d3['min']=d2.min().X
        d3['max'] = d2.max().X
        d3['sum'] = d2.sum().Y
        d3['total'] = d2.count().Y
        d3['rate'] = d2.mean().Y
        d3['woe']=np.log((d3['rate']/(1-d3['rate']))/(good/bad))
        d4 = (d3.sort_index(by = 'min')).reset_index(drop=True)
        print("=" * 60)
        print(d4)
        return d4
    

    针对我们将使用最优分段对于数据集中的RevolvingUtilizationOfUnsecuredLines、age、DebtRatio和MonthlyIncome进行分类。


     
    图5-1 RevolvingUtilizationOfUnsecuredLines分箱情况.png

     
    图5-2 age分箱情况.png

     
    图5-3 DebtRatio分箱情况.png

     
    图5-4 MonthlyIncome分箱情况.png

    针对不能最优分箱的变量,分箱如下:

        # 连续变量离散化
        cutx3 = [ninf, 0, 1, 3, 5, pinf]
        cutx6 = [ninf, 1, 2, 3, 5, pinf]
        cutx7 = [ninf, 0, 1, 3, 5, pinf]
        cutx8 = [ninf, 0,1,2, 3, pinf]
        cutx9 = [ninf, 0, 1, 3, pinf]
        cutx10 = [ninf, 0, 1, 2, 3, 5, pinf]
    

    5.2 WOE

    WoE分析, 是对指标分箱、计算各个档位的WoE值并观察WoE值随指标变化的趋势。其中WoE的数学定义是:
    woe=ln(goodattribute/badattribute)
    在进行分析时,我们需要对各指标从小到大排列,并计算出相应分档的WoE值。其中正向指标越大,WoE值越小;反向指标越大,WoE值越大。正向指标的WoE值负斜率越大,反响指标的正斜率越大,则说明指标区分能力好。WoE值趋近于直线,则意味指标判断能力较弱。若正向指标和WoE正相关趋势、反向指标同WoE出现负相关趋势,则说明此指标不符合经济意义,则应当予以去除。
    woe函数实现在上一节的mono_bin()函数里面已经包含,这里不再重复。

    5.3 相关性分析和IV筛选

    接下来,我们会用经过清洗后的数据看一下变量间的相关性。注意,这里的相关性分析只是初步的检查,进一步检查模型的VI(证据权重)作为变量筛选的依据。
    相关性图我们通过Python里面的seaborn包,调用heatmap()绘图函数进行绘制,实现代码如下:

        corr = data.corr()#计算各变量的相关性系数
        xticks = ['x0','x1','x2','x3','x4','x5','x6','x7','x8','x9','x10']#x轴标签
        yticks = list(corr.index)#y轴标签
        fig = plt.figure()
        ax1 = fig.add_subplot(1, 1, 1)
        sns.heatmap(corr, annot=True, cmap='rainbow', ax=ax1, annot_kws={'size': 9, 'weight': 'bold', 'color': 'blue'})#绘制相关性系数热力图
        ax1.set_xticklabels(xticks, rotation=0, fontsize=10)
        ax1.set_yticklabels(yticks, rotation=0, fontsize=10)
        plt.show()
    

    生成的图形如图5-5所示:


     
    图5-5 数据集各变量的相关性

    由上图可以看出,各变量之间的相关性是非常小的。NumberOfOpenCreditLinesAndLoans和NumberRealEstateLoansOrLines的相关性系数为0.43。
    接下来,我进一步计算每个变量的Infomation Value(IV)。IV指标是一般用来确定自变量的预测能力。 其公式为:
    IV=sum((goodattribute-badattribute)*ln(goodattribute/badattribute))
    通过IV值判断变量预测能力的标准是:
    < 0.02: unpredictive
    0.02 to 0.1: weak
    0.1 to 0.3: medium
    0.3 to 0.5: strong
    > 0.5: suspicious
    IV的实现放在mono_bin()函数里面,代码实现如下:

    # 定义自动分箱函数
    def mono_bin(Y, X, n = 20):
        r = 0
        good=Y.sum()
        bad=Y.count()-good
        while np.abs(r) < 1:
            d1 = pd.DataFrame({"X": X, "Y": Y, "Bucket": pd.qcut(X, n)})
            d2 = d1.groupby('Bucket', as_index = True)
            r, p = stats.spearmanr(d2.mean().X, d2.mean().Y)
            n = n - 1
        d3 = pd.DataFrame(d2.X.min(), columns = ['min'])
        d3['min']=d2.min().X
        d3['max'] = d2.max().X
        d3['sum'] = d2.sum().Y
        d3['total'] = d2.count().Y
        d3['rate'] = d2.mean().Y
        d3['woe']=np.log((d3['rate']/(1-d3['rate']))/(good/bad))
        d3['goodattribute']=d3['sum']/good
        d3['badattribute']=(d3['total']-d3['sum'])/bad
        iv=((d3['goodattribute']-d3['badattribute'])*d3['woe']).sum()
        d4 = (d3.sort_index(by = 'min')).reset_index(drop=True)
        print("=" * 60)
        print(d4)
        cut=[]
        cut.append(float('-inf'))
        for i in range(1,n+1):
            qua=X.quantile(i/(n+1))
            cut.append(round(qua,4))
        cut.append(float('inf'))
        woe=list(d4['woe'].round(3))
        return d4,iv,cut,woe
    

    生成的IV图代码:

        ivlist=[ivx1,ivx2,ivx3,ivx4,ivx5,ivx6,ivx7,ivx8,ivx9,ivx10]#各变量IV
        index=['x1','x2','x3','x4','x5','x6','x7','x8','x9','x10']#x轴的标签
        fig1 = plt.figure(1)
        ax1 = fig1.add_subplot(1, 1, 1)
        x = np.arange(len(index))+1
        ax1.bar(x, ivlist, width=0.4)#生成柱状图
        ax1.set_xticks(x)
        ax1.set_xticklabels(index, rotation=0, fontsize=12)
        ax1.set_ylabel('IV(Information Value)', fontsize=14)
        #在柱状图上添加数字标签
        for a, b in zip(x, ivlist):
            plt.text(a, b + 0.01, '%.4f' % b, ha='center', va='bottom', fontsize=10)
        plt.show()
    

    输出图像:


     
    图5-6 输出的各变量IV图

    可以看出,DebtRatio、MonthlyIncome、NumberOfOpenCreditLinesAndLoans、NumberRealEstateLoansOrLines和NumberOfDependents变量的IV值明显较低,所以予以删除。

    小结

    本文主要介绍了信用评分模型开发过程中的数据预处理、探索性分析和变量选择。数据预处理主要针对缺失值用随机森林法和直接剔除法进行处理,对于异常值主要根据实际情况和箱形图的数据分布,对异常值进行剔除;探索性分析主要对各变量的分布情况进行初始的探究;变量选择主要考虑了变量的分箱方法,根据分箱结果计算WOE值,然后检查变量之间的相关性,根据各变量的IV值来选择对数据处理有好效果的变量。
    接下来会介绍信用评分模型的模型开发、模型评估和信用评分等。

  • 相关阅读:
    浅谈值对象
    循环一个节点列表(NodeList)或者数组,并且绑定事件处理函数引发对闭包的理解
    当前窗口和Iframe之间的相互访问(图片上传成功后立刻显示在当前页面上)
    网页动态加载图片 通过JS和jquery实现。
    javascript拖动层函数封装
    javascript中变量声明提升(Hoisting)
    运动框架必备的运动算法 留着用!
    CSS3特性之改变在浏览器上选中文字时,默认的背景颜色和文字颜色
    仿淘宝商品图片放大镜效果(鼠标移动上去会出现放大的图片,并且可以移动)
    与PHP交互中文编码的几个函数 decodeURIComponent,encodeURIComponent,encodeURI,decodeURI
  • 原文地址:https://www.cnblogs.com/tan2810/p/10722292.html
Copyright © 2011-2022 走看看