信用风险计量体系包括主体评级模型和债项评级两部分。主体评级和债项评级均有一系列评级模型组成,其中主体评级模型可用“四张卡”来表示,分别是A卡、B卡、C卡和F卡;债项评级模型通常按照主体的融资用途,分为企业融资模型、现金流融资模型和项目融资模型等。
A卡,又称为申请者评级模型,主要应用于相关融资类业务中新用户的主体评级,适用于个人和机构融资主体。
B卡,又称为行为评级模型,主要应用于相关融资类业务中存量客户在续存期内的管理,如对客户可能出现的逾期、延期等行为进行预测,仅适用于个人融资主体。
C卡,又称为催收评级模型,主要应用于相关融资类业务中存量客户是否需要催收的预测管理,仅适用于个人融资主体。
F卡,又称为欺诈评级模型,主要应用于相关融资类业务中新客户可能存在的欺诈行为的预测管理,适用于个人和机构融资主体。
我们今天来做一个A卡申请者评级模型,kaggle上比较经典的一个数据集。
一、数据集
数据来源:https://www.kaggle.com/c/GiveMeSomeCredit/data
本案例是kaggle上一个很经典的评分卡案例:give me some credit。
rawdata=pd.read_csv(path+'\\give-me-some-credit-dataset\\cs-training.csv',index_col=[0])
rawdata.head()
为了方便大家理解本数据集每个特征的含义,也为了操作方便,我们将每个特征名字改为中文。
column={'ID':'用户ID',
'SeriousDlqin2yrs':'好坏客户',
'RevolvingUtilizationOfUnsecuredLines':'可用额度比值',
'age':'年龄',
'NumberOfTime30-59DaysPastDueNotWorse':'逾期30-59天笔数',
'DebtRatio':'负债率',
'MonthlyIncome':'月收入',
'NumberOfOpenCreditLinesAndLoans':'信贷数量',
'NumberOfTimes90DaysLate':'逾期90天笔数',
'NumberRealEstateLoansOrLines':'固定资产贷款量',
'NumberOfTime60-89DaysPastDueNotWorse':'逾期60-89天笔数',
'NumberOfDependents':'家属数量'}
rawdata.rename(columns=column,inplace=True)
rawdata.head()
# 查看数据维度
rawdata.shape
# 查看数据类型和缺失值
rawdata.info()
# 查看标签分布
rawdata['好坏客户'].value_counts()
数据维度(150000, 11),“月收入”和“家属数量”有缺失,标签分布0:1=139974:10026。
二、特征工程
2.1 去除重复值
rawdata.drop_duplicates(inplace=True) # 去重
rawdata.index=range(rawdata.shape[0]) # 重置索引
rawdata.shape
去重后数据维度变为(149391, 11)。
2.2 缺失值处理
# 查看缺失值情况
rawdata.isnull().mean()
可以看到,“月收入”缺失达到近20%,“家属数量”缺失较少仅有2.6%的确实。
# 查看缺失值分布形态
null_list=['月收入','家属数量']
for col in null_list:
plt.style.use('bmh')
plt.hist(rawdata[col].dropna().values)
plt.show()
可以看到,数据呈现正偏态。由于“家属数量”缺失较少,我们直接用中位数填充。
rawdata['家属数量'].fillna(rawdata['家属数量'].median(),inplace=True)
“月收入”这个特征对于征信来说非常重要,在这里采用随机森林填补法来填充,即将缺失的特征值作为预测值,将未缺失的“月收入”数据作为训练样本的标签。
# 定义随机森林填补函数
def fill_missing_rf(X,y,to_fill):
"""
使用随机森林填补一个特征的缺失值的函数
参数:
X:要填补的特征矩阵
y:完整的,没有缺失值的标签
to_fill:字符串,要填补的那一列的名称
"""
#构建我们的新特征矩阵和新标签
df = X.copy()
fill = df.loc[:,to_fill]
df = pd.concat([df.loc[:,df.columns != to_fill],pd.DataFrame(y)],axis=1)
#找出我们的训练集和测试集
Ytrain = fill[fill.notnull()]
Ytest = fill[fill.isnull()]
Xtrain = df.iloc[Ytrain.index,:]
Xtest = df.iloc[Ytest.index,:]
#用随机森林回归来填补缺失值
from sklearn.ensemble import RandomForestRegressor as rfr
rfr = rfr(n_estimators=100)
rfr = rfr.fit(Xtrain, Ytrain)
Ypredict = rfr.predict(Xtest)
return Ypredict
用这个函数预测缺失的“月收入”部分,并替换缺失值。
X = rawdata.iloc[:,1:]
y = rawdata['好坏客户']
y_pred = fill_missing_rf(X,y,'月收入')
rawdata.loc[rawdata.loc[:,'月收入'].isnull(),'月收入'] = y_pred
2.3 异常值处理
我们先看下各列的描述性统计。
#描述性统计
rawdata.describe([0.01,0.1,0.25,.5,.75,.9,.99]).T
如果这样看的不明显,完全可以再画出箱线图。
x1=rawdata['可用额度比值']
x2=rawdata['负债率']
x3=rawdata['年龄']
x4=rawdata['逾期30-59天笔数']
x5=rawdata['逾期60-89天笔数']
x6=rawdata['逾期90天笔数']
x7=rawdata['信贷数量']
x8=rawdata['固定资产贷款量']
fig=plt.figure(figsize=(20,15))
ax1=fig.add_subplot(221)
ax2=fig.add_subplot(222)
ax3=fig.add_subplot(223)
ax4=fig.add_subplot(224)
ax1.boxplot([x1,x2])
ax1.set_xticklabels(["可用额度比值","负债率"], fontsize=20)
ax2.boxplot(x3)
ax2.set_xticklabels("年龄", fontsize=20)
ax3.boxplot([x4,x5,x6])
ax3.set_xticklabels(["逾期30-59天笔数","逾期60-89天笔数","逾期90天笔数"], fontsize=20)
ax4.boxplot([x7,x8])
ax4.set_xticklabels(["信贷数量","固定资产贷款量"], fontsize=20)
我们可以先来看描述性统计,“年龄”的最小值居然是0,但是根据我们的常识,小于18岁是不能在银行办理信用卡或是贷款业务的。我们来看下小于18岁的有几行。
rawdata[rawdata['年龄']<18]
会发现仅有“年龄”为0的这一行数据,很显然是异常值,将其删除。
rawdata=rawdata[rawdata['年龄']!=0]
另外,通过箱线图,我们看到三个逾期天数指标(逾期30-59天、逾期60-80天,逾期90天)是存在比较严重的离群值的,这一点我们通过描述性统计也可以加以验证,这三个指标的99%分位数与max相差过大,存在异常。
因为本案例定义逾期90天以上就算作坏客户,我们先查看一下这三个逾期指标超过90天的数据。
rawdata[rawdata['逾期30-59天笔数']>90].shape
rawdata[rawdata['逾期60-89天笔数']>90].shape
rawdata[rawdata['逾期90天笔数']>90].shape
可以看到维度均为(225, 11),可以猜测这三个指标出现异常的情况发生在相同的行。
rawdata[(rawdata['逾期90天笔数']>90) & (rawdata['逾期60-89天笔数']>90) & (rawdata['逾期30-59天笔数']>90)].shape
数据维度也是(225, 11),果然验证了我们的猜想。将异常行删去。
# 删除异常数据
rawdata=rawdata[rawdata.loc[:,'逾期30-59天笔数']<90]
2.4 处理样本不平衡
我们在之前查看过标签分布情况,0:1=139974:10026,是存在严重的样本不平衡的。这是在金融风控中非常常见的,因为会存在严重违约的用户毕竟是少数。
在这里我们采取SMOTE上采样的方法处理数据不平衡。需要导入另一个机器学习库imblearn。
import imblearn
from imblearn.over_sampling import SMOTE
smote=SMOTE(random_state=404)
X,y=smote.fit_sample(df.iloc[:,1:],df.iloc[:,0])
此时我们再查看标签分布会发现数据已经平衡。
pd.Series(y).value_counts()
我们将经过平衡后的数据合并为一个新的数据集。
y=y.reshape(-1,1)
data=np.concatenate((y,X),axis=1)
data=pd.DataFrame(data,columns=df.columns)
data.head()
三、分箱
常用的分箱方法主要有三种:等频分箱、等宽分箱、CART树分箱。我们选择最优的CART树分箱方法,将分箱的函数都写在一个cart_bins.py的类文件里,下面贴出了最重要的一个自动分箱函数。
def auto_bins(df,y,x,p=0.01,max_bin=20,plot=True):
"""
参数
df:传入需要进行分箱的数据集
y:标签名
x:进行分箱的特征名
p:叶子包含观测数的最小占比(0<p<0.5),默认为0.01
max_bin:最大箱子数(大于2),默认为20
plot:是否画WOE图
"""
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
import numpy as np
import pandas as pd
# 指明X和Y变量数据,排除NA值
X = np.array(df[x][df[x].notna()]).reshape(-1,1)
Y = df[y][df[x].notna()]
# 调参,得到最合适的叶节点观测占比
gini_impure = np.linspace(0,0.001,50)
param_grid = {"min_impurity_decrease":gini_impure}
Es = GridSearchCV(DecisionTreeClassifier(),param_grid,cv=5,iid=False)
Es.fit(X,Y)
# 使用最优参数构建树模型
ES = DecisionTreeClassifier(min_impurity_decrease = Es.best_params_["min_impurity_decrease"],
max_leaf_nodes = max_bin,
min_samples_leaf = p)
ES.fit(X,Y)
# 输出树结构
TS = tree_structure(ES,x)
# 抽取测试节点的阈值
TH = ES.tree_.threshold[ES.tree_.children_left != ES.tree_.children_right]
# 计算最优分割点
CP = np.append(TH,np.array([X.max(),X.min()]))
CP.sort()
# 使用自定义分箱函数
smbin_cust = smbin_cu(df,y,x,cutpoints = CP,plot = plot)
out_a = pd.Series({"Tree_structure":TS})
out = smbin_cust.append(out_a)
return(out)
我们可以来看下几个特征分箱后的WOE图。
# 可用额度比值
cut1 = auto_bins(data,'好坏客户','可用额度比值')
# 年龄
cut2 = auto_bins(data,'好坏客户','年龄')
# 月收入
cut3 = auto_bins(data,'好坏客户','月收入')
# 信贷数量
cut4 = auto_bins(data,'好坏客户','信贷数量')
# 负债率
cut5 = auto_bins(data,'好坏客户','负债率')
# 固定资产贷款量
cut6 = auto_bins(data,'好坏客户','负债率')
# 逾期30-59天笔数
cut7 = auto_bins(data,'好坏客户','逾期30-59天笔数')
# 逾期60-89天笔数
cut8 = auto_bins(data,'好坏客户','逾期60-89天笔数')
# 逾期90天笔数
cut9 = auto_bins(data,'好坏客户','逾期90天笔数')
# 家属数量
cut10 = auto_bins(data,'好坏客户','家属数量')
四、LR建模
金融风控中最常用的模型是逻辑回归模型,虽然现在很多企业开始使用性能更好的GBDT、XGBoost,甚至更加改进的Light GBM进行建模,但LR有着它自己独特的优势:
1、逻辑回归经过信贷历史的反复验证是有效的
2、模型比较稳定相对成熟
3、建模过程透明而不是黑箱
4、不太容易过拟合
本案例选择LR逻辑回归进行建模。
首先切分训练集和测试集。
from sklearn.model_selection import train_test_split
X_train,X_test,Y_train,Y_test = train_test_split(X,Y,test_size=0.3,random_state=1221)
用statsmodels库建模。
import statsmodels.api as sm
from statsmodels.stats.outliers_influence import variance_inflation_factor
import scipy.stats as stats
glmodel = sm.GLM(Y_train,X_train,family=sm.families.Binomial()).fit()
glmodel.summary()
一般评分卡模型中常用ROC曲线和KS曲线来评价模型的好坏。
可以看到,AUC值为0.86,KS最大值为0.5631,模型效果还可以。
五、生成评分卡
最后生成我们的评分卡。
scorecard = smscale(glmodel,x_list,pdo=43,score=1150,odds=10)