金融风控实战——申请评分卡

import pandas as pd
import numpy as np
from scipy.stats import mode
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
from sklearn.preprocessing import LabelEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve,auc
from wordcloud import WordCloud
import rule
import joblib
from sklearn.preprocessing import MinMaxScaler
from sklearn.cluster import KMeans
from pylab import mpl
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')
df=pd.read_csv('./cs-training.csv')
df.head()

在这里插入图片描述

查看各字段名

df.info()
#<class 'pandas.core.frame.DataFrame'>
#RangeIndex: 150000 entries, 0 to 149999
#Data columns (total 12 columns):
# #   Column                                Non-Null Count   Dtype  
#---  ------                                --------------   -----  
# 0   ID                                    150000 non-null  int64  
# 1   SeriousDlqin2yrs                      150000 non-null  int64  
# 2   RevolvingUtilizationOfUnsecuredLines  150000 non-null  float64
# 3   age                                   150000 non-null  int64  
# 4   NumberOfTime30-59DaysPastDueNotWorse  150000 non-null  int64  
# 5   DebtRatio                             150000 non-null  float64
# 6   MonthlyIncome                         120269 non-null  float64
# 7   NumberOfOpenCreditLinesAndLoans       150000 non-null  int64  
# 8   NumberOfTimes90DaysLate               150000 non-null  int64  
# 9   NumberRealEstateLoansOrLines          150000 non-null  int64  
# 10  NumberOfTime60-89DaysPastDueNotWorse  150000 non-null  int64  
# 11  NumberOfDependents                    146076 non-null  float64
#dtypes: float64(4), int64(8)
#memory usage: 13.7 MB

有12列数据,将ID列设置为索引列

len(df.columns)
#12
df=df.set_index('ID',drop=True)   #设置id列为索引列
df.head()

在这里插入图片描述

将各英文字段转为中文字段名方便理解

states={'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()    #修改英文字段名为中文字段名

在这里插入图片描述

坏客户是1,好客户对应0

df.info()
# <class 'pandas.core.frame.DataFrame'>
# Int64Index: 150000 entries, 1 to 150000
# Data columns (total 11 columns):
#  #   Column      Non-Null Count   Dtype  
# ---  ------      --------------   -----  
#  0   好坏客户        150000 non-null  int64  
#  1   可用额度比值      150000 non-null  float64
#  2   年龄          150000 non-null  int64  
#  3   逾期30-59天笔数  150000 non-null  int64  
#  4   负债率         150000 non-null  float64
#  5   月收入         120269 non-null  float64
#  6   信贷数量        150000 non-null  int64  
#  7   逾期90天笔数     150000 non-null  int64  
#  8   固定资产贷款量     150000 non-null  int64  
#  9   逾期60-89天笔数  150000 non-null  int64  
#  10  家属数量        146076 non-null  float64
# dtypes: float64(4), int64(7)
# memory usage: 13.7 MB

月收入和家属数量存在缺失

print("月收入缺失比:{:.2%}".format(df['月收入'].isnull().sum()/df.shape[0])
#月收入缺失比:19.82%
print("家属数量缺失比:{:.2%}".format(df['家属数量'].isnull().sum()/df.shape[0]))
#家属数量缺失比:2.62%
df=df.fillna({'月收入':df['月收入'].mean()})
df1=df.dropna()
df1.shape
#(146076, 11)

gbdt不考虑特征缺失,逻辑回归要对缺失值处理,逻辑回归这类广义线性模型在评分卡的构建过程中会对连续、类别特征进行分箱,对于类别特征,类别不多不分箱、类别多woe编码后根据woe编码分箱

异常值处理

gbdt一般不对异常值处理,逻辑回归、神经网络要做异常值处理,过高的异常值会让梯度下降难以收敛

plt.rcParams['font.sans-serif'] = ["Heiti TC"]
x1=df['可用额度比值']
x2=df['负债率']
x3=df1["年龄"]
x4=df1["逾期30-59天笔数"]
x5=df1["逾期60-89天笔数"]
x6=df1["逾期90天笔数"]
x7=df1["信贷数量"]
x8=df1["固定资产贷款量"]

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)
'''或者'''
plt.rcParams['font.sans-serif'] = ["Heiti TC"]
x1=df['可用额度比值']
x2=df['负债率']
x3=df1["年龄"]
x4=df1["逾期30-59天笔数"]
x5=df1["逾期60-89天笔数"]
x6=df1["逾期90天笔数"]
x7=df1["信贷数量"]
x8=df1["固定资产贷款量"]

f,ax = plt.subplots(2,2,figsize=(20,15))
ax[0,0].boxplot([x1,x2])
ax[0,0].set_xticklabels(['可用额度比值','负债率'],fontsize = 20)

ax[0,1].boxplot(x3)
ax[0,1].set_xticklabels(['年龄'],fontsize = 20)

ax[1,0].boxplot([x4,x5,x6])
ax[1,0].set_xticklabels(["逾期30-59天笔数","逾期60-89天笔数","逾期90天笔数"],fontsize = 20)

ax[1,1].boxplot([x7,x8])
ax[1,1].set_xticklabels(["信贷数量","固定资产贷款量"],fontsize = 20)

在这里插入图片描述

df1=df1[df1['可用额度比值']<1]
df1=df1[df1['年龄']>0]
df1=df1[df1['逾期30-59天笔数']<80]
df1=df1[df1['逾期60-89天笔数']<80]
df1=df1[df1['逾期90天笔数']<80]
df1=df1[df1['固定资产贷款量']<50]
df1.shape
#(142559, 11)

异常值可以当作缺失值处理,也可以通过分箱的方式把异常值单独分到一个箱子里

探索分析

age_cut=pd.cut(df1['年龄'],5)
age_cut_group=df1['好坏客户'].groupby(age_cut).count()
age_cut_group
#年龄
#(20.914, 38.2]    26984
#(38.2, 55.4]      56660
#(55.4, 72.6]      45709
#(72.6, 89.8]      12640
#(89.8, 107.0]       566
#Name: 好坏客户, dtype: int64

求各组的坏客户数

age_cut_grouped1=df1["好坏客户"].groupby(age_cut).sum()
age_cut_grouped1
#年龄
#(20.914, 38.2]    2478
#(38.2, 55.4]      4067
#(55.4, 72.6]      1664
#(72.6, 89.8]       273
#(89.8, 107.0]       12
#Name: 好坏客户, dtype: int64

联结

df2=pd.merge(pd.DataFrame(age_cut_group),pd.DataFrame(age_cut_grouped1),left_index=True,right_index=True)
df2.rename(columns={'好坏客户_x':'总客户数','好坏客户_y':'坏客户数'},inplace=True)
df2
'''或者'''
df2 = pd.merge(age_cut_group,age_cut_grouped1,on = "年龄",how="inner")
df2.columns = ["总客户数",'坏客户数']
df2

在这里插入图片描述

加一列好客户数

df2.insert(2,"好客户数",df2["总客户数"]-df2["坏客户数"])
df2

在这里插入图片描述

再加一列坏客户占比

df2.insert(2,"坏客户占比",df2["坏客户数"]/df2["总客户数"])
df2

在这里插入图片描述

ax1=df2[["好客户数","坏客户数"]].plot.bar(figsize=(10,5))
ax1.set_xticklabels(df2.index,rotation=15)
ax1.set_ylabel("客户数")
ax1.set_title("年龄与好坏客户数分布图")
'''或者'''
ax1 = df2[["好客户数","坏客户数"]].plot(kind="bar",figsize=(10,5))
ax1.set_ylabel("客户数")
ax1.set_title("年龄与好坏客户数分布图")
ax1.set_xticklabels(df2.index,rotation=45)

在这里插入图片描述

ax11=df2["坏客户占比"].plot(figsize=(10,5))
ax11.set_ylabel("坏客户率")
ax11.set_title("坏客户率随年龄的变化趋势图")

在这里插入图片描述

多变量分析

from matplotlib.font_manager import FontProperties
myfont=FontProperties(fname=r'./SimHei.ttf',size=14)

#sns.set(font=myfont.get_name())
#plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文字体设置-黑体
#plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
#sns.set(font='SimHei',font_scale=1.5)  # 解决Seaborn中文显示问题并调整字体大小

corr = df1.corr()#计算各变量的相关性系数
xticks = list(corr.index)#x轴标签
yticks = list(corr.index)#y轴标签
fig = plt.figure(figsize=(15,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': 'blue'})
ax1.set_xticklabels(xticks, rotation=35, fontsize=15,fontproperties=myfont)
ax1.set_yticklabels(yticks, rotation=0, fontsize=15,fontproperties=myfont)
plt.show()

在这里插入图片描述

可以看到各变量之间的相关性比较小,所以不需要操作,一般相关系数大于某一阈值(通常是0.8左右)可以进行变量剔除,具体删除哪个特征主要是根据特征的iv值决定的,a和b的相关系数超过0.8,a的IV值是0.6,b的IV值是0.3,那么保留a删除b。

四、特征选择

cut1=pd.qcut(df1["可用额度比值"],4,labels=False)
cut2=pd.qcut(df1["年龄"],8,labels=False)
bins3=[-1,0,1,3,5,13]
cut3=pd.cut(df1["逾期30-59天笔数"],bins3,labels=False)
cut4=pd.qcut(df1["负债率"],3,labels=False)
cut5=pd.qcut(df1["月收入"],4,labels=False)
cut6=pd.qcut(df1["信贷数量"],4,labels=False)
bins7=[-1, 0, 1, 3,5, 20]
cut7=pd.cut(df1["逾期90天笔数"],bins7,labels=False)
bins8=[-1, 0,1,2, 3, 33]
cut8=pd.cut(df1["固定资产贷款量"],bins8,labels=False)
bins9=[-1, 0, 1, 3, 12]
cut9=pd.cut(df1["逾期60-89天笔数"],bins9,labels=False)
bins10=[-1, 0, 1, 2, 3, 5, 21]
cut10=pd.cut(df1["家属数量"],bins10,labels=False)

WOE值计算

WOE是什么?
WOE,全称是“Weight of Evidence”,翻译过来就是证据权重,是对于字符型变量的某个值或者是连续变量的某个分段下的好坏客户的比例的对数。

实际的应用会将原始变量对应的数据替换为应用WOE公式后的数据,也称作WOE编码或者WOE化。

WOE编码需要首先将这个变量分组处也就是分箱。一般选择使用均匀分箱,离散型数据分箱个数就是该数据的数据类别个数,连续型数据一般会使用6组,尽可能均分。对某一变量分完组后,假设第i组下的数据的WOE的计算公式为:
W O E i = ln ⁡ ( p y 1 p y 0 ) = ln ⁡ ( B i B T G i G T ) W O E_{i}=\ln \left(\frac{p_{y_{1}}}{p_{y_{0}}}\right)=\ln \left(\frac{\frac{B_{i}}{B_{T}}}{\frac{G_{i}}{G_{T}}}\right) WOEi=ln(py0py1)=ln(GTGiBTBi)
这个第i组的WOE,其中Bi表示这一组的风险客户,BT表示这一样本总的风险客户,Gi表示这一组的正常客户,也就是无风险客户,GT表示这样本总的正常客户。

所以WOE就是将风险客户在所有风险客户的比例和正常客户在所有正常客户的比例,这两者做比,衡量的是两者的差异,再取对数,两者差异越大,对风险客户区分越明显。

我们也如果对这个公式做个分子分母的变换,就可以得到:
W O E i = ln ⁡ ( p y 1 p y 0 ) = ln ⁡ ( B i B T G i G T ) = ln ⁡ ( B i G i B T G T ) W O E_{i}=\ln \left(\frac{p_{y_{1}}}{p_{y_{0}}}\right)=\ln \left(\frac{\frac{B_{i}}{B_{T}}}{\frac{G_{i}}{G_{T}}}\right)=\ln \left(\frac{\frac{B_{i}}{G_{i}}}{\frac{B_{T}}{G_{T}}}\right) WOEi=ln(py0py1)=ln(GTGiBTBi)=ln(GTBTGiBi)
变换后我们也可以这样去理解WOE的含义,它表示的是当前这个组中风险的客户和正常客户的比值,和总体数据集中对应的这个比值的差异。这个差异是用这两个比值的比值,再取对数来表示的。

grouped=df1["好坏客户"].groupby(cut1).value_counts() #“as_index”就类似表示将组标签(类似“主键”)作为索引
print(grouped)
grouped.unstack()

在这里插入图片描述

rate=df1["好坏客户"].sum()/(df1["好坏客户"].count()-df1["好坏客户"].sum())

def get_woe_data(cut):
    grouped=df1["好坏客户"].groupby(cut,as_index = True).value_counts()
    #“as_index”就类似表示将组标签(类似“主键”)作为索引
    woe=np.log(grouped.unstack().iloc[:,1]/grouped.unstack().iloc[:,0]/rate)
    #将行旋转到列
    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)
cut10_woe=get_woe_data(cut10)

cut1_woe
#可用额度比值
#0   -1.203637
#1   -1.135436
#2   -0.247395
#3    1.044468
#dtype: float64

随便挑几个变量看下woe

#可用额度比值
cut1_woe.plot.bar(color='g',alpha=0.3,rot=0)
#cut1_woe.plot(kind="bar",rot=0 ,alpha=0.5)

在这里插入图片描述

#年龄
cut2_woe.plot.bar(color='b',alpha=0.3,rot=0)

在这里插入图片描述

#逾期30-59天笔数
cut3_woe.plot.bar(color='b',alpha=0.3,rot=0)

在这里插入图片描述

可以看出woe已调整到具有单调性

IV值计算

1.IV的用途
IV的全称是Information Value,中文意思是信息价值,或者信息量。

我们在用逻辑回归、决策树等模型方法构建分类模型时,经常需要对自变量进行筛选。比如我们有200个候选自变量,通常情况下,不会直接把200个变量直接放到模型中去进行拟合训练,而是会用一些方法,从这200个自变量中挑选一些出来,放进模型,形成入模变量列表。那么我们怎么去挑选入模变量呢?

挑选入模变量过程是个比较复杂的过程,需要考虑的因素很多,比如:变量的预测能力,变量之间的相关性,变量的简单性(容易生成和使用),变量的强壮性(不容易被绕过),变量在业务上的可解释性(被挑战时可以解释的通)等等。但是,其中最主要和最直接的衡量标准是变量的预测能力。

“变量的预测能力”这个说法很笼统,很主观,非量化,在筛选变量的时候我们总不能说:“我觉得这个变量预测能力很强,所以他要进入模型”吧?我们需要一些具体的量化指标来衡量每自变量的预测能力,并根据这些量化指标的大小,来确定哪些变量进入模型。IV就是这样一种指标,他可以用来衡量自变量的预测能力。类似的指标还有信息增益、基尼系数等等。

2.对IV的直观理解
从直观逻辑上大体可以这样理解“用IV去衡量变量预测能力”这件事情:我们假设在一个分类问题中,目标变量的类别有两类:Y1,Y2。对于一个待预测的个体A,要判断A属于Y1还是Y2,我们是需要一定的信息的,假设这个信息总量是I,而这些所需要的信息,就蕴含在所有的自变量C1,C2,C3,……,Cn中,那么,对于其中的一个变量Ci来说,其蕴含的信息越多,那么它对于判断A属于Y1还是Y2的贡献就越大,Ci的信息价值就越大,Ci的IV就越大,它就越应该进入到入模变量列表中。

3 IV的计算公式
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 I V_{i}=\left(\frac{B a d_{i}}{B a d_{T}}-\frac{G o o d_{i}}{G o o d_{T}}\right) * W O E_{i} IVi=(BadTBadiGoodTGoodi)WOEi = ( B a d i B a d T − G o o d i G o o d T ) ∗ ln ⁡ ( B a d i B a d T / G o o d i G o o d T ) =\left(\frac{B a d_{i}}{B a d_{T}}-\frac{G o o d_{i}}{G o o d_{T}}\right) * \ln \left(\frac{B a d_{i}}{B a d_{T}} / \frac{G o o d_{i}}{G o o d_{T}}\right) =(BadTBadiGoodTGoodi)ln(BadTBadi/GoodTGoodi) I V = ∑ i = 1 n I V i I V=\sum_{i=1}^{n} I V_{i} IV=i=1nIVi

def get_IV_data(cut,cut_woe):
    grouped=df1["好坏客户"].groupby(cut,as_index = True).value_counts()
    cut_IV=((grouped.unstack().iloc[:,1]/df1["好坏客户"].sum()-grouped.unstack().iloc[:,0]/(df1["好坏客户"].count()-df1["好坏客户"].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,cut10_woe)

IV=pd.DataFrame([cut1_IV,cut2_IV,cut3_IV,cut4_IV,cut5_IV,cut6_IV,cut7_IV,cut8_IV,cut9_IV,cut10_IV],index=['可用额度比值','年龄','逾期30-59天笔数','负债率','月收入','信贷数量','逾期90天笔数','固定资产贷款量','逾期60-89天笔数','家属数量'],columns=['IV'])
iv=IV.plot.bar(color='b',alpha=0.3,rot=30,figsize=(10,5),fontsize=(10))
iv.set_title('特征变量与IV值分布图',fontsize=(15),fontproperties=myfont)
iv.set_xlabel('特征变量',fontsize=(15),fontproperties=myfont)
iv.set_ylabel('IV',fontsize=(15),fontproperties=myfont)

在这里插入图片描述

IV

在这里插入图片描述

一般选取IV大于0.02的特征变量进行后续训练,从以上可以看出所有变量均满足,所以选取全部的

WOE值替换

df_new=pd.DataFrame()   #新建df_new存放woe转换后的数据

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["好坏客户"]=df1["好坏客户"]
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["负债率"]=replace_data(cut4,cut4_woe)
df_new["月收入"]=replace_data(cut5,cut5_woe)
df_new["信贷数量"]=replace_data(cut6,cut6_woe)
df_new["逾期90天笔数"]=replace_data(cut7,cut7_woe)
df_new["固定资产贷款量"]=replace_data(cut8,cut8_woe)
df_new["逾期60-89天笔数"]=replace_data(cut9,cut9_woe)
df_new["家属数量"]=replace_data(cut10,cut10_woe)
df_new.head()

在这里插入图片描述

df1["可用额度比值"]
#ID
#1         0.766127
#2         0.957151
#3         0.658180
#4         0.233810
#5         0.907239
#            ...   
#149996    0.040674
#149997    0.299745
#149998    0.246044
#149999    0.000000
#150000    0.850283
#Name: 可用额度比值, Length: 142559, dtype: float64
print(cut1)
cut1_woe
#ID
#1         3
#2         3
#3         3
#4         2
#5         3
#         ..
#149996    1
#149997    2
#149998    2
#149999    0
#150000    3
#Name: 可用额度比值, Length: 142559, dtype: int64

#可用额度比值
#0   -1.203637
#1   -1.135436
#2   -0.247395
#3    1.044468
#dtype: float64
cut1
#ID
#1         1.044468
#2         1.044468
#3         1.044468
#4        -0.247395
#5         1.044468
#            ...   
#149996   -1.135436
#149997   -0.247395
#149998   -0.247395
#149999   -1.203637
#150000    1.044468
#Name: 可用额度比值, Length: 142559, dtype: float64

模型训练

在这里插入图片描述

x=df_new.iloc[:,1:]
y=df_new.iloc[:,:1]
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.6,random_state=0)
model=LogisticRegression()
model.fit(x_train,y_train)
print('测试成绩:{}'.format(model.score(x_test,y_test)))
#测试成绩:0.9418841189674523

y_pred = model.predict_proba(x_test)
predictions = y_pred[:,1]
auc = metrics.roc_auc_score(y_test,predictions)
fpr,tpr,threshold = roc_curve(y_test,y_pred[:,1])
ks = max(tpr - fpr)
print("KS:%.4f%%"%(ks * 100.0))
print("AUC:%.4f%%"%(auc * 100.0))
metrics.auc(fpr,tpr)
#KS:52.7410%
#AUC:83.9908%
#0.8399080245785004
#ks40以上 auc80以上就算好了。一般auc超过90 ks超过80大概率存在标签泄漏问题

#求特征权值系数coe,后面训练结果转分值时会用到:
coe=clf.coef_        #特征权值系数,后面转换为打分规则时会用到
coe
#array([[0.62019761, 0.43160547, 0.56449512, 1.25787618, 0.57195395,
#        0.21789471, 0.59067794, 0.63781761, 0.46569727, 0.37101125]])

#roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, color='darkorange',label='ROC curve (area = %0.2f)' % 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()

在这里插入图片描述

fig, ax = plt.subplots(figsize=(6,4))
ax.plot(1 - threshold, tpr, label='tpr') # ks曲线要按照预测概率降序排列,所以需要1-threshold镜像
ax.plot(1 - threshold, fpr, label='fpr')
ax.plot(1 - threshold, tpr-fpr,label='KS')
ax.set_xlabel('score')
ax.set_title('KS Curve')
ax.set_ylim([0.0, 1.0])
ax.legend(loc='upper left')
plt.show()

在这里插入图片描述

模型结果转评分

在这里插入图片描述
在这里插入图片描述

假设好坏比为20的时候分数为600分,每高20分好坏比翻一倍

现在我们求每个变量不同woe值对应的分数刻度可得:

factor = 20 / np.log(2)
offset = 600 - 20 * np.log(20) / np.log(2)
def get_score(coe,woe,factor):
    scores=[]
    interval = []
    for key,w in woe.items():
        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)
x4 = get_score(coe[0][3], cut4_woe, factor)
x5 = get_score(coe[0][4], cut5_woe, factor)
x6 = get_score(coe[0][5], cut6_woe, factor)
x7 = get_score(coe[0][6], cut7_woe, factor)
x8 = get_score(coe[0][7], cut8_woe, factor)
x9 = get_score(coe[0][8], cut9_woe, factor)
x10 = get_score(coe[0][9], cut10_woe, factor)
print("可用额度比值对应的分数:{}".format(x1))
print("年龄对应的分数:{}".format(x2))
print("逾期30-59天笔数对应的分数:{}".format(x3))
print("负债率对应的分数:{}".format(x4))
print("月收入对应的分数:{}".format(x5))
print("信贷数量对应的分数:{}".format(x6))
print("逾期90天笔数对应的分数:{}".format(x7))
print("固定资产贷款量对应的分数:{}".format(x8))
print("逾期60-89天笔数对应的分数:{}".format(x9))
print("家属数量对应的分数:{}".format(x10))
#可用额度比值对应的分数:[-22.0, -20.0, -4.0, 19.0]
#年龄对应的分数:[6.0, 4.0, 3.0, 2.0, -0.0, -5.0, -11.0, -13.0]
#逾期30-59天笔数对应的分数:[-8.0, 15.0, 28.0, 38.0, 43.0]
#负债率对应的分数:[-5.0, -2.0, 7.0]
#月收入对应的分数:[5.0, 2.0, -3.0, -5.0]
#信贷数量对应的分数:[1.0, -2.0, -1.0, 0.0]
#逾期90天笔数对应的分数:[-6.0, 34.0, 47.0, 56.0, 56.0]
#固定资产贷款量对应的分数:[4.0, -4.0, -3.0, 1.0, 12.0]
#逾期60-89天笔数对应的分数:[-3.0, 25.0, 36.0, 40.0]
#家属数量对应的分数:[-2.0, 1.0, 2.0, 3.0, 4.0, 7.0]

计算用户总分

1.取自动分箱的边界分割点

cu1=pd.qcut(df1["可用额度比值"],4,labels=False,retbins=True)
#当bins取整数时可以设置retbins=True以显示分界值,得到划分后的区间
bins1=cu1[1]
cu2=pd.qcut(df1["年龄"],8,labels=False,retbins=True)
bins2=cu2[1]

# bins3=[-1,0,1,3,5,13]
# cut3=pd.cut(df1["逾期30-59天笔数"],bins3,labels=False)
cu4=pd.qcut(df1["负债率"],3,labels=False,retbins=True)
bins4=cu4[1]
cu5=pd.qcut(df1["月收入"],4,labels=False,retbins=True)
bins5=cu5[1]
cu6=pd.qcut(df1["信贷数量"],4,labels=False,retbins=True)
bins6=cu6[1]

2.各变量对应的分数求和,算出每个用户的总分

def compute_score(series,bins,score):
    list = []
    i = 0
    while i < len(series):
        value = series[i]
        j = len(bins) - 2
        m = len(bins) - 2
        while j >= 0:
            if value >= bins[j]:
                j = -1
            else:
                j -= 1
                m -= 1
        list.append(score[m])
        i += 1
    return list
    
def calScore(test1):
    test1['x1'] = pd.Series(compute_score(test1['RevolvingUtilizationOfUnsecuredLines'], bins1, x1))
    test1['x2'] = pd.Series(compute_score(test1['age'], bins2, x2))
    test1['x3'] = pd.Series(compute_score(test1['NumberOfTime30-59DaysPastDueNotWorse'], bins3, x3))
    test1['x4'] = pd.Series(compute_score(test1['DebtRatio'], bins4, x4))
    test1['x5'] = pd.Series(compute_score(test1['MonthlyIncome'], bins5, x5))
    test1['x6'] = pd.Series(compute_score(test1['NumberOfOpenCreditLinesAndLoans'], bins6, x6))
    test1['x7'] = pd.Series(compute_score(test1['NumberOfTimes90DaysLate'], bins7, x7))
    test1['x8'] = pd.Series(compute_score(test1['NumberRealEstateLoansOrLines'], bins8, x8))
    test1['x9'] = pd.Series(compute_score(test1['NumberOfTime60-89DaysPastDueNotWorse'], bins9, x9))
    test1['x10'] = pd.Series(compute_score(test1['NumberOfDependents'], bins10, x10))
    score = test1['x1']+test1['x2']+test1['x3']+test1['x4']+test1['x5']+test1['x6']+test1['x7']+test1['x8']+test1['x9']+test1['x10']+600
    return score[0]
   
def creditLevel(score):
    if score < 600.0:
        return -1
    elif score >= 600.0 and score < 640.0:
        return 2000
    elif score >= 640.0 and score < 680.0:
        return 4000
    elif score >= 680.0 and score < 720.0:
        return 6000
    elif score >= 720.0 and score < 760.0:
        return 8000
    elif score >= 760.0:
        return 10000
rule.is_simple_type

#进行全流程验证函数
def main(ID_Card,tel):
    print('开始进入准入规则:')
    result = rule.check(ID_Card,tel)
    print(result)
    if result != '通过准入规则检查':
        return -1
    else:
        print("开始反欺诈检验!")
        df = pd.read_csv('./anti-fraud.txt',sep = '\t')
        if int(ID_Card) in df['ID'].tolist():
            data = df[df['ID'] == int(ID_Card)]
            if len(data.index.tolist()) > 0:
                data.drop('ID',inplace=True,axis=1)
                # 加载模型
                model = joblib.load("./anti-fraud-rf.model")
                pred = model.predict(data.values)
                if pred[0] == 0:
                    print('您的信息已经通过反欺诈检验。')
                else:
                    print('您的信息存在异常,命中欺诈库,请完善信息,重新申请。')
                    return -1
            else:
                print("您的信息不全,请重新填写。")
                return -1
        else:
            print("您的信息不全,请重新填写。")
            return -1
        print("开始进行申请评分!")
        df = pd.read_csv('./score-card.txt',sep = '\t')
        if int(ID_Card) in df['ID'].tolist():
            data = df[df['ID'] == int(ID_Card)]
            if len(data.index.tolist()) > 0:
                score = calScore(data)
                amt = creditLevel(score)
                if amt == -1:
                    print('抱歉,您没有通过申请,请完善更多资料重新进行申请。')
                    return -1
                else:
                    print('恭喜您,获得'+str(amt)+'额度,快去体现使用吧!')      
            else:
                print("您的信息不全,请重新填写。")
                return -1
        else:
            print("您的信息不全,请重新填写。")
            return -1
            
#实际验证
inputData = input("请输入身份证号,手机号:")
data = inputData.strip().split(',')
if len(data) == 2:
    ret = main(data[0],data[1])
    if ret == -1:
        print("真是遗憾!")
    else:
        print("进行后续步骤!")

进入准入规则检查——>反欺诈评分卡、反欺诈规则——>人工审核——>申请评分卡

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值