文章目录
信用风险和评分卡模型的基本概念:信用风险指的是交易对手未能履行约定合同中的义务造成经济损失的风险,即受信人不能履行还本付息的责任而使授信人的预期收益与实际收益发生偏离的可能性,它是金融风险的主要类型。借贷场景中的评分卡是一种以分数的形式来衡量风险几率的一种手段,也是对未来一段时间内违约、逾期、失联概率的预测。一般来说,分数越高,风险越小。
信用风险计量体系包括主体评级模型和债项评级两部分。其中主体评级模型包含以下四个方面内容:
经过分析,已给的4个数据集文件在内容上可以对应上面四个模型,分别是客户信用记录对应申请者评级模型;申请客户信息对应行为评级;拖欠历史记录对应催收评级模型;消费历史记录对应欺诈评级模型。
学生可以从影响用户信用等级的主要因素进行分析,以及结合信用卡用户的人口特征属性对欺诈行为和拖欠行为的影响因素进行分析。通过对银行的客户信用记录、申请客户信息、拖欠历史记录、消费历史记录等数据进行分析,对不同信用程度的客户进行归类,研究信用卡贷款拖欠、信用卡欺诈等问题与客户的个人信息、信用卡使用信息的关系。
学生可以从四个方面建模:申请者评级模型,行为评级模型,催收评级模型,欺诈评级模型,全面分析银行信用卡信用风险。
技术要求:Python、scikit-learn、numpy、pandas、matplotlib等。
数据分析及预处理:对原始数据进行预处理,比如数据清洗,数据标准化,数据编码等。
模型构建:对预处理后的数据进行建模,模型方法不限。
模型评估及优化:对数据进行评估,输出评估结果,并就结果进行分析,提出改进建议。
数据可视化:对数据进行可视化输出,方便客户理解。
一、申请者评级模型
客户信用记录中记录了用户的个人信息,以及银行根据相关信息给出的信用评分和评级,其中有部分内容于预测工作无关,比如“客户号”、“客户姓名”、“户籍”;由于文件中出现的用户都是已经通过了申请的,所以“审批结果”于预测工作没有帮助;文件中“额度”一栏是信用得到的结果,信用越好额度越高,这一栏不是预测工作的目标;“信用总评分”和“信用等级”反应了同一内容,如果以评分作为标签,那么预测工作应该是预测连续值的回归问题,他们的信用总评分就是是否能通过申请的标准。换句话说,信用总评分大于60分的客户可以通过申请。基于这个思路我们可以构建一个线性回归模型,预测每个申请客户的信用总评分来判断是否通过申请。
因此在申请者评级模型中我们用到的数据集为申请客户信息和客户信用记录信息。处理步骤如下:
代码如下(示例):
#申请者评级模型:从申请者与客户信息来进行入手分析
#先导入常用库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from scipy.stats import norm,skew
#这里大概需要总结一下:就是在去重的时候,需要数据一模一样,然而有些虽然相同名字,但是数据有些缺失导致不能进行去重,这个时候需要选择两种办法去解决:
# 1.第一个:就是通过一个独特的信息在大范围的数据中把小范围数据进行筛选出来,然后把需要的特征提取再合并
# 2.第二个:就是先合并,然后把有空数据的行都去掉,之后再进行去除列,有一个问题就是,当有行的索引重复时,就会导致索引错误,从而只能选择第一种方法
#进行数据的导入,简单查看:
customer_credit_info = pd.read_excel('客户信用记录.xlsx',index_col='客户号')
#这里不进行以列名进行排序,是因为后面需要'客户号'进行去重:
customer_applications_info = pd.read_excel('申请客户信息.xlsx')
#这里是进行在申请客户记录中以客户信用记录中的客户号去寻找一样的客户信息:
customer_applications_info = customer_applications_info[customer_applications_info['客户号'].isin(customer_credit_info.index)].loc[:,['客户号','信贷情况']]
#在找到客户相关记录之后,我们发现两条数据重合了,这个时候需要去重,如果前面没有客户号这一列的去重将不会成功:
customer_applications_info.drop_duplicates(inplace=True)
#去重后按'客户号'进行组合为一条数据:
customer_applications_info.set_index(['客户号'],inplace=True)
print(customer_applications_info)
all_info = pd.concat([customer_applications_info,customer_credit_info],axis=1)
all_info.info()
2.删除无用特征和相关性小的特征:
对于审批结果、额度等信息在该阶段不会用到可以删除。我们判断数据与评分的相关性进行删除某些特征数据。对于信用等级、审批结果、额度、年龄、工作年限、姓名等无关特征可直接删除,
#这里有一些没有用的信息,例如审批结果,信用等级,这个信用等级是根据信用总评分来进行判定的,以及额度,在贷款准入环节没有额度,而且我们发现额度与其信用等级的判定有着必然联系:
all_info.drop(['信用等级','审批结果','额度'],axis=1,inplace=True)
all_info.info()
#对于所有数值类型的数据都特征提取完之后,就把所有没有用的特征去掉就好了:
all_info.drop(['年龄_连续','工作年限','个人收入_连续'],axis=1,inplace=True)
#查看一下,特征有没有弄错:
all_info.info()
all_info.head()
剩下的特征进行相关度评分,在进行删除:
#有了那么多特征,我们可以大致来筛选一下看一下他们的相关性:
corrDf = all_info.corr()['信用总评分']
corrDf.sort_values(ascending=False)
#plt.figure(figsize=(25,20))
#corr = sns.heatmap(all_info.corr(),annot=True)
#这里我们看到有两个特别小的,我们就先去掉吧:
#这里我们看到有两个特别小的,我们就先去掉吧:
all_info.drop(['婚姻状态_丧偶','教育程度_大专'],axis=1,inplace=True)
#个人年收入密度分布图
plt.subplot2grid((1,3),(0,2))
all_info.个人收入_连续.plot(kind='kde') #密度图像
plt.xlabel(u"个人年收入")# plots an axis lable
plt.ylabel(u"密度")
plt.title(u"个人年分布")
#对于个人收入来说,横坐标被拉到1500亿,而大部分都集中在趋于零的位置,所以这里要进行分类,这里我们按中国收入阶层划分(http://www.360doc.com/content/20/0101/11/29249721_883480188.shtml)来进行分类:
def 收入分级标准(number):
if number<10000:
return '赤贫'
elif number<30000:
return '下层贫民'
elif number<80000:
return '中层贫民'
elif number<150000:
return '上层贫民'
elif number<300000:
return '低产'
elif number<500000:
return '中产'
elif number<1000000:
return '高产'
elif number<10000000:
return '富人'
elif number<100000000:
return '富豪'
else:
return '大富豪'
个人收入分级 = pd.DataFrame()
个人收入分级['年收入分级'] = all_info['个人收入_连续'].map(收入分级标准)
#print(个人收入分级.分级.value_counts())#看分类是否出错
个人收入分级 = pd.get_dummies(data=个人收入分级,columns=['年收入分级'])
all_info = pd.concat([all_info,个人收入分级],axis=1)
import sklearn.preprocessing as preprocessing
fig=plt.figure(figsize=(15,5),dpi=150)
fig.set(alpha=0.2)
#年龄密度分布图
plt.subplot2grid((1,3),(0,0))
all_info.年龄_连续.plot(kind='kde') #密度图像
plt.xlabel(u"年龄")# plots an axis lable
plt.ylabel(u"密度")
plt.title(u"年龄分布")
#对于年龄来说,分布正常,我们只需要进行数据的标准化就好了:
scaler = preprocessing.StandardScaler()
all_info['年龄_scaled'] = scaler.fit_transform(all_info['年龄_连续'].values.reshape(-1,1))
#工作年限密度分布图
plt.subplot2grid((1,3),(0,1))
all_info.工作年限.plot(kind='kde') #密度图像
plt.xlabel(u"年数")# plots an axis lable
plt.ylabel(u"密度")
plt.title(u"工作年限分布")
#对于工作年限来说,也分布正常,我们像上面一样进行数据的标准化处理:
scaler = preprocessing.StandardScaler()
all_info['工作年限_scaled'] = scaler.fit_transform(all_info['工作年限'].values.reshape(-1,1))
#个人年收入密度分布图
plt.subplot2grid((1,3),(0,2))
all_info.个人收入_连续.plot(kind='kde') #密度图像
plt.xlabel(u"个人年收入")# plots an axis lable
plt.ylabel(u"密度")
plt.title(u"个人年分布")
分析可知,由于部分申请者年收入过高,收入分布过于集中在0周围,这时我们进行离散化处理,根据国家标准将收入分级处理,分为 中产、上层贫民、下层贫民等等
特征变量我们处理的差不多了,我们现在对目标变量进行处理,调整其数值分布,尽量符合正态分布:再次查看数据
#特征变量我们处理的差不多了,我们现在对目标变量进行处理,调整其数值分布,尽量符合正态分布:
sns.distplot(all_info['信用总评分'],fit=norm)
plt.xlabel('信用总评分')
plt.ylabel('频率')
plt.title('评分分布')
fig = plt.figure()
a = stats.probplot(all_info['信用总评分'],plot=plt)
如图,我们看到这个数据分布是一个三峰,甚至可能是四峰的数据,我们要把它变成正态分布,这样在线性回归做的时候能更好的训练出更好的模型:
from sklearn.preprocessing import QuantileTransformer
quantile_transformer = QuantileTransformer(n_quantiles = 300,output_distribution='normal', random_state=0,copy=True)
all_info['信用总评分'] = quantile_transformer.fit_transform(all_info['信用总评分'].values.reshape(-1,1))
#注意在最新版的sklearn中,所有数据都应该是二维矩阵,哪怕本来是一行或者一列,所以这里加了reshape(-1,1)
采取梯度提升决策树以及随机森林模型进行训练,并通过网格调参得到最优参数,并且绘制学习曲线。
#所有工作都已经就绪开始进行模型的建立:
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
#第一种模型:梯度提升决策树:
#网格调参:
# param_grid_GradientBoostingRegressor={'n_estimators':[700,800,900],'learning_rate':[0.005,0.007,0.01],'max_depth':[6,7,8]}
# grid_search_GradientBoostingRegressor=GridSearchCV(GradientBoostingRegressor(),param_grid_GradientBoostingRegressor,cv=5)
# grid_search_GradientBoostingRegressor.fit(X,Y)
# print(grid_search_GradientBoostingRegressor.best_params_,grid_search_GradientBoostingRegressor.best_score_)
GB_model = GradientBoostingRegressor(n_estimators=800,learning_rate=0.01,max_depth=7)
#第二种模型:随机森林:
#网格调参:
# param_grid_RandomForestRegressor={'n_estimators':[300,400,500],'max_depth':[4,5,6]}
# grid_search_RandomForestRegressor=GridSearchCV(RandomForestRegressor(),param_grid_RandomForestRegressor,cv=5)
# grid_search_RandomForestRegressor.fit(X,Y)
# print(grid_search_RandomForestRegressor.best_params_,grid_search_RandomForestRegressor.best_score_)
RFR_model = RandomForestRegressor(n_estimators=300,max_depth=6)
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
DT_model = DecisionTreeClassifier(criterion='entropy',min_samples_leaf = 6,random_state=1)
绘制学习曲线
#进行学习曲线的绘制:
from sklearn.model_selection import learning_curve
# 用sklearn的learning_curve得到training_score和cv_score,使用matplotlib画出learning curve
def plot_learning_curve(estimator, title, X, y, cv=5, n_jobs=-1,train_sizes=np.linspace(.05, 1., 20), verbose=0, plot=True):
train_sizes, train_scores, test_scores = learning_curve(estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, verbose=verbose)
#计算训练与验证集的分数的平均值和方差
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.figure()
plt.title(title)
plt.xlabel(u"训练样本数")
plt.ylabel(u"得分")
plt.grid()
plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, alpha=0.1, color="b")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, alpha=0.1, color="r")
plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label=u"训练集上得分")
plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉验证集上得分")
plt.legend(loc="best")
plt.show()
plot_learning_curve(GB_model,'GB_model 学习曲线',X,Y)
plot_learning_curve(RFR_model,'RFR_model 学习曲线',X,Y)
plot_learning_curve(DT_model,'RFR_model 学习曲线',X,Y)
import graphviz
dot_data = tree.export_graphviz(DT_model, out_file=None,
feature_names=X_train.columns,
class_names=['no default','default'],
filled=True, rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
graph
二、欺诈评级模型,三、行为评级模型,四、催收评级模型 在后续文章更新。首次更新,希望大家能够多加指点。