信贷风控评分卡模型(上)_Give Me Some Credit(技术实现过程)

本帖是在2019年5月初入门python之时,选取的较为系统的练手案例,主要内容是信用风险计量体系之主体评级模型的开发过程(可用“四张卡”来表示,分别是A卡、B卡、C卡和F卡)。
如今再回顾,结合前几月股票市场被割韭菜的切身体会,应该能应用这个模型来做股票市场的风险评级模型分析/收益评级模型分析。已经跃跃欲试了。
——2020.11.6补充说明

—— —— —— —— ——
【本帖技术层面的说明】

此贴是评分卡技术实现过程,含完整代码。另文字报告版见本人知乎专栏:
https://zhuanlan.zhihu.com/p/67031799

本次项目主要参考:https://www.jianshu.com/p/f931a4df202c?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
原帖子代码不全,本帖自行补上。并且在分析过程中,做了一些延伸探讨,或者战略性舍弃。
本帖大幅的定义说明文字,系参考原贴,在此感谢。

另有一处自定义函数(self_bin)代码系参考此贴,在此感谢 https://blog.csdn.net/sunyaowu315/article/details/82981216
—— —— —— —— ——

【正文开篇】

信用风险计量体系包括主体评级模型和债项评级两部分。
主体评级和债项评级均有一系列评级模型组成:其中主体评级模型可用“四张卡”来表示,分别是A卡、B卡、C卡和F卡;债项评级模型通常按照主体的融资用途,分为企业融资模型、现金流融资模型和项目融资模型等。
我们主要讨论主体评级模型的开发过程。

一、项目流程

# 一、项目流程
# 信用风险评级模型的主要开发流程如下:
# (1) 数据获取,包括获取存量客户及潜在客户的数据。存量客户是指已经在证券公司开展相关融资类业务的客户,包括个人客户和机构客户;潜在客户是指未来拟在证券公司开展相关融资类业务的客户,主要包括机构客户,这也是解决证券业样本较少的常用方法,这些潜在机构客户包括上市公司、公开发行债券的发债主体、新三板上市公司、区域股权交易中心挂牌公司、非标融资机构等。
# (2) 数据预处理,主要工作包括数据清洗、缺失值处理、异常值处理,主要是为了将获取的原始数据转化为可用作模型开发的格式化数据。
# (3) 探索性数据分析,该步骤主要是获取样本总体的大概情况,描述样本总体情况的指标主要有直方图、箱形图等。
# (4) 变量选择,该步骤主要是通过统计学的方法,筛选出对违约状态影响最显著的指标。主要有单变量特征选择方法和基于机器学习模型的方法 。
# (5) 模型开发,该步骤主要包括变量分段、变量的WOE(证据权重)变换和逻辑回归估算三部分。
# (6) 模型评估,该步骤主要是评估模型的区分能力、预测能力、稳定性,并形成模型评估报告,得出模型是否可以使用的结论。
# (7) 信用评分,根据逻辑回归的系数和WOE等确定信用评分的方法。将Logistic模型转换为标准评分的形式。
# (8) 建立评分系统,根据信用评分方法,建立自动信用评分系统。

# PS:有些时候为了便于命名,相应的变量用标号代替

二、数据获取

# 二、数据获取
# 数据来自Kaggle的Give Me Some Credit,有15万条的样本数据,大致情况如下:
# 数据属于个人消费类贷款,只考虑信用评分最终实施时能够使用到的数据应从如下一些方面获取数据:
# –            基本属性:包括了借款人当时的年龄。
# –            偿债能力:包括了借款人的月收入、负债比率。
# –            信用往来:两年内35-59天逾期次数、两年内60-89天逾期次数、两年内90
# 天或高于90天逾期的次数。
# –            财产状况:包括了开放式信贷和贷款数量、不动产贷款或额度数量。
# –            贷款属性:暂无。
# –            其他因素:包括了借款人的家属数量(不包括本人在内)。
# –            时间窗口:自变量的观察窗口为过去两年,因变量表现窗口为未来两年。

# 附:数据下载网址 https://www.kaggle.com/c/GiveMeSomeCredit/data

三、数据预处理

# 三、数据预处理

# 在对数据处理之前,需要对数据的缺失值和异常值情况进行了解。Python内有describe()函数,可以了解数据集的缺失值、均值和中位数等。
# import pandas as pd
# import numpy as np
# import matplotlib.pyplot as plt
# %matplotlib inline

# import warnings
# warnings.filterwarnings("ignore")

# # seaborn中文乱码解决方案
# import seaborn as sns
# from matplotlib.font_manager import FontProperties
# myfont=FontProperties(fname=r'C:\Windows\Fonts\simhei.ttf',size=14)
# sns.set(font=myfont.get_name())
# Basic packages
import numpy as np
import pandas as pd

# Viz
import matplotlib.pyplot as plt
import seaborn as sns

# settings
import warnings
warnings.filterwarnings("ignore")

from  sklearn.ensemble import RandomForestClassifier
from  sklearn.ensemble import RandomForestRegressor
C:\ProgramData\Anaconda3\lib\site-packages\sklearn\ensemble\weight_boosting.py:29: DeprecationWarning: numpy.core.umath_tests is an internal NumPy module and should not be imported. It will be removed in a future NumPy release.
  from numpy.core.umath_tests import inner1d
#载入数据
data = pd.read_csv(r'D:\2018_BigData\Python\Kaggle_learning\GiveMeSomeCredit\cs-training.csv')
#数据集确实和分布情况
data.describe().to_csv(r'D:\2018_BigData\Python\Kaggle_learning\GiveMeSomeCredit\DataDescribe.csv')

# np.set_printoptions(suppress=True) # 搜索可用该语句取消MonthlyIncome的科学计数法显示,但是此处没有生效。
data.describe()

# 由生成的describe.csv可知,变量MonthlyIncome和NumberOfDependents存在缺失,变量MonthlyIncome共有缺失值29731个,NumberOfDependents有3924个缺失值。
Unnamed: 0SeriousDlqin2yrsRevolvingUtilizationOfUnsecuredLinesageNumberOfTime30-59DaysPastDueNotWorseDebtRatioMonthlyIncomeNumberOfOpenCreditLinesAndLoansNumberOfTimes90DaysLateNumberRealEstateLoansOrLinesNumberOfTime60-89DaysPastDueNotWorseNumberOfDependents
count150000.000000150000.000000150000.000000150000.000000150000.000000150000.0000001.202690e+05150000.000000150000.000000150000.000000150000.000000146076.000000
mean75000.5000000.0668406.04843852.2952070.421033353.0050766.670221e+038.4527600.2659731.0182400.2403870.757222
std43301.4145270.249746249.75537114.7718664.1927812037.8185231.438467e+045.1459514.1693041.1297714.1551791.115086
min1.0000000.0000000.0000000.0000000.0000000.0000000.000000e+000.0000000.0000000.0000000.0000000.000000
25%37500.7500000.0000000.02986741.0000000.0000000.1750743.400000e+035.0000000.0000000.0000000.0000000.000000
50%75000.5000000.0000000.15418152.0000000.0000000.3665085.400000e+038.0000000.0000001.0000000.0000000.000000
75%112500.2500000.0000000.55904663.0000000.0000000.8682548.249000e+0311.0000000.0000002.0000000.0000001.000000
max150000.0000001.00000050708.000000109.00000098.000000329664.0000003.008750e+0658.00000098.00000054.00000098.00000020.000000
# 3.1 缺失值处理
# 这种情况在现实问题中非常普遍,这会导致一些不能处理缺失值的分析方法无法应用,因此,在信用风险评级模型开发的第一步我们就要进行缺失值处理。
# 缺失值处理的方法,包括如下几种。
# (1) 直接删除含有缺失值的样本。
# (2) 根据样本之间的相似性填补缺失值。
# (3) 根据变量之间的相关关系填补缺失值。

# 变量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(r'D:\2018_BigData\Python\Kaggle_learning\GiveMeSomeCredit\MissingData.csv',index=False)
# data.shape

# ValueError: Input contains NaN, infinity or a value too large for dtype('float32').
# 其中错误指向上述自定义函数第18行 ---> 18     predicted = rfr.predict(unknown[:, 1:]).round(0)
# 为了节省时间,这里我们先跳过MonthlyIncome的缺失值补充,采取与NumberOfDependents相同的方式,直接删除含有缺失值的行(约占总数18%)。

data=data.dropna()#删除比较少的缺失值
data = data.drop_duplicates()#删除重复项
data.to_csv(r'D:\2018_BigData\Python\Kaggle_learning\GiveMeSomeCredit\MissingData.csv',index=False)
data.shape

# 源数据15万条,删除缺失值后剩余12万,恰好占80%,数据量也足够用来构建信用评分卡模型了。
# 而且,不额外补充缺失值,保持原始数据的原始性,最终预测结果反而更佳。
(120269, 12)
# 3.2 异常值处理

# 缺失值处理完毕后,我们还需要进行异常值处理。
# 异常值是指明显偏离大多数抽样数据的数值,比如个人客户的年龄为0时,通常认为该值为异常值。找出样本总体中的异常值,通常采用离群值检测的方法。
# 首先,我们发现变量age中存在0,显然是异常值,直接剔除:

# 年龄等于0的异常值进行剔除
data = data[data['age'] > 0]
# 对于变量NumberOfTime30-59DaysPastDueNotWorse、NumberOfTimes90DaysLate、NumberOfTime60-89DaysPastDueNotWorse这三个变量,
# 由下面的箱线图图3-2可以看出,均存在异常值,且由unique函数可以得知均存在96、98两个异常值,因此予以剔除。
# 同时会发现剔除其中一个变量的96、98值,其他变量的96、98两个值也会相应被剔除。

# plt.figure(figsize=(16,12))

fig,axes = plt.subplots(1,3)
color = dict(boxes='DarkGreen', whiskers='DarkOrange',
              medians='DarkBlue', caps='Red')
# boxes表示箱体,whisker表示触须线
# medians表示中位数,caps表示最大与最小值界限

datatemp1=data[["NumberOfTime30-59DaysPastDueNotWorse","NumberOfTimes90DaysLate","NumberOfTime60-89DaysPastDueNotWorse"]]
datatemp1.plot(kind='box',ax=axes,subplots=True,
                              title='3 Different boxplots',color=color,sym='r+')
# sym参数表示异常值标记的方式
 
axes[0].set_ylabel('NumberOfTime30-59DaysPastDueNotWorse')
axes[1].set_ylabel('NumberOfTimes90DaysLate')
axes[2].set_ylabel('NumberOfTime60-89DaysPastDueNotWorse')


fig.subplots_adjust(wspace=3,hspace=1)  # 调整子图之间的间距

在这里插入图片描述

# 查看上述三个变量的不重复值。
print(np.unique(datatemp1["NumberOfTime30-59DaysPastDueNotWorse"]))
print(np.unique(datatemp1["NumberOfTimes90DaysLate"]))
print(np.unique(datatemp1["NumberOfTime60-89DaysPastDueNotWorse"]))
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 96 98]
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 17 96 98]
[ 0  1  2  3  4  5  6  7  8  9 11 96 98]
data.head(2)
Unnamed: 0SeriousDlqin2yrsRevolvingUtilizationOfUnsecuredLinesageNumberOfTime30-59DaysPastDueNotWorseDebtRatioMonthlyIncomeNumberOfOpenCreditLinesAndLoansNumberOfTimes90DaysLateNumberRealEstateLoansOrLinesNumberOfTime60-89DaysPastDueNotWorseNumberOfDependents
0110.7661274520.8029829120.0130602.0
1200.9571514000.1218762600.040001.0
# 剔除变量NumberOfTime30-59DaysPastDueNotWorse、NumberOfTimes90DaysLate、NumberOfTime60-89DaysPastDueNotWorse的异常值。
# 另外,数据集中好客户为0,违约客户为1,考虑到正常的理解,能正常履约并支付利息的客户为1,所以我们将其取反。

#剔除异常值
data = data[data['NumberOfTime30-59DaysPastDueNotWorse'] < 90]

#变量SeriousDlqin2yrs取反
data['SeriousDlqin2yrs']=1-data['SeriousDlqin2yrs']
data.head(2)
Unnamed: 0SeriousDlqin2yrsRevolvingUtilizationOfUnsecuredLinesageNumberOfTime30-59DaysPastDueNotWorseDebtRatioMonthlyIncomeNumberOfOpenCreditLinesAndLoansNumberOfTimes90DaysLateNumberRealEstateLoansOrLinesNumberOfTime60-89DaysPastDueNotWorseNumberOfDependents
0100.7661274520.8029829120.0130602.0
1210.9571514000.1218762600.040001.0
# 后面报错,查找原因后回到这里删除列“Unnamed: 0”

data=data.drop(["Unnamed: 0"],axis=1)
data.head(2)
SeriousDlqin2yrsRevolvingUtilizationOfUnsecuredLinesageNumberOfTime30-59DaysPastDueNotWorseDebtRatioMonthlyIncomeNumberOfOpenCreditLinesAndLoansNumberOfTimes90DaysLateNumberRealEstateLoansOrLinesNumberOfTime60-89DaysPastDueNotWorseNumberOfDependents
000.7661274520.8029829120.0130602.0
110.9571514000.1218762600.040001.0
# 3.3 数据切分

# 为了验证模型的拟合效果,我们需要对数据集进行切分,分成训练集和测试集。

from sklearn.cross_validation import train_test_split
C:\ProgramData\Anaconda3\lib\site-packages\sklearn\cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
Y = 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)
test.head()
SeriousDlqin2yrsRevolvingUtilizationOfUnsecuredLinesageNumberOfTime30-59DaysPastDueNotWorseDebtRatioMonthlyIncomeNumberOfOpenCreditLinesAndLoansNumberOfTimes90DaysLateNumberRealEstateLoansOrLinesNumberOfTime60-89DaysPastDueNotWorseNumberOfDependents
11024711.0619752400.1295024802.040000.0
6234310.1445337400.3589673601.0120200.0
8935110.0124324900.22628910300.040100.0
11938610.3020734500.4725055000.050100.0
1905410.0000003400.1848681400.050001.0
test['SeriousDlqin2yrs'].head()

# 该语句原运行结果出现两列同名'SeriousDlqin2yrs',且引发了两个报错:
# 后面clasTest = test.groupby(['SeriousDlqin2yrs'])['SeriousDlqin2yrs'].count() 提示 ValueError: Grouper for 'SeriousDlqin2yrs' not 1-dimensional,
# 以及test['SeriousDlqin2yrs'].value_counts() 提示 AttributeError: 'DataFrame' object has no attribute 'value_counts'
# 都是因为有两列同样的test['SeriousDlqin2yrs']

# 那,如何去掉一列,只保留其中一列呢?或者另一个疑问:为什么会产生两列一模一样的数据?
# 我们发现data的列'SeriousDlqin2yrs'前面,还多了一列不知名数据列“Unnamed: 0”。
# 所以,我们回到前面尝试先删除该列,证明成功。好了接下来继续跟着参考帖子的节奏走。
110247    1
62343     1
89351     1
119386    1
19054     1
Name: SeriousDlqin2yrs, dtype: int64
# test.reset_index()
test['SeriousDlqin2yrs'].value_counts()

# 能正常履约并支付利息的客户有33689位,而无法正常履约并支付利息的客户有2447位。
1    33589
0     2447
Name: SeriousDlqin2yrs, dtype: int64
# from os import path
# d = path.dirname(r'D:\2018_BigData\Python\Kaggle_learning\GiveMeSomeCredit')
clasTest = test.groupby(['SeriousDlqin2yrs'])['SeriousDlqin2yrs'].count()  
# 该groupby语句效果和前面test['SeriousDlqin2yrs'].value_counts()几乎一致,且当是多个方法。

train.to_csv(r'D:\2018_BigData\Python\Kaggle_learning\GiveMeSomeCredit\TrainData.csv',index=False)
test.to_csv(r'D:\2018_BigData\Python\Kaggle_learning\GiveMeSomeCredit\TestData.csv',index=False)

clasTest.head()
SeriousDlqin2yrs
0     2447
1    33589
Name: SeriousDlqin2yrs, dtype: int64

四、探索性分析

# 四、探索性分析

# 在建立模型之前,我们一般会对现有的数据进行 探索性数据分析(Exploratory Data Analysis)。
# EDA是指对已有的数据(特别是调查或观察得来的原始数据)在尽量少的先验假定下进行探索。
# 常用的探索性数据分析方法有:直方图、散点图和箱线图等。
plt.figure(figsize=(12,8))
bins=30

plt.subplot(211)
ax1= plt.hist(data.age, bins, color="green", alpha=0.8,rwidth=0.9)
plt.title("Age distribution")
plt.ylabel('# of age', fontsize=12)
plt.xlabel('age', fontsize=12)

plt.subplot(212)
ax2= plt.hist(data.MonthlyIncome,bins,color="green",alpha=0.8,rwidth=0.9)
plt.title("MonthlyIncome distribution")
plt.ylabel('# of MonthlyIncome', fontsize=12)
plt.xlabel('MonthlyIncome', fontsize=12)

plt.show()

# 客户年龄分布如下图所示,可以看到年龄变量大致呈正态分布,符合统计分析的假设。

# 原参考帖中,看原贴客户收入分布图,月收入也大致呈正态分布,符合统计分析的需要。
# 而我们这里呈现的收入分布集中在一条柱子上,看不清分布。我们猜测是异常值影响所致。接下来用箱线图找找异常值。

在这里插入图片描述

datatemp2=data["MonthlyIncome"]
datatemp2.plot(kind='box',title='MonthlyIncome Distribution',sym='r+');

# 果然被异常值影响了,那接下来我们先把异常值分拆出去。

在这里插入图片描述

print(data[data['MonthlyIncome'] > 50000].count()) # 回头查看下面被剔除了多少异常值

# 如前文处理'NumberOfTime30-59DaysPastDueNotWorse'异常值的方式,直接剔除异常值
data = data[data['MonthlyIncome'] < 50000]
# 上述语句的阈值,从100万尝试到50万尝试到10万,再到6万,5万,最终确定5万。

# 重新查看收入直方图分布
plt.figure(figsize=(15,5))
plt.hist(data.MonthlyIncome,bins,color="green",alpha=0.8,rwidth=0.9)
plt.title("MonthlyIncome distribution")
plt.ylabel('# of MonthlyIncome', fontsize=12)
plt.xlabel('MonthlyIncome', fontsize=12)

plt.show()

# 所以,从客户收入分布图看出,月收入也大致呈正态分布,符合统计分析的需要。
# 另外,剔除掉的月收入异常值,有301个,在12万的数据量中可忽略不计。
SeriousDlqin2yrs                        301
RevolvingUtilizationOfUnsecuredLines    301
age                                     301
NumberOfTime30-59DaysPastDueNotWorse    301
DebtRatio                               301
MonthlyIncome                           301
NumberOfOpenCreditLinesAndLoans         301
NumberOfTimes90DaysLate                 301
NumberRealEstateLoansOrLines            301
NumberOfTime60-89DaysPastDueNotWorse    301
NumberOfDependents                      301
dtype: int64

在这里插入图片描述

# 来个快速版的直方图

data.hist(bins=50, figsize=(20,15))
plt.show()

# 发现挺多变量含有异常值影响了直方图分布。

在这里插入图片描述

# 用箱线图看看异常值
plt.figure(figsize=(16,8))
data.plot(kind='box',title='Various Var Distribution',sym='r+');
# 因为各个变量的数量级相差较大,直接放一起,无法观察。后面如有需要,再分开观察。此处不过多赘述。
<Figure size 1152x576 with 0 Axes>

在这里插入图片描述

五、变量选择

# 五、变量选择
# 特征变量选择(排序)对于数据分析、机器学习从业者来说非常重要。
# 好的特征选择能够提升模型的性能,更能帮助我们理解数据的特点、底层结构,这对进一步改善模型、算法都有着重要作用。
# 至于Python的变量选择代码实现可以参考结合Scikit-learn介绍几种常用的特征选择方法。

# 在本文中,我们采用信用评分模型的变量选择方法,通过WOE分析方法,即是通过比较指标分箱和对应分箱的违约概率来确定指标是否符合经济意义。
# 首先我们对变量进行离散化(分箱)处理。
# 5.1 分箱处理
# 变量分箱(binning)是对连续变量离散化(discretization)的一种称呼。
# 信用评分卡开发中一般有常用的等距分段、等深分段、最优分段。
# 其中等距分段(Equval length intervals)是指分段的区间是一致的,比如年龄以十年作为一个分段;
# 等深分段(Equal frequency intervals)是先确定分段数量,然后令每个分段中数据数量大致相等;
# 最优分段(Optimal Binning)又叫监督离散化(supervised discretizaion),使用递归划分(Recursive Partitioning)将连续变量分为分段,背后是一种基于条件推断查找较佳分组的算法。

# 我们首先选择对连续变量进行最优分段,在连续变量的分布不满足最优分段的要求时,再考虑对连续变量进行等距分段。最优分箱的代码如下:
# 定义自动分箱函数

from scipy import stats
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,duplicates="drop")}) 
        # 后面报错You can drop duplicate edges by setting the 'duplicates' kwarg,所以回到这里补充duplicates参数
        # pandas中使用qcut(),边界易出现重复值,如果为了删除重复值设置 duplicates=‘drop’,则易出现于分片个数少于指定个数的问题
        # 经尝试,设置duplicates参数为“drop”可行,而不能设置为“raise”。
        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)
    woe=list(d4['woe'].round(3))
    return d4

# 此定义函数暂未理解通透,暂且保留。这里先直接使用。
# 原帖代码没有导入scipy.stats模块,会导致下一条语句运行报错,上面补上,搞定。
# 原帖代码qcut()函数中没有设置duplicates参数,上面补上,搞定。
# 自定义函数分箱RevolvingUtilizationOfUnsecuredLines时报错You can drop duplicate edges by setting the 'duplicates' kwarg
# 所以先回来删除重复值。删除后发现没有解决问题,真正解决问题是在qcut()函数中设置duplicates参数为“drop”(不能设置为“raise”)
data=data.drop_duplicates(subset=None,keep='first',inplace=False)
data.shape
(119703, 11)
# 针对我们将使用最优分段对于数据集中的RevolvingUtilizationOfUnsecuredLines、age、DebtRatio和MonthlyIncome进行分类。
mono_bin(data.SeriousDlqin2yrs,data.RevolvingUtilizationOfUnsecuredLines)
============================================================
        min           max    sum  total      rate       woe
0  0.000000      0.035034  29333  29926  0.980184  1.298275
1  0.035037      0.176771  29205  29926  0.975907  1.098457
2  0.176777      0.577036  28305  29925  0.945865  0.257613
3  0.577040  50708.000000  24607  29926  0.822262 -1.071254
minmaxsumtotalratewoe
00.0000000.03503429333299260.9801841.298275
10.0350370.17677129205299260.9759071.098457
20.1767770.57703628305299250.9458650.257613
30.57704050708.00000024607299260.822262-1.071254
mono_bin(data.SeriousDlqin2yrs,data.age)
============================================================
    min  max   sum  total      rate       woe
0    21   30  7913   8885  0.890602 -0.506093
1    31   34  6640   7383  0.899363 -0.412828
2    35   38  7594   8386  0.905557 -0.342447
3    39   41  7131   7849  0.908523 -0.307262
4    42   43  4890   5362  0.911973 -0.265031
5    44   46  8163   8868  0.920501 -0.153830
6    47   48  5776   6274  0.920625 -0.152133
7    49   51  8545   9280  0.920797 -0.149768
8    52   53  5454   5901  0.924250 -0.101453
9    54   56  7922   8463  0.936075  0.080980
10   57   59  7517   7946  0.946011  0.260466
11   60   61  4942   5200  0.950385  0.349567
12   62   64  7464   7776  0.959877  0.571844
13   65   68  6968   7212  0.966167  0.748916
14   69   75  7911   8141  0.971748  0.934931
15   76  103  6620   6777  0.976833  1.138606
minmaxsumtotalratewoe
02130791388850.890602-0.506093
13134664073830.899363-0.412828
23538759483860.905557-0.342447
33941713178490.908523-0.307262
44243489053620.911973-0.265031
54446816388680.920501-0.153830
64748577662740.920625-0.152133
74951854592800.920797-0.149768
85253545459010.924250-0.101453
95456792284630.9360750.080980
105759751779460.9460110.260466
116061494252000.9503850.349567
126264746477760.9598770.571844
136568696872120.9661670.748916
146975791181410.9717480.934931
1576103662067770.9768331.138606
mono_bin(data.SeriousDlqin2yrs,data.MonthlyIncome)
============================================================
      min      max    sum  total      rate       woe
0     0.0   3400.0  27355  30073  0.909620 -0.293996
1  3401.0   5400.0  27655  30008  0.921588 -0.138884
2  5401.0   8200.0  27925  29725  0.939445  0.138736
3  8201.0  49750.0  28515  29897  0.953775  0.423899
minmaxsumtotalratewoe
00.03400.027355300730.909620-0.293996
13401.05400.027655300080.921588-0.138884
25401.08200.027925297250.9394450.138736
38201.049750.028515298970.9537750.423899
# 针对不能最优分箱的变量,分箱如下:

# 连续变量离散化
pinf = float('inf')#正无穷大
ninf = float('-inf')#负无穷大

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]

# 原贴代码没有定义pinf和ninf,这里已补充定义。

# 5.2 WOE

# WoE分析, 是对指标分箱、计算各个档位的WoE值并观察WoE值随指标变化的趋势。其中WoE的数学定义是:
# woe=ln(goodattribute/badattribute)
# 在进行分析时,我们需要对各指标从小到大排列,并计算出相应分档的WoE值。其中正向指标越大,WoE值越小;反向指标越大,WoE值越大。正向指标的WoE值负斜率越大,反响指标的正斜率越大,则说明指标区分能力好。WoE值趋近于直线,则意味指标判断能力较弱。若正向指标和WoE正相关趋势、反向指标同WoE出现负相关趋势,则说明此指标不符合经济意义,则应当予以去除。
# woe函数实现在上一节的mono_bin()函数里面已经包含,这里不再重复。


# 5.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

#自定义分箱函数
# ——该定义函数参考另一篇帖子:https://blog.csdn.net/sunyaowu315/article/details/82981216 
def self_bin(Y,X,cat):
    good=Y.sum()
    bad=Y.count()-good
    d1=pd.DataFrame({'X':X,'Y':Y,'Bucket':pd.cut(X,cat)})
    d2=d1.groupby('Bucket', as_index = True)
    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'))
    print("=" * 60)
    print(d4)
    woe = list(d4['woe'].round(3))
    return d4, iv,woe
dfx1, ivx1,cutx1,woex1 = mono_bin(data.SeriousDlqin2yrs, data.RevolvingUtilizationOfUnsecuredLines,n=10)
dfx2, ivx2,cutx2,woex2 = mono_bin(data.SeriousDlqin2yrs, data.age, n=10)
dfx4, ivx4,cutx4,woex4 =mono_bin(data.SeriousDlqin2yrs, data.DebtRatio, n=20)
dfx5, ivx5,cutx5,woex5 =mono_bin(data.SeriousDlqin2yrs, data.MonthlyIncome, n=10)

# 连续变量离散化
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]
dfx3, ivx3,woex3 = self_bin(data.SeriousDlqin2yrs, data['NumberOfTime30-59DaysPastDueNotWorse'], cutx3)
dfx6, ivx6,woex6= self_bin(data.SeriousDlqin2yrs, data['NumberOfOpenCreditLinesAndLoans'], cutx6)
dfx7, ivx7,woex7 = self_bin(data.SeriousDlqin2yrs, data['NumberOfTimes90DaysLate'], cutx7)
dfx8, ivx8,woex8 = self_bin(data.SeriousDlqin2yrs, data['NumberRealEstateLoansOrLines'], cutx8)
dfx9, ivx9,woex9 = self_bin(data.SeriousDlqin2yrs, data['NumberOfTime60-89DaysPastDueNotWorse'], cutx9)
dfx10, ivx10,woex10 = self_bin(data.SeriousDlqin2yrs, data['NumberOfDependents'], cutx10)
============================================================
        min           max    sum  total      rate       woe  goodattribute  \
0  0.000000      0.035034  29333  29926  0.980184  1.298275       0.263194   
1  0.035037      0.176771  29205  29926  0.975907  1.098457       0.262046   
2  0.176777      0.577036  28305  29925  0.945865  0.257613       0.253970   
3  0.577040  50708.000000  24607  29926  0.822262 -1.071254       0.220790   

   badattribute  
0      0.071853  
1      0.087362  
2      0.196292  
3      0.644493  
============================================================
   min  max    sum  total      rate       woe  goodattribute  badattribute
0   21   33  12867  14407  0.893108 -0.480116       0.115451      0.186599
1   34   39  11600  12806  0.905825 -0.339303       0.104083      0.146129
2   40   44  12301  13454  0.914301 -0.235686       0.110372      0.139707
3   45   49  14269  15543  0.918034 -0.187071       0.128031      0.154368
4   50   53  11069  11978  0.924111 -0.103441       0.099318      0.110142
5   54   58  13016  13846  0.940055  0.149510       0.116788      0.100569
6   59   63  12629  13270  0.951696  0.377722       0.113315      0.077669
7   64   70  11804  12185  0.968732  0.830395       0.105913      0.046165
8   71  103  11895  12214  0.973882  1.015683       0.106729      0.038653
============================================================
   min      max     sum   total      rate  woe  goodattribute  badattribute
0  0.0  61106.5  111450  119703  0.931054  0.0            1.0           1.0
============================================================
      min      max    sum  total      rate       woe  goodattribute  \
0     0.0   3400.0  27355  30073  0.909620 -0.293996       0.245446   
1  3401.0   5400.0  27655  30008  0.921588 -0.138884       0.248138   
2  5401.0   8200.0  27925  29725  0.939445  0.138736       0.250561   
3  8201.0  49750.0  28515  29897  0.953775  0.423899       0.255855   

   badattribute  
0      0.329335  
1      0.285108  
2      0.218103  
3      0.167454  
============================================================
             min  max    sum  total      rate       woe  goodattribute  \
Bucket                                                                   
(-inf, 0.0]    0    0  95431  99609  0.958056  0.525572       0.856267   
(0.0, 1.0]     1    1  11501  13499  0.851989 -0.852712       0.103194   
(1.0, 3.0]     2    3   3900   5466  0.713502 -1.690547       0.034993   
(3.0, 5.0]     4    5    510    917  0.556161 -2.377402       0.004576   
(5.0, inf]     6   13    108    212  0.509434 -2.565259       0.000969   

             badattribute  
Bucket                     
(-inf, 0.0]      0.506240  
(0.0, 1.0]       0.242094  
(1.0, 3.0]       0.189749  
(3.0, 5.0]       0.049315  
(5.0, inf]       0.012601  
============================================================
             min  max    sum  total      rate       woe  goodattribute  \
Bucket                                                                   
(-inf, 1.0]    0    1   3255   3880  0.838918 -0.952803       0.029206   
(1.0, 2.0]     2    2   4159   4613  0.901582 -0.388067       0.037317   
(2.0, 3.0]     3    3   6020   6570  0.916286 -0.210075       0.054015   
(3.0, 5.0]     4    5  17435  18729  0.930909 -0.002258       0.156438   
(5.0, inf]     6   58  80581  85911  0.937959  0.112912       0.723024   

             badattribute  
Bucket                     
(-inf, 1.0]      0.075730  
(1.0, 2.0]       0.055010  
(2.0, 3.0]       0.066642  
(3.0, 5.0]       0.156791  
(5.0, inf]       0.645826  
============================================================
             min  max     sum   total      rate       woe  goodattribute  \
Bucket                                                                     
(-inf, 0.0]    0    0  107586  113143  0.950885  0.360233       0.965330   
(0.0, 1.0]     1    1    2857    4301  0.664264 -1.920644       0.025635   
(1.0, 3.0]     2    3     844    1771  0.476567 -2.696800       0.007573   
(3.0, 5.0]     4    5     111     344  0.322674 -3.344508       0.000996   
(5.0, inf]     6   17      52     144  0.361111 -3.173544       0.000467   

             badattribute  
Bucket                     
(-inf, 0.0]      0.673331  
(0.0, 1.0]       0.174967  
(1.0, 3.0]       0.112323  
(3.0, 5.0]       0.028232  
(5.0, inf]       0.011147  
============================================================
             min  max    sum  total      rate       woe  goodattribute  \
Bucket                                                                   
(-inf, 0.0]    0    0  39502  43118  0.916137 -0.212016       0.354437   
(0.0, 1.0]     1    1  39268  41621  0.943466  0.211720       0.352337   
(1.0, 2.0]     2    2  25027  26570  0.941927  0.183227       0.224558   
(2.0, 3.0]     3    3   4974   5345  0.930589 -0.007222       0.044630   
(3.0, inf]     4   54   2679   3049  0.878649 -0.623303       0.024038   

             badattribute  
Bucket                     
(-inf, 0.0]      0.438144  
(0.0, 1.0]       0.285108  
(1.0, 2.0]       0.186962  
(2.0, 3.0]       0.044953  
(3.0, inf]       0.044832  
============================================================
             min  max     sum   total      rate       woe  goodattribute  \
Bucket                                                                     
(-inf, 0.0]    0    0  107494  113592  0.946317  0.266475       0.964504   
(0.0, 1.0]     1    1    3331    4792  0.695117 -1.778848       0.029888   
(1.0, 3.0]     2    3     571    1179  0.484309 -2.665785       0.005123   
(3.0, inf]     4   11      54     140  0.385714 -3.068363       0.000485   

             badattribute  
Bucket                     
(-inf, 0.0]      0.738883  
(0.0, 1.0]       0.177027  
(1.0, 3.0]       0.073670  
(3.0, inf]       0.010420  
============================================================
             min   max    sum  total      rate       woe  goodattribute  \
Bucket                                                                    
(-inf, 0.0]  0.0   0.0  61292  65127  0.941115  0.168481       0.549951   
(0.0, 1.0]   1.0   1.0  22482  24283  0.925833 -0.078626       0.201723   
(1.0, 2.0]   2.0   2.0  16567  18033  0.918705 -0.178124       0.148650   
(2.0, 3.0]   3.0   3.0   7922   8707  0.909843 -0.291284       0.071081   
(3.0, 5.0]   4.0   5.0   2983   3322  0.897953 -0.428315       0.026765   
(5.0, inf]   6.0  20.0    204    231  0.883117 -0.580716       0.001830   

             badattribute  
Bucket                     
(-inf, 0.0]      0.464680  
(0.0, 1.0]       0.218224  
(1.0, 2.0]       0.177632  
(2.0, 3.0]       0.095117  
(3.0, 5.0]       0.041076  
(5.0, inf]       0.003272  
# 生成的IV图代码:

ivlist=[ivx1,ivx2,ivx3,ivx4,ivx5,ivx6,ivx7,ivx8,ivx9,ivx10]#各变量IV
index=['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))+1
ax1.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(x4)、MonthlyIncome(x5)、NumberOfOpenCreditLinesAndLoans(x6)、NumberRealEstateLoansOrLines(x8)和NumberOfDependents(x10)变量的IV值明显较低,所以予以删除。

在这里插入图片描述

data.columns  # 用于对比上条语句结果,查看x2、x4、x5、x6、x8、x10具体对应哪些字段
Index(['SeriousDlqin2yrs', 'RevolvingUtilizationOfUnsecuredLines', 'age',
       'NumberOfTime30-59DaysPastDueNotWorse', 'DebtRatio', 'MonthlyIncome',
       'NumberOfOpenCreditLinesAndLoans', 'NumberOfTimes90DaysLate',
       'NumberRealEstateLoansOrLines', 'NumberOfTime60-89DaysPastDueNotWorse',
       'NumberOfDependents'],
      dtype='object')

小结

# 小结

# 本文主要介绍了信用评分模型开发过程中的数据预处理、探索性分析和变量选择。
# 数据预处理主要针对缺失值用随机森林法和直接剔除法进行处理,对于异常值主要根据实际情况和箱形图的数据分布,对异常值进行剔除;探索性分析主要对各变量的分布情况进行初始的探究;变量选择主要考虑了变量的分箱方法,根据分箱结果计算WOE值,然后检查变量之间的相关性,根据各变量的IV值来选择对数据处理有好效果的变量。

# 接下来会介绍信用评分模型的模型开发、模型评估和信用评分等。
  • 6
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值