利用Python进行信用评分

项目简介

信用风险:未履行合同的义务而造成的经济损失的风险。
评分卡:以分数的形式来衡量风险几率的一种手段,分数越高越安全。

数据来源:Kaggle

有15万条的样本数据,

– 基本属性:包括了借款人当时的年龄。
– 偿债能力:包括了借款人的月收入、负债比率。
– 信用往来:两年内35-59天逾期次数、两年内60-89天逾期次数、两年内90天或高于90天逾期的次数。
– 财产状况:包括了开放式信贷和贷款数量、不动产贷款或额度数量。
– 贷款属性:暂无。
– 其他因素:包括了借款人的家属数量(不包括本人在内)。
– 时间窗口:自变量的观察窗口为过去两年,因变量表现窗口为未来两年。

标号变量标签变量解释
x0SeriousDlqin2yrs好客户和坏客户
x1RevolvingUtilizationOfUnsecuredLines无担保放款的循环利用
x2age借款人借款时的年龄
x3NumberOfTime30-59DaysPastDueNotWorse35-59天逾期但不糟糕次数
x4DebtRatio负债比率
x5MonthlyIncome月收入
x6NumberOfOpenCreditLinesAndLoans开放式信贷和贷款数量
x7NumberOfTimes90DaysLate90天逾期次数
x8NumberRealEstateLoansOrLines不动产贷款或额度数量
x9NumberOfTime60-89DaysPastDueNotWorse60-89天逾期但不糟糕次数
x10NumberOfDependents家属数量
import numpy as np
import matplotlib
import matplotlib.pyplot as plt 
plt.rcParams['font.sans-serif'] =['Microsoft YaHei']
plt.rcParams['axes.unicode_minus']=False
import seaborn as sns 
import pandas as pd 
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve, auc
import os 
os.chdir(r'\信用评分\GiveMeSomeCredit')
df=pd.read_csv('cs-training.csv')
states={"Unnamed: 0":"用户ID",
        "SeriousDlqin2yrs":"好坏客户",
        "RevolvingUtilizationOfUnsecuredLines":"可用额度比值",
        "age":"年龄",
        "NumberOfTime30-59DaysPastDueNotWorse":"逾期30-59天笔数",
        "DebtRatio":"负债率",
        "MonthlyIncome":"月收入",
        "NumberOfOpenCreditLinesAndLoans":"信贷数量",
        "NumberOfTimes90DaysLate":"逾期90天笔数",
        "NumberRealEstateLoansOrLines":"固定资产贷款量",
        "NumberOfTime60-89DaysPastDueNotWorse":"逾期60-89天笔数",
        "NumberOfDependents":"家属数量"}
df.rename(columns=states,inplace=True)
df.head()

在这里插入图片描述

数据预处理

df.info()

在这里插入图片描述

def missing_values_table(df):
    #全部缺失值
    mis_val =df.isnull().sum()
    #缺失值比例
    mis_val_percent =100*df.isnull().sum()/len(df)
    #能成一个表
    mis_val_table=pd.concat([mis_val,mis_val_percent],axis=1)
    #改列名
    mis_val_table_ren_columns=mis_val_table.rename(
                               columns={0:'缺失值',1:'缺失比例'})
    #对缺失值排序
    mis_val_table_ren_columns=mis_val_table_ren_columns[mis_val_table_ren_columns.iloc[:,1]!=0]\
                              .sort_values('缺失比例',ascending=False).round(1)
    #打印出表
    print("数据有"+str(df.shape[1])+"列.\n"
    "其中"+str(mis_val_table_ren_columns.shape[0])+
    "列含有缺失值.")
    #返回确实行列
    return mis_val_table_ren_columns #显示缺失值和表示缺失值函数
missing_values_table(df)

在这里插入图片描述

缺失值

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

df=df[df.家属数量.notnull()].drop_duplicates()

“月收入”缺失率比较大,采用随机森林法,根据变量之间的相关关系填补缺失值

# 用随机森林对缺失值预测填充函数
def set_missing(df):
    # 把已有的数值型特征取出来
#     process_df = df.iloc[:,1:]
    process_df = df.iloc[:,[6,1,2,3,4,5,7,8,9,10,11]]
    # 分成已知该特征和未知该特征两部分
    known = process_df[process_df.月收入.notnull()].values
    unknown = process_df[process_df.月收入.isnull()].values
    # 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.月收入.isnull()), '月收入'] = predicted
    return df

df=set_missing(df)

异常值

缺失值处理完毕后,我们还需要进行异常值处理。异常值是指明显偏离大多数抽样数据的数值,比如个人客户的年龄为0时,通常认为该值为异常值。这里用盖帽法处理。
盖帽法:整行替换数据框里99%以上和1%以下的点,将99%以上的点值=99%的点值;小于1%的点值=1%的点值。

df.年龄.hist(bins=50)

在这里插入图片描述

#盖帽法
def blk(floor,root):
    def f(x):
        if x<floor:
            x=floor
        elif x>root:
            x=root
        return x
    return f

q1=df.年龄.quantile(0.01)
q99=df.年龄.quantile(0.99)
blk_tot=blk(floor=q1,root=q99)

df.年龄=df.年龄.map(blk_tot)
df.年龄.hist(bins=50)

在这里插入图片描述

探索性分析

单变量分析

age_cut=pd.cut(df.年龄,5)
age_cut_grouped=df['好坏客户'].groupby(age_cut).count()
age_cut_grouped1=df['好坏客户'].groupby(age_cut).sum()

df2=pd.merge(pd.DataFrame(age_cut_grouped), pd.DataFrame(age_cut_grouped1),right_index=True, left_index=True)
df2.rename(columns={'好坏客户_x':'总客户', '好坏客户_y':'坏客户'},inplace=True)
df2.insert(2,'坏客户率',df2['坏客户']/df2['总客户'])

ax1=df2[['总客户','坏客户']].plot.bar()
ax1.set_xticklabels(df2.index,rotation=15)
ax1.set_ylabel('客户数')
ax1.set_title('年龄与好坏客户数分布图')

在这里插入图片描述

ax11=df2['坏客户率'].plot()
ax11.set_ylabel('坏客户率')
ax11.set_title('坏客户率随年龄的变化趋势图')

在这里插入图片描述

多变量分析

corr = df.iloc[:,1:].corr() #计算各变量的相关性系数
xticks = list(corr.index)  #x轴标签
yticks = list(corr.index)  #y轴标签
fig = plt.figure(figsize=(10,10))
ax1 = fig.add_subplot(1, 1, 1)

sns.heatmap(corr,annot=True,cmap="rainbow",ax=ax1,linewidths=.5,annot_kws={'size':9,'weight':'bold', 'color':'k'})
ax1.set_xticklabels(xticks, rotation=30, fontsize=10)
ax1.set_yticklabels(yticks, rotation=0, fontsize=10)
plt.show()

在这里插入图片描述

WOE值替换和LR建模

woe全称叫Weight of Evidence,常用在风险评估、授信评分卡等领域。
IV全称是Information value,可通过woe加权求和得到,衡量自变量对应变量的预测能力。
WOE的计算公式是:ln[(违约/总违约)/(正常/总正常)]。
W O E i = l n ( B a d i B a d T / G o o d i G o o d T ) = l n ( B a d i B a d T ) − l n ( G o o d i G o o d T ) WOE_i=ln(\frac{Bad_i}{Bad_T} / \frac{Good_i}{Good_T})=ln(\frac{Bad_i}{Bad_T})-ln( \frac{Good_i}{Good_T}) WOEi=ln(BadTBadi/GoodTGoodi)=ln(BadTBadi)ln(GoodTGoodi)

I V i = ( B a d i B a d T − G o o d i G o o d T ) ∗ W O E i IV_i=(\frac{Bad_i}{Bad_T} -\frac{Good_i}{Good_T})* WOE_i IVi=(BadTBadiGoodTGoodi)WOEi

I V = ∑ i = 1 n ( I V i ) IV=\displaystyle \sum_{i=1}^n(IV_i) IV=i=1n(IVi)

pinf = float('inf') #正无穷大
ninf = float('-inf') #负无穷大

cut1=pd.qcut(df['可用额度比值'],4)
cut2=pd.qcut(df['年龄'],8)
bins3=[ninf, 0, 1, 3, 5, pinf]
cut3=pd.cut(df['逾期30-59天笔数'],bins3)
cut4=pd.qcut(df['负债率'],3)
cut5=pd.qcut(df['月收入'],4)
cut6=pd.qcut(df['信贷数量'],4)
bins7=[ninf, 0, 1, 3, 5, pinf]
cut7=pd.cut(df['逾期90天笔数'],bins7)
bins8=[ninf, 0,1,2, 3, pinf]
cut8=pd.cut(df['固定资产贷款量'],bins8)
bins9=[ninf, 0, 1, 3, pinf]
cut9=pd.cut(df['逾期60-89天笔数'],bins9)
bins10=[ninf, 0, 1, 2, 3, 5, pinf]
cut10=pd.cut(df['家属数量'],bins10)
#好坏客户比率
rate=df['好坏客户'].sum()/(df['好坏客户'].count()-df['好坏客户'].sum())

#定义woe计算函数
def get_woe_data(cut):
    grouped=df['好坏客户'].groupby(cut,as_index = True).value_counts()
    woe=np.log(pd.DataFrame(grouped).unstack().iloc[:,1]/pd.DataFrame(grouped).unstack().iloc[:,0]/rate) #计算每个分组的woe值
    return(woe)

cut1_woe=get_woe_data(cut1)
cut2_woe=get_woe_data(cut2)
cut3_woe=get_woe_data(cut3)
cut4_woe=get_woe_data(cut4)
cut5_woe=get_woe_data(cut5)
cut6_woe=get_woe_data(cut6)
cut7_woe=get_woe_data(cut7)
cut8_woe=get_woe_data(cut8)
cut9_woe=get_woe_data(cut9)
cutl0_woe=get_woe_data(cut10)
#定义IV值计算函数
def get_IV_data(cut,cut_woe):
    grouped=df['好坏客户'].groupby(cut,as_index = True).value_counts()
    cut_IV=((pd.DataFrame(grouped).unstack().iloc[:,1]/df['好坏客户'].sum()-pd.DataFrame(grouped).unstack().iloc[:,0]/
    (df['好坏客户'].count()-df['好坏客户'].sum()))*cut_woe).sum()
    return(cut_IV)

#计算各分组的IV值
cut1_IV=get_IV_data(cut1,cut1_woe)
cut2_IV=get_IV_data(cut2,cut2_woe)
cut3_IV=get_IV_data(cut3,cut3_woe)
cut4_IV=get_IV_data(cut4,cut4_woe)
cut5_IV=get_IV_data(cut5,cut5_woe)
cut6_IV=get_IV_data(cut6,cut6_woe)
cut7_IV=get_IV_data(cut7,cut7_woe)
cut8_IV=get_IV_data(cut8,cut8_woe)
cut9_IV=get_IV_data(cut9,cut9_woe)
cut10_IV=get_IV_data(cut10,cutl0_woe)

#各组的IV值可视化
df_IV=pd.DataFrame([cut1_IV,cut2_IV,cut3_IV,cut4_IV,cut5_IV,cut6_IV,cut7_IV,cut8_IV,cut9_IV,cut10_IV],index=df.columns[2:])
df_IV.plot(kind='bar')
for a,b in zip(range(10),df_IV.values):
    plt.text(a,b,'%.2f' % b, ha='center',va= 'bottom',fontsize=9)

在这里插入图片描述
LR建模

df_new=df.drop(['负债率', '月收入', '信贷数量','固定资产贷款量', '家属数量','用户ID'],axis=1)

def replace_data(cut,cut_woe):
    a=[]
    for i in cut.unique():
        a.append(i)
        a.sort()

    for m in range(len(a)):
        cut.replace(a[m],cut_woe.values[m],inplace=True)
    return cut

#进行替换
df_new['可用额度比值']=replace_data(cut1,cut1_woe)
df_new['年龄']=replace_data(cut2,cut2_woe)
df_new['逾期30-59天笔数']=replace_data(cut3,cut3_woe)
df_new['逾期90天笔数']=replace_data(cut7,cut7_woe)
df_new['逾期60-89天笔数']=replace_data(cut9,cut9_woe)
x=df_new.iloc[:,1:]
y=df_new.iloc[:,0]

x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.4,random_state=0)


model=LogisticRegression()
clf=model.fit(x_train,y_train)

print('测试成绩:{}'.format(clf.score(x_test,y_test)))
y_pred=clf.predict(x_test)
y_pred1=clf.decision_function(x_test)
print('y_pred\n',y_pred)
print('y_pred1\n',y_pred1)

#绘制ROC曲线以及计算AUC值
fpr,tpr,threshold = roc_curve(y_test, y_pred1)
roc_auc = auc(fpr,tpr)
plt.plot(fpr,tpr,color='darkorange',
         label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC_curve')
plt.legend(loc="lower right")
plt.show()

在这里插入图片描述

LR模型转化为对应分数

在Logistic回归模型中,将概率发生比的对数表示成特征变量的线性组合,公式如下:
在这里插入图片描述
Logit(pi)是一个对数值,即为log(P(bad)/P(good)),由于P(bad)/P(good)的取值在0到∞ ,log (P(bad )/P (good))的取值在-∞到+∞之间。
为了使获得的评分更具“实用性”,需要对每个属性的分值需要进行线性比例变换,然后再加上一个偏移量。评分和用于逻辑回归(Logistic Regression )建模的好/坏比( good/bad odds )的对数成比例,而不是好/坏比( good/bad odds )本身。所以分值可以是负数,而且越小的分值代表风险越高。
每个属性对应的分值可以通过下面的公式计算:WOE乘该变量的回归系数,加上回归截距,再乘上比例因子,最后加上偏移量:
( w o e i ∗ β i + a n ) ∗ f a c t o r + o f f s e t n (woe_i* \beta_i+\frac{a}{n})*factor+\frac{offset}{n} (woeiβi+na)factor+noffset
对于评分卡的分值,可以这样计算:
在这里插入图片描述
依据以上论文资料得到:
a=log(p_good/P_bad)
Score = offset + factor * log(odds)
其中,比例因子factor和偏移量offset可以通过以下行业规则确定: 好:坏 = 20:1 时,评分刻度为600; 评分每增减20分,好坏比增加一倍。

在从log (odds)到score的转换过程中,前面的推导都是使用factor和offset作为转换参数。但在实际的应用当中,这两个参数难以解释,因此也不方便指定初始值。更为常用的是事先指定下述三个参数:
b: 基准分值 (base point)
o: 基准分值对应的odds (odds at base point)
p: 当odds增加一倍,评分增加的分数 (point double odds)
factor, offset和b、o,p之间的转换公式为:
f a c t o r = p l o g 2 factor=\frac{p}{log2} factor=log2p
o f f s e t = b − p ∗ l o g o l o g 2 offset=b-p*\frac{log o}{log2} offset=bplog2logo
无论是factor,offset,还是b、o、p,这些参数仅仅是为了将log (odds)转换成为合适的分数,与逻辑回归本身无关。不同的转换参数会得到不同的评分,但不会改变概率。

coe=model.coef_

# 一般行业规则,一般设定当odds为50时,score为600
# Odds翻倍时,score+20
factor = 20/np.log(2)
offset = 600 - 20 * np.log(20)/np.log(2)

#定义变量分数计算函数
def get_score(coe,woe,factor):
    scores=[]
    for w in woe:
        score=round(coe*w*factor,0)
        scores.append(score)
    return scores

#计算每个变量得分
x1 = get_score(coe[0][0], cut1_woe, factor)
x2 = get_score(coe[0][1], cut2_woe, factor)
x3 = get_score(coe[0][2], cut3_woe, factor)
x7 = get_score(coe[0][3], cut7_woe, factor)
x9 = get_score(coe[0][4], cut9_woe, factor)

#打印输出每个特征对应的分数
print("可用额度比值对应的分数:{}".format(x1))
print("年龄对应的分数:{}".format(x2))
print("逾期30-59天笔数对应的分数:{}".format(x3))
print("逾期90天笔数对应的分数:{}".format(x7))
print("逾期60-89天笔数对应的分数:{}".format(x9))

在这里插入图片描述

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值