一、背景介绍
信用风险计量体系包括主体评级模型和债项评级两部分。
主体评级和债项评级均有一系列评级模型组成,其中主体评级模型可用“四张卡”来表示,分别是A卡(贷前申请评分卡)、B卡(贷中行为评分)、C卡(贷后评分卡)和F卡(贷前反欺诈评分卡);债项评级模型通常按照主体的融资用途,分为企业融资模型、现金流融资模型和项目融资模型等。 我们主要讨论主体评级模型的开发过程。
二、项目流程
主要开发流程如下:
1)数据获取,包括机构客户和个人客户;
2)数据预处理,包括数据清洗、缺失值处理、异常值处理;
3)探索性数据分析,获取样本总体的大概情况,描述样本总体情况的指标主要有直方图、箱型图等;
4)变量选择,该步骤主要通过统计学的方法,筛选出对违约状态影响最显著的指标。主要有单变量特征选择方法和基于机器学习模型的方法;
5)模型开发,该步骤主要包括变量分段、变量的WOE(证据权重)变换和逻辑回归估算三部分;
6)模型评估,该步骤主要是评估模型的区分能力、预测能力、稳定性,并形成模型评估报告,得出模型是否可以使用的结论;
7)信用评分,根据逻辑回归的系数和WOE等确定信用评分的方法。将logistic模型转换为标准评分的形式;
8)建立评分系统,根据信用评分方法,简历自动信用评分系统;
三、数据集介绍
以下给出该数据集的前三行,是一个信用卡数据集,各个特征及其含义已给出
| SeriousDlqin2yrs | RevolvingUtilizationOfUnsecuredLines | age | NumberOfTime30-59DaysPastDueNotWorse | DebtRatio | MonthlyIncome | NumberOfOpenCreditLinesAndLoans | NumberOfTimes90DaysLate | NumberRealEstateLoansOrLines | NumberOfTime60-89DaysPastDueNotWorse | NumberOfDependents |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 0.766126609 | 45 | 2 | 0.802982 | 9120 | 13 | 0 | 6 | 0 | 2 |
| 0 | 0.957151019 | 40 | 0 | 0.121876201 | 2600 | 4 | 0 | 0 | 0 | 1 |
| 0 | 0.65818014 | 38 | 1 | 0.085113375 | 3042 | 2 | 1 | 0 | 0 | 0 |
SeriousDlqin2yrs:是否是好客户,其中好客户为0,违约客户为1
RevolvingUtilizationOfUnsecuredLines:无担保循环信贷额度使用率
age :年龄
NumberOfTime30-59DaysPastDueNotWorse:逾期30-59天且未恶化次数
DebtRatio :负债比率
NumberOfOpenCreditLinesAndLoans:未结清信贷额度与贷款数量
NumberOfTimes90DaysLate:逾期90天的次数
NumberRealEstateLoansOrLines:房地产贷款或额度数量
NumberOfTime60-89DaysPastDueNotWorse:逾期60-89天次数未恶化
NumberOfDependents:抚养人数
可以看到labelY即是SeriousDlqin2yrs,我们需要用剩余的特征去预测一个样本是否延期。
3.0 特征解读
1、RevolvingUtilizationOfUnsecuredLines无担保循环信贷额度使用率
计算公式:使用率 = (所有循环信贷账户的当前总欠款 / 所有循环信贷账户的总授信额度) × 100%
使用率高低有什么影响?
- 对个人信用评分
- 高使用率(>30%):会被解读为过度依赖信贷、财务状况紧张,从而拉低信用评分。银行会认为你的违约风险较高;
- 低使用率(<30%):表明你管理信贷的能力良好,是负责人借款人的信号,有助于提升信用评分。
- 对带宽审批和额度管理
- 影响新贷款审批:即使未使用的额度不计入负债,但银行在审批房贷、车贷时,会关注你的总授信和近期平均使用率。过高的手心总额或过多的信贷账户,都可能让银行认为你“资金紧张”而变得谨慎。
- 触发风控导致“锁额”:如果每次都把额度用尽,风控系统会判定“资金缺口过大,风险过高”,可能导致额度被冻结或降低。相反,如果额度长期闲置不用,平台也可能判定你需求低而主动降低额度。
该数据是数值型数据,以下展示其训练集的分布
2、逾期指标
- NumberOfTime30-59DaysPastDueNotWorse (30-59天):衡量轻微、早期的逾期行为;
- NumberOfTime60-89DaysPastDueNotWorse (60-89天):衡量中等程度的逾期行为;
- NumberOfTimes90DaysLate (90天及以上):衡量严重的逾期行为,这通常被直接定义为“坏账”或“违约”的代理变量。
与M0\M1\M2的对应关系如下:
| 行业术语 | 对应逾期天数 | 本项目特征名称 |
|---|---|---|
| M0 | 无逾期/逾期1-29天 | - |
| M1 | 逾期30-59天 | NumberOfTime30-59DaysPastDueNotWorse |
| M2 | 逾期60-89天 | NumberOfTime60-89DaysPastDueNotWorse |
| M3 | 逾期90-119天 | NumberOfTimes90DaysLate |
其他的指标比如DebtRatio负债比率、NumberOfOpenCreditLinesAndLoans未结清信贷额度与贷款数量、NumberRealEstateLoansOrLines房地产贷款或额度数量、NumberOfDependents受抚养人数较好理解,此处不做过多赘述。
3.1探索性数据分析
拿到一个数据集,我习惯是先看看整个数据的大小、分布、以及缺失值情况
3.1.1数据集基本信息
(0)数据读取
#载入数据data=pd.read_csv('cs-training.csv')(1)数据集大小
""" 探索性数据分析 """data.shape data.columns data.info()3.2 数据清洗
3.2.1 缺失值处理
""" 缺失值 """# 判断哪些列有空值data.isnull().any()# 每列空值的数量data.isnull().sum()可以看到MonthlyIncome、NumberOfDependents有空值。
由于NumberOfDependents缺失样本仅占整个训练集的2.6%,故直接删除NumberOfDependents缺失的样本。
接下来采用随机森林的方法对MonthlyIncome进行缺失值补全
# 用随机森林对缺失值预测填充函数defset_missing(df):# 把已有的数值型特征取出来process_df=df.iloc[:,[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']=predictedreturndf data=set_missing(data)#用随机森林填补比较多的缺失值data=data.dropna()#删除比较少的缺失值data=data.drop_duplicates()#删除重复项data.to_csv('MissingData.csv',index=False)3.2.2 异常值处理
缺失值处理完毕后,我们还需要进行异常值处理。异常值是指明显偏离大多数抽样数据的数值,比如个人客户的年龄为0时,通常认为该值为异常值。找出样本总体中的异常值,通常采用离群值检测的方法。
首先,我们发现变量age中存在0,显然是异常值,直接剔除:
# 年龄等于0的异常值进行剔除data=ata[data['age']>0]对于变量NumberOfTime30-59DaysPastDueNotWorse、NumberOfTimes90DaysLate、NumberOfTime60-89DaysPastDueNotWorse这三个变量,均存在异常值,且由unique函数可以得知均存在96、98两个异常值,因此予以剔除。同时会发现剔除其中一个变量的96、98值,其他变量的96、98两个值也会相应被剔除。
剔除变量NumberOfTime30-59DaysPastDueNotWorse、NumberOfTimes90DaysLate、NumberOfTime60-89DaysPastDueNotWorse的异常值。另外,数据集中好客户为0,违约客户为1,考虑到正常的理解,能正常履约并支付利息的客户为1,所以我们将其取反。
#剔除异常值data=data[data['NumberOfTime30-59DaysPastDueNotWorse']<90]#变量SeriousDlqin2yrs取反data['SeriousDlqin2yrs']=1-data['SeriousDlqin2yrs']3.2.3 数据切分
from sklearn.cross_validation import train_test_splitY=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)3.3 探索性数据分析
在建立模型前,通常会对现有数据进行探索性数据分析,这样可以帮助我们初步了解数据分布,在尽量少的先验假定下进行探索。常用的探索性数据分析方法有:直方图、散点图、箱线图等。
以上是对客户年龄分布的可视化,可以看到大体是呈现正态分布的。
3.4变量选择
3.4.1分箱处理
变量分箱是连续变量离散化的一种方法。信用评分卡开发中一般常用等距分段、等深分段和最优分段:
- 等距分段:分段的区间宽度一致,比如年龄以十年为一个分段
- 等深分段:先确定分段数量,然后令每个分段中的样本数量大致相等
- 最优分段(监督离散化):利用目标变量的信息,将连续变量转化为能最大化其预测能力的离散变量
等距分段和等深分段很好理解,此处着重介绍最优分段。 - 常见的最优分段算法
- 基于决策树的方法:利用决策树的生长和剪枝过程来寻找最佳分割点,其中最小描述长度原则是常用的一种,它能自动平衡模型复杂度与拟合效果,避免过拟合。
- 基于合并的方法:先进行非常细致的预分箱,然后根据某种准则(如卡方检验的P值)迭代地合并相邻箱子,直到满足停止条件。
- 最优化方法:将分箱问题转化为一个有约束的数学优化问题,通过算法(如分支定界法)搜索在满足约束(如单调性)下使用目标函数(如信息值Ⅳ)最大的分箱方案。
- 为什么使用最优分箱
- 提升模型预测能力:可更好捕捉特征与目标之间的非线性关系
- 增强模型可解释性
- 处理数据异常值
- 满足特定弄醒要求
在本项目中,首先选择对连续变量进行最优分段,在连续变量的分布不满足最优分段的要求时,再考虑对连续变量进行等距分段
# 定义自动分箱函数defmono_bin(Y,X,n=20):r=0good=Y.sum()bad=Y.count()-goodwhilenp.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-1d3=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'))print("="*60)print(d4)cut=[]cut.append(float('-inf'))foriinrange(1,n+1):qua=X.quantile(i/(n+1))cut.append(round(qua,4))cut.append(float('inf'))woe=list(d4['woe'].round(3))returnd4,iv,cut,woe此段代码用于分箱的核心是通过暴力搜索,直到分箱后的X和分箱后的Y的Spearman相关系数的绝对值为1。之后,其关键的几个步骤是:
1)计算证据权重WOE
若指标越大代表风险越低(资质越好),即指标越大,好客户越多,则WOE随指标增大而增大;若指标越大代表风险越大(资质越差),即指标越大,好客户越少,则WOE随指标增大而减小。
| 趋势特征 | 含义 | 说明 |
|---|---|---|
| 斜率的绝对值越大 | 区分能力越强 | 指标值的小幅变化就能引起WOE的大幅波动,说明该指标能很好区分好/坏客户 |
| 斜率小或接近水平 | 区分能力弱 | WOE值几乎不随指标变化,说明无论指标取值如何,客户风险分布差不多,这个指标没用 |
| 近似直线 | 通常较好 | 表示指标与风险之间存在稳定的线性关系,便于评分卡解释。若斜率太小,则区分力弱 |
2)计算信息值IV
IV值的作用:衡量特征“含金量”的最直接标尺,可以根据数值大小将特征划分为不同等级,直接决定是否剔除该变量。
| IV值范围 | 预测能力 | 处理建议 |
|---|---|---|
| < 0.02 | 无预测能力 | 直接剔除 |
| 0.02~0.10 | 弱预测能力 | 保留备用 |
| 0.10~0.30 | 中等预测能力 | 有效可用,常用于模型组合 |
| 0.30~0.50 | 强预测能力 | 优质特征,模型首选 |
| > 0.50 | 预测能力极强 | 建议谨慎/暂缓(可能存在过拟合或数据泄露风险,需人工审查) |
- 注意事项
- IV值只能用于二分类
- 分箱数量影响IV值:分箱越多,IV值越高(易过拟合)。因此计算IV值时,必须先进行合理的预分箱,不能用原始值直接算
- 极端IV值(>0.5)往往有问题
我们将数据集中RevolvingUtilizationOfUnsecuredLines到NumberOfDependents这10个自变量分别定义为x1-x10,通过以下代码计算他们的IV值并绘制条形图:
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()
根据前述基于IV值筛选变量的标准,删除了IV值小于0.1的5个变量,即DebtRatio、MonthlyIncome、NumberOfOpenCreditLinesAndLoans、NumberRealEstateLoansOrLines、NumberOfDependents。好坏用户居然跟负债率和月收入的关系较低,很出乎意料!
四、模型分析
4.1 WOE转换
# 替换成woecol_names=['RevolvingUtilizationOfUnsecuredLines','age','NumberOfTime30-59DaysPastDueNotWorse','DebtRatio','MonthlyIncome','NumberOfOpenCreditLinesAndLoans','NumberOfTimes90DaysLate','NumberRealEstateLoansOrLines','NumberOfTime60-89DaysPastDueNotWorse','NumberOfDependents']cut_list=[cutx1,cutx2,cutx3,cutx4,cutx5,cutx6,cutx7,cutx8,cutx9,cutx10]woe_list=[woex1,woex2,woex3,woex4,woex5,woex6,woex7,woex8,woex9,woex10]forcol,cut,woeinzip(col_names,cut_list,woe_list):data[col]=pd.cut(data[col],bins=cut,labels=woe,include_lowest=True)data.to_csv('WoeData.csv',index=False)4.2 逻辑回归模型建立
# 逻辑回归模型建立importstatsmodels.apiassm data=pd.read_csv('WoeData.csv')#应变量Y=data['SeriousDlqin2yrs']#自变量,剔除对因变量影响不明显的变量X=data.drop(['SeriousDlqin2yrs','DebtRatio','MonthlyIncome','NumberOfOpenCreditLinesAndLoans','NumberRealEstateLoansOrLines','NumberOfDependents'],axis=1)X1=sm.add_constant(X)logit=sm.Logit(Y,X1)result=logit.fit()print(result.summary())
由上图知,逻辑回归各变量都通过显著性检验,满足要求