文章目录
一、构建信用风险类型的特征
将信息度比较分散的特征综合起来变成信息度比较高的特征。
已经加工成型的信息
表—Master,该表是人维度的信息
- idx:每一笔贷款的unique key
- UserInfo_*:借款人特征字段
- WeblogInfo_*:Info网络行为字段
- Education_Info*:学历学籍字段
- ThirdParty_Info_PeriodN_*:第三方数据时间段N字段
- SocialNetwork_*:社交网络字段
- LinstingInfo:借款成交时间
- Target:违约标签(1 = 贷款违约,0=正常还款)
1.需要衍生的信息—表1
表—借款人的登录信息,该表是以事件为维度的信息,需要转变成以人为维度的信息
- ListingInfo:借款成交时间
- LogInfo1:操作代码—类别型
- LogInfo2:操作类别—类别型
- LogInfo3:登陆时间
- idx:每一笔贷款的unique key
时间切片—两个时刻间的跨度
例:申请日期之前30天内的登录次数
基于时间切片进行衍生:
比如:申请日期之前180天内,平均内每月(30天)的登陆次数
常用的时间切片有:(1,2个)月,(1,2个)季度,半年,1年,2年
时间切片的选择:不能太长:保证大多数样本都能覆盖到
不能太短:丢失信息
如何选择最佳的时间切片?
描述基于不同时间切片所对应的覆盖度,选取覆盖度达到95%的时间切片
如何选取在最佳时间切片内所衍生的信息?
在该表中,在同一时间切片内,可以衍生的特征有:
- 操作的次数
- 不同类别/代码的个数
- 同一类别/代码的平均操作次数
共30个变量。
2.需要衍生的信息—表2
借款人修改信息。该表是以事件为维度的信息,需要转变成以人为维度的信息
- ListingInfo1:借款成交时间
- UserupdateInfo1:修改内容
- UserupdateInfo2:修改时间
特别地,需要做数据预处理
- 统一大小写
- 统一Phone、Mobilephone
关注几个特殊变量
- 是否修改IDNumber(身份证号码)
- 是否修改Mobilephone(电话号码)
- 是否修改HASBUYCAR(是否有车)
- 是否修改MARRIAGESTATUSID(婚姻状况改变)
3.数据清洗
对于类别型变量,
删除缺失率超过50%的变量,剩余变量中的缺失作为一种状态。
对于连续型变量,
删除缺失率超过30%的变量,剩余变量利用随机抽样法对缺失进行补缺;如果使用均值填充的话,最好将极端值剔除掉!
注:在申请评分卡模型中,连续变量中的缺失也可以当成一种状态。
二、特征的分箱
分箱的定义:
- 将连续变量离散化(组1、组2…)
- 将多状态的离散变量合并成少状态
分箱的重要性:
- 稳定性:避免特征中无意义的波动对评分带来的波动
- 健壮性:避免了极端值的影响
分箱的优势:
- 可以将缺失作为独立的一个箱代入模型中
- 将所有变量变换到相似的尺度上
分箱的限制:
- 计算量大,分箱后需要编码
1.分箱的方法
分箱方法分为有监督与无监督的方法。对于有监督的方法,需要根据目标变量来对变量进行分箱。比如:在流失预警模型中,根据目标变量流失与否来决定收入是如何分箱的
- Best—KS
- ChiMerge
对于无监督方法,常用的有:等频、等距、聚类(K-means)等
2.监督式分箱法:Best-KS
注意:bad rate不单调是一个比较强的条件,当数据质量比较好时,可以用这个
Best-KS的缺陷:
- 只能针对二分类的情况计算KS
3.卡方分箱法—ChiMerge
卡方分箱法是一种自底向上的数据离散化方法。它依赖于卡方检验:具有最小卡方值的相邻区间合并到一起,直至满足确定的停止准则。基本思想:对于精确的离散化,相对类频率在一个区间内应当完全一致。因此,如果两个相邻的区间具有非常相似的类分布,则这两个区间可以合并;否则,它们应当保持分开(组内的差别很小,组间的差别很大)。而低卡方值表明它们具有相似的类分布。
和Best-KS相比,ChiMerge可以应用到多分类的情况下
组内的差别很小,组间的差别很大。具体到申请评分卡模型中,即组内的逾期率差不多,组间的逾期率差别较大。
4.无监督分箱方法
无监督的分箱方法是一种不依赖于目标变量的方法。一般有等频、等距、聚类(K-means)分箱
等距与等频是没有理由的,这两种方法完全忽略了目标变量与特征变量(属性)关联度的作用
5.分箱的注意点
对于连续型变量,
- 使用ChiMerge进行分箱(默认分成5个箱)
- 检查分箱后的bad rate单调性;倘若不满足,需要进行相邻两箱的合并,直到bad rate单调为止
- 上述过程是收敛的,因为当箱数为2时,bad rate自然单调
- 分箱必须覆盖所有训练样本外可能存在的值!
连续性变量,左开右闭
比如收入变量,在训练样本中只有[1004,1008,…1400],[2100,2134,…2500]
但是在测试样本中可能存在1500,此时1500不在任何一个区间,上述划分是有误的。应该[0 ~ 1004],(1004~1400],(1400 ~ 2500]…(10000 ~ 10e9)
对于类别型变量,
- 当类别数较少时,不需要分箱
- 当某个或者几个类别的bad rate为0时,需要和最小的非bad rate的箱进行合并
- 当该变量可以完全区分目标变量时,需要认真检查该变量的合理性
例如:“该申请者在本机构的历史信用行为”把客群的好坏样本完全区分时,需要检查该变量的合理性(有可能是事后变量)
6.多类别离散变量和连续变量的分箱的注意点
多类别离散变量
以bad rate代替原有值,转化成连续型变量再分箱
例:UserInfo_2,原始值有327个城市,分箱后有5个组别
连续型变量
把特殊值单独化为一组
比如:ThirdParty_Info_Period4_1: -1 U [0,1506],-1单独分为一组
7.特征信息度—IV
IV(Information Value)是衡量特征包含预测变量浓度的一种指标,其恒为正值。
特征信息度:
- 非负指标
- 高IV表示该特征和目标变量的关联度高
特征信息度的缺陷:
- 目标变量只能是二分类
- 过高的IV,可能有潜在的风险
- 特征分箱越细,IV越高
常用的阈值:
IV最大值不要超过0.8
特征信息度的计算:
特征信息度的作用:
- 挑选变量
1 .不进行特征挑选,样本矩阵很可能是奇异矩阵(转置×它本身是不可逆的)
2 .变量的维护是有成本的,模型的维护是有成本的。如果能够降低变量的个数,但是仍能保证模型的性能基本不变,降低了模型部署开发的成本。
三、WOE编码
一种有监督的编码方式,将预测类别的集中度的属性作为编码的数值。
优点:
- 将特征的值规范到相近的尺度上(有正有负,WOE的绝对值波动范围在0.1~3之间)
- 具有业务意义
缺点:
- 需要每箱中同时包含好、坏两个类别,由WOE的表达式就可以看得出来
1.计算公式
WOE大于0,表明第i组倾向于出现好的
WOE小于0,表明第i组倾向于出现坏的
2.WOE编码的意义
- 符号与好样本的比例相关
WOE大于0,表明第i组倾向于出现好的
WOE小于0,表明第i组倾向于出现坏的 - 要求回归模型的系数为负
四、信用风险中的变量分析
1.单变量分析
单变量分析主要考虑的是特征与目标变量的关联度以及特征自身的一些情况。以分箱后的WOE为值
-
用IV检验有效性,IV不能低于0.02
-
连续变量bad rate的单调性(可以放宽到U型)
bad rate 与WOE的趋势一致
当然也有可能不一样,毕竟公式不一样
bad rate = Bi / (Bi + Gi) -
单一区间的占比不宜过高
要求是占比最高的不超过90%(常用)或者占比最少的不低于5%,满足一项即可
2.多变量分析
多变量分析主要是为了避免共线性对建模造成的影响,同时,也实现了降维。
变量的两两相关性:
当变量之间相关性高时,只能保留一个
- 可以选择IV高的
- 可以选择分箱均衡的
WOE相关性矩阵:
多变量分析—变量之间的多重共线性:
当某个变量的VIF超过10,需要逐一删除解释变量。当删除掉xk时,发现VIF低于10,从{xk,xi}中删除掉IV较低的一个。
五、操作步骤总结
- 读取csv文件,并检查Idx的一致性
为了方便后续缺失值操作,一般先将表中代表缺失值的东西替换为np.nan
- 对表—借款人登录信息与借款人修改信息进行特征衍生
1 .查看不同时间切片的复杂度,选择复杂度大于95%的时间切片,这就是最大的时间窗口
2.以人为维度,分别统计每个Idx的总操作次数、总操作类数,每一类的平均操作次数,并且关注其中的特别变量 - 对连续型变量与类别型变量填充缺失值
删除缺失率超过50%的类别变量,剩余变量缺失作为一种状态
删除缺失率超过30%的连续型变量,剩余变量利用随机抽样法对缺失值进行补缺 - 对连续型变量与类别型变量进行卡方分箱
对于类别型变量,按照下列步骤:
1.如果变量的唯一值超过5个,我们就需要分箱;计算bad rate,并以bad rate对变量进行编码,按照bad rate进行排序,计算每一对相邻区间的卡方值,将卡方值最小的区间进行合并,直至分箱数为5
2.另外,
2.1 检查占比最高的组,如果有一组占比超过95%(90%),则删除该变量(占比高相当于常量型特征)
2.2 检查每一个分箱的bad rate,当某个或者几个类别的bad rate为0时,需要和最小的非bad rate的箱进行合并
对于连续型变量,我们需要做如下工作:
1.按ChiMerge拆分变量(默认分为5个bin)
2.检查bate rate,如果不是单调的话,我们减少箱数,直到bate rate是单调
3.如果最大bin占用超过90%,则删除变量
- 选择IV大于0.02的变量,并进行WOE编码
- 单因子分析与多因子分析
单因子分析,经过上述步骤就以满足
多因子分析,变量两两间具有相关性,则选择IV值大的变量
六、代码
写法规范:将自定义函数与主程序分开,将专门书写自定义函数的程序写入路径(增加到系统路径),然后在主程序里import
自定义函数文件
#!usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: admin
@file: scoredcard_functions.py
@time: 2021/03/11
@desc:
"""
import random
import pandas as pd
import numpy as np
def timeWindowSelection(df, daysCol, time_windows):
"""
计算每一个时间切片内的事件的累积频率
:param df: 数据集
:param daysCol:时间间隔
:param time_windows:时间窗口列表
:return:返回覆盖度
"""
freq_tw = {
}
for tw in time_windows:
freq = sum(df[daysCol].apply(lambda x: int(x <= tw)))
freq_tw[tw] = freq / df[daysCol].shape[0]
return freq_tw
def ChangeContent(x):
"""
数据预处理:统一大小写、统一_PHONE与_MOBILEPHONE
:param x: UserupdateInfo1列字符
:return:返回处理后的字符
"""
y = x.upper()
if y == '_MOBILEPHONE':
y = '_PHONE'
return y
def missingCategorical(df, x):
"""
计算类别型变量的缺失比例
:param df: 数据集
:param x: 类别型变量
:return: 返回缺失比例
"""
missing_vals = df[x].map(lambda x: int(x != x))
return sum(missing_vals) * 1.0 / df.shape[0]
def missingContinuous(df, x):
"""
计算连续型变量的缺失比例
:param df:
:param x:
:return:
"""
missing_vals = df[x].map(lambda x: int(np.isnan(x)))
return sum(missing_vals) * 1.0 / df.shape[0]
def makeUpRandom(x, sampledList):
"""
对于连续型变量,利用随机抽样法补充缺失值
:param x:连续型变量的值
:param sampledList:随机抽样的列表
:return:补缺后的值
"""
# 非缺失,直接返回;缺失,填充后返回
if x == x:
return x
else:
return random.sample(sampledList, 1)
def AssignBin(x, cutOffPoints):
'''
设置使得分箱覆盖所有训练样本外可能存在的值
:param x: the value of variable
:param cutOffPoints: the ChiMerge result for continous variable连续变量的卡方分箱结果
:return: bin number, indexing from 0
for example, if cutOffPoints = [10,20,30], if x = 7, return Bin 0. If x = 35, return Bin 3
即将cutOffPoints = [10,20,30]分为4段,[0,10],(10,20],(20,30],(30,30+]
'''
numBin = len(cutOffPoints) + 1
if x <= cutOffPoints[0]:
return 'Bin 0'
elif x > cutOffPoints[-1]:
return 'Bin {}'.format(numBin - 1)
else:
for i in range(0, numBin - 1):
if cutOffPoints[i] < x <= cutOffPoints[i + 1]:
return 'Bin {}'.format(i + 1)
def MaximumBinPcnt(df, col):
"""
:param df:
:param col:
:return:
"""
N = df.shape[0]
total = df.groupby([col])[col].count()
pcnt = total * 1.0 / N
return max(pcnt)
def CalcWOE(df, col, target):
'''
计算WOE
:param df: dataframe containing feature and target
:param col: 需要计算WOE与IV的特征变量,通常是类别型变量
:param target: 目标变量
:return: WOE and IV in a dictionary
'''
total = df.groupby([col])[target].count()
total = pd.DataFrame({
'total': total})
bad = df.groupby([col])[target].sum()
bad = pd.DataFrame({
'bad': bad})
regroup = total.merge(bad, left_index=True, right_index=True, how='left')
regroup.reset_index(level=0, inplace=True)
# 总数量
N = sum(regroup['total'])
# 坏的数量
B = sum(regroup['bad'])
regroup['good'] = regroup['total'] - regroup['bad']
# 好的数量
G = N - B
regroup['bad_pcnt'] = regroup['bad'].map(lambda x: x * 1.0 / B)
regroup['good_pcnt'] = regroup['good'].map(lambda x: x * 1.0 / G)
regroup['WOE'] = regroup.apply(lambda x: np.log(x.good_pcnt * 1.0 / x.bad_pcnt), axis=1)
# 计算WOE
WOE_dict = regroup[[col, 'WOE']].set_index(col).to_dict(orient='index')
# 计算IV
IV = regroup.apply(lambda x: (x.good_pcnt - x.bad_pcnt) * np.log(x.good_pcnt * 1.0 / x.bad_pcnt), axis=1)
IV = sum(IV)
return {
"WOE": WOE_dict, 'IV': IV}
def BadRateEncoding(df, col, target):
'''
bad rate编码
:param df: dataframe containing feature and target
:param col: 需要以bad rate进行编码的特征变量,通常是类别型变量
:param target: good/bad indicator
:return: 返回被bad rate编码的类别型变量
'''
total = df.groupby([col])[target].count()
total = pd.DataFrame({
'total': total})
bad = df.groupby([col])[target].sum()
bad = pd.DataFrame({
'bad': bad})
regroup = total.merge(bad, left_index=True, right_index=True, how='left')
regroup.reset_index(level=0, inplace=True)
regroup['bad_rate'] = regroup.apply(lambda x: x.bad