主体评级和债项评级均有一系列评级模型组成,其中主体评级模型可用"四张卡"来表示,分别是A卡、B卡、C卡和F卡;债项评级模型通常按照主体的融资用途,分为企业融资模型、现金流融资模型和项目融资模型等。
我们主要讨论主体评级模型的开发过程。
一.项目流程
典型的信用评分模型下图所示:
信用评分模型开发流程
信用风险评级模型的主要开发流程如下:
1.数据获取
包括获取存量客户及潜在客户的数据。
存量客户是指已经在证券公司开展相关融资类业务的客户,包括个人客户和机构客户;潜在客户是指未来拟在证券公司开展相关融资类业务的客户,主要包括机构客户,这也是解决证券业样本较少的常用方法,这些潜在机构客户包括上市公司、公开发行债券的发债主体、新三板上市公司、区域股权交易中心挂牌公司、非标融资机构等。
2.数据预处理
主要工作包括数据清洗、缺失值处理、异常值处理,主要是为了将获取的原始数据转化为可用作模型开发的格式化数据。
3.探索性数据分析
该步骤主要是获取样本总体的大概情况,描述样本总体情况的指标主要有直方图、箱形图等。
4.变量选择
该步骤主要是通过统计学的方法,筛选出对违约状态影响最显著的指标。主要有单变量特征选择方法和基于机器学习模型的方法。
5.模型开发
该步骤主要包括变量分段、变量的WOE(证据权重)变换和逻辑回归估算三部分。
6.模型评估
该步骤主要是评估模型的区分能力、预测能力、稳定性,并形成模型评估报告,得出模型是否可以使用的结论。
7.信用评分
根据逻辑回归的系数和WOE等确定信用评分的方法。将Logistic模型转换为标准评分的形式。
8.建立评分系统
根据信用评分方法,建立自动信用评分系统。
二.数据获取
数据来自Kaggle的Give Me Some Credit:https://www.kaggle.com/c/GiveMeSomeCredit/data
有15万条的样本数据,下图可以看到这份数据的大致情况:
原始数据的变量
数据属于个人消费类贷款,只考虑信用评分最终实施时能够使用到的数据应从如下一些方面获取数据:
- 基本属性:包括了借款人当时的年龄。
- 偿债能力:包括了借款人的月收入、负债比率。
- 信用往来:两年内35-59天逾期次数、两年内60-89天逾期次数、两年内90天或高于90天逾期的次数。
- 财产状况:包括了开放式信贷和贷款数量、不动产贷款或额度数量。
- 贷款属性:暂无。
- 其他因素:包括了借款人的家属数量(不包括本人在内)。
- 时间窗口:自变量的观察窗口为过去两年,因变量表现窗口为未来两年。
三.数据预处理
在对数据处理之前,需要对数据的缺失值和异常值情况进行了解。
Python内有describe()函数,可以了解数据集的缺失值、均值和中位数等。
#载入数据data = pd.read_csv("cs-training.csv")#数据集确实和分布情况data.describe().to_csv("DataDescribe.csv")
数据集的详细情况:
变量详细情况
从上图可知,变量MonthlyIncome和NumberOfDependents存在缺失:
- MonthlyIncome共有缺失值29731个
- NumberOfDependents有3924个缺失值
1.缺失值处理
这种情况在现实问题中非常普遍,这会导致一些不能处理缺失值的分析方法无法应用,因此,在信用风险评级模型开发的第一步我们就要进行缺失值处理。
缺失值处理的方法,包括如下几种:
- 直接删除含有缺失值的样本
- 根据样本之间的相似性填补缺失值
- 根据变量之间的相关关系填补缺失值
变量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)
2.异常值处理
缺失值处理完毕后,我们还需要进行异常值处理。异常值是指明显偏离大多数抽样数据的数值。
比如个人客户的年龄为0时,通常认为该值为异常值。找出样本总体中的异常值,通常采用离群值检测的方法。
首先,我们发现变量age中存在0,显然是异常值,直接剔除:
# 年龄等于0的异常值进行剔除data = data[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.数据切分
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)
为了验证模型的拟合效果,我们需要对数据集进行切分,分成训练集和测试集。
四.探索性分析
在建立模型之前,我们一般会对现有的数据进行探索性数据分析(Exploratory Data Analysis)。
EDA是指对已有的数据(特别是调查或观察得来的原始数据)在尽量少的先验假定下进行探索。常用的探索性数据分析方法有:直方图、散点图和箱线图等。
客户年龄分布如上图所示,可以看到年龄变量大致呈正态分布,符合统计分析的假设。
客户年收入分布如上图所示,月收入也大致呈正态分布,符合统计分析的需要。
五.变量选择
特征变量选择(排序)对于数据分析、机器学习从业者来说非常重要。好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点、底层结构,这对进一步改善模型、算法都有着重要作用。
在本文中,我们采用信用评分模型的变量选择方法,通过WOE分析方法,即是通过比较指标分箱和对应分箱的违约概率来确定指标是否符合经济意义。首先我们对变量进行离散化(分箱)处理。
1.分箱处理
变量分箱(binning)是对连续变量离散化(discretization)的一种称呼。
信用评分卡开发中一般有常用的等距分段、等深分段、最优分段。
- 等距分段(Equval length intervals):分段的区间是一致的,比如年龄以十年作为一个分段;
- 等深分段(Equal frequency intervals):先确定分段数量,然后令每个分段中数据数量大致相等;
- 最优分段(Optimal Binning):又叫监督离散化(supervised discretizaion),使用递归划分(Recursive Partitioning)将连续变量分为分段,背后是一种基于条件推断查找较佳分组的算法。
我们首先选择对连续变量进行最优分段,在连续变量的分布不满足最优分段的要求时,再考虑对连续变量进行等距分段。
针对我们将使用最优分段对于数据集中的RevolvingUtilizationOfUnsecuredLines、age、DebtRatio和MonthlyIncome进行分类。最优分箱的代码如下:
# 定义自动分箱函数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分箱情况
针对不能最优分箱的变量,分箱如下:
# 连续变量离散化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]
2.WOE
WoE分析, 是对指标分箱、计算各个档位的WoE值并观察WoE值随指标变化的趋势。其中WoE的数学定义是:
woe=ln(goodattribute/badattribute)
在进行分析时,我们需要对各指标从小到大排列,并计算出相应分档的WoE值。
- 其中正向指标越大,WoE值越小;反向指标越大,WoE值越大。
- 正向指标的WoE值负斜率越大,反响指标的正斜率越大,则说明指标区分能力好。
- WoE值趋近于直线,则意味指标判断能力较弱。
- 若正向指标和WoE正相关趋势、反向指标同WoE出现负相关趋势,则说明此指标不符合经济意义,则应当予以去除。
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()
数据集各变量的相关性
由上图可以看出,各变量之间的相关性是非常小的。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]#各变量IVindex=["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))+1ax1.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()
输出图像:
输出的各变量IV图
可以看出,以下几个变量:
- DebtRatio
- MonthlyIncome
- NumberOfOpenCreditLinesAndLoans
- NumberRealEstateLoansOrLines
- NumberOfDependents
变量的IV值明显较低,所以予以删除。
END.
作者:Carl
来源:知乎
本文为转载分享,如侵权请联系后台删除
- 我的微信公众号
- 微信扫一扫
- 我的微信公众号
- 微信扫一扫
评论