【风控算法】一、变量分箱、WOE和IV值计算
一、变量分箱
变量分箱常见于逻辑回归评分卡的制作中,在入模前,需要对原始变量值通过分箱映射成woe值。举例来说,如“年龄”这一变量,我们需要找到合适的切分点,将连续的年龄打散到不同的”箱“中,并按年龄落入的“箱”对变量进行编码。
关于变量分箱的作用,相关资料中的解释有很多,我认为变量分箱最主要有三个作用:
- 归一化:分箱且woe编码映射后的变量,可以将变量归一到近似尺度上;
- 引入非线性:对于逻辑回归这类线性模型,引入变量分箱可以增强模型的拟合能力;
- 增强鲁棒性:分箱可以避免异常数据对模型的影响
二、IV值和WOE
(1)WOE
WOE(Weight of Evidence),是一种对变量编码的形式。通过对分箱后每一箱WOE值的计算,可以完成变量从原始数值->WOE数值的映射。
=ln(\frac{y^1_i}{y^1})-ln(\frac{y^0_i}{y^0})=ln(\frac{y^1_i}{y^0_i})-ln(\frac{y^1}{y^0})
\]
关于WOE的理解,主要有如下几点:
- WOE可以理解成分箱区间内的正负样本差异相对于整体的差异。机器学习二分类中,通常将分类任务中更关注的类label设为”1“,因此WOE越大说明该分箱内的样本越可能为“1”类;
- 经过WOE编码,实现了按WOE排序的区间正样本比例呈单调趋势。
(2)IV值
IV(Information Value)是基于WOE计算来的:
\]
(3)KL散度
KL散度(相对熵)通常用于衡量两个分布之间的差异,机器学习中,\(P\)往往代表样本的真实分布,而\(Q\)代表样本的预测分布,那么KL散度可以计算两个分布之间的差异:
\]
如果\(P\)的分布和\(Q\)的分布越接近,KL散度的值就会越小。
KL散度通常被称作KL距离,但却只满足距离的非负性和同一性,不满足对称性和直递性,因此不是严格意义上的“距离"。
设分箱后,\(y=1\)的分布为\(p_1(x)\),\(y=0\)的分布为\(p_0(x)\),那么
KL(p_0,p_1)+KL(p_1,p_0)
&= \sum p(x_i)*log(\frac{p(x_i)}{q(x_i)})+\sum q(x_i)*log(\frac{q(x_i)}{p(x_i)})\\
&=\sum{(p(x_i)-q(x_i))*(log(p(x_i))-log(q(x_i)))}\\
&= IV
\end{aligned}
\]
由此可知,\(IV=KL(p_0,p_1)+KL(p_1,p_0)\)
据此可以得到关于IV和KL散度更加深刻的理解:
- IV值衡量了分组下好坏样本分布差异,IV值越大分布差异越大,IV值越小分布差异越小
- IV是KL散度的一种对称化处理
三、分箱方法
变量分箱主要包括无监督方法和有监督方法,无监督的方法,在分箱中没有用到y有关的信息,而有监督的分箱方法,在分箱时引入了y的分布,运用训练集找到分箱的切点。
(1)无监督分箱
a.等频分箱/等距分箱
如字面理解,等频分箱和等距分箱可直接通过pandas的qcut和cut实现
等频分箱:qcut
IV=0.0158
等距分箱:cut
IV=0.019
b.聚类分箱
无监督分箱中除了等频分箱和等距分箱外,也可以使用KMeans
算法实现聚类分箱,一个粗糙的代码实现如下:
from sklearn.cluster import KMeans
def kmeansbin(data,x='x',bin_nums=5):
minus_ = 999*(data[x].max() - data[x].min())
bin_nums = 5
clf = KMeans(n_clusters=bin_nums-1, random_state=999)
_ = clf.fit_predict(data[[x,x]])
cut_point = sorted(clf.cluster_centers_[:,0])
#cut_point = [(clf_center[i]+clf_center[i+1])/2 for i in range(len(clf_center)-1)]
return [data[x].min()-minus_,] + cut_point + [data[x].max()+minus_,]
效果如下: IV=0.0208
(2)有监督分箱
a.决策树分箱
决策树分箱利用单变量生成决策树,利用决策树的分裂规则完成变量分箱,因分箱速度快且效果比较稳定,同时也有sklearn
接口可以调用,因此较为常用,一个简单的代码实现版本如下:
# 决策树分箱
from sklearn.tree import DecisionTreeClassifier
def treebin(df,x='x',y='y',max_leaf_nodes=5,min_samples_leaf=0.05):
# 训练决策树
df = df.copy()
df[x] = df[x].fillna(-9999)
model = DecisionTreeClassifier(criterion='entropy',max_leaf_nodes=max_leaf_nodes,min_samples_leaf=min_samples_leaf)
model.fit(df[[x]],df[[y]])
# 从树结构获取决策边界
right_node = model.tree_.children_right
left_node = model.tree_.children_left
tree_threshold = model.tree_.threshold
# sklearn树结构,详见:https://scikit-learn.org/stable/auto_examples/tree/plot_unveil_tree_structure.html#sphx-glr-auto-examples-tree-plot-unveil-tree-structure-py
final_cut = [tree_threshold[i] for i,node in enumerate(zip(right_node,left_node)) if node[0]!=node[1]]
minus_ = df[x].max() - df[x].min()
# 返回分箱边界
return [df[x].min()-999*minus_,]+sorted(final_cut)+[df[x].max()+999*minus_]
分箱效果如下: IV=0.0286
b.卡方分箱
卡方分箱通过计算变量所有不同值之间的卡方值,并对卡方值最低的区间进行合并迭代,最终达到迭代要求的剩余箱数来完成分箱,简要代码实现如下:
def chibin(data,x='x',y='y',bin_nums=5,confidenceVal=6.635):
def cal_chi(arr):
sum_ = np.sum(arr[:,:2])
denom_ = np.sum(arr[0,:2])*np.sum(arr[1,:2])*np.sum(arr[:,0])*np.sum(arr[:,1])
chi_ = (arr[0][0]*arr[1][1] - arr[0][1]*arr[1][0])**2 * sum_ / denom_
return chi_
# 所有不同值,计数
total_num = data.groupby([x])[y].apply(lambda x:{'nums':x.count()}).unstack()
# 正样本计数
total_num['x'] = total_num.index.tolist()
total_num['pos_nums'] = data.groupby([x])[y].sum()
total_num['neg_nums'] = data.groupby([x])[y].apply(lambda x:x.count()-x.sum())
total_num = total_num[['pos_nums','neg_nums','x']]
total_num = total_num.values
# 第一步:合并连续的全正/全负区间
i = 0
while i<len(total_num)-1:
if (total_num[i][0] == total_num[i+1][0] == 0) or (total_num[i][1] == total_num[i+1][1] == 0):
total_num[i,2] = 0
total_num[i] += total_num[i+1]
total_num = np.delete(total_num,i+1,axis=0)
else:
i += 1
# 第二步:计算相邻区间的卡方值,并存入数组
arr_chi = np.array([])
for i in range(len(total_num)-1):
arr_chi = np.append(arr_chi,cal_chi(total_num[i:i+2]))
# 第三部:分箱合并
while len(arr_chi)>bin_nums and min(arr_chi)<confidenceVal:
idxmin = np.argmin(arr_chi)
# 与下一个区间合并
total_num[idxmin,2] = 0
total_num[idxmin] += total_num[idxmin+1]
total_num = np.delete(total_num,idxmin+1,axis=0)
# 更新卡方值
# 如果 最低卡方值是前两个区间
# 如果 最低卡方值是最后两个区间
# 如果 最低卡方值在中间
if idxmin == 0:
arr_chi[0] = cal_chi(total_num[:2])
arr_chi = np.delete(arr_chi,1,axis=0)
elif idxmin == len(arr_chi)-1:
arr_chi[idxmin-1] = cal_chi(total_num[idxmin-1:])
arr_chi = np.delete(arr_chi,idxmin,axis=0)
else:
arr_chi[idxmin-1] = cal_chi(total_num[idxmin-1:idxmin+1,:])
arr_chi[idxmin+1] = cal_chi(total_num[idxmin:idxmin+2,:])
arr_chi = np.delete(arr_chi,idxmin,axis=0)
# 合并完毕,返回分箱边界
minus_ = total_num[:,2].max() - total_num[:,2].min()
return [total_num[:2].min() - 999*minus_,] + sorted(total_num[:-1,2]) + [total_num[:2].max() + 999*minus_,]
卡方值和KL散度一样,都是用于衡量分布之间差异的指标,因此处不是重点,所以不再详细说明
效果如下: IV=0.0303
c.BestKS分箱
BestKS分箱通过不断计算所有可能切分点的KS,每次分箱选择让KS最大的切分点,最终达到要求的分箱数来完成分箱,具体的实现思路其他文章中都有较为详细的介绍,因此此处也不再赘述。
一个简要的代码实现:
def bestksbin(data,x='x',y='y',bin_nums=5,stopl=0.05):
cut_point = []
minus_ = 999*(data[x].max() - data[x].min())
if len(data[x].unique())<=bin_nums:
cut_point = data[x].unique()
return [data[x].min()-minus_,] + cut_point + [data[x].max()+minus_,]
cut_point.append(binks(data,x,y)[0])
while len(cut_point) < bin_nums-1:
bestks = -999
bestcut = None
icnt = 0
while icnt <= len(cut_point):
if icnt == 0:
tmpcp,tmpks = binks(data[data[x]<=cut_point[icnt]],x,y,l=data.shape[0],stopl=stopl)
elif icnt == len(cut_point):
tmpcp,tmpks = binks(data[data[x]>cut_point[icnt-1]],x,y,l=data.shape[0],stopl=stopl)
else:
tmpcp,tmpks = binks(data[(data[x]>cut_point[icnt-1])&(data[x]<=cut_point[icnt])],x,y,l=data.shape[0],stopl=stopl)
if tmpks > bestks:
bestcut,bestks = tmpcp,tmpks
icnt += 1
if not bestcut:
break
cut_point.append(bestcut)
cut_point = sorted(cut_point)
return [data[x].min()-minus_,] + cut_point + [data[x].max()+minus_,]
def binks(data,x='x',y='y',l=10000,stopl=0.05):
if (len(data[x].unique()) == 1) or (data.shape[0]/l<stopl) or (data[y].sum()==data.shape[0]) or (data[y].sum()==0):
return None,-9999
tmp = data.groupby(x)[y].apply(lambda x:{'count':x.count(),'bad':x.sum()}).unstack()
tmp['x'] = tmp.index.tolist()
tmp['good'] = tmp['count'] - tmp['bad']
tmp['cumgood'] = tmp['good'].cumsum()
tmp['cumbad'] = tmp['bad'].cumsum()
tmp['cumgood_ratio'] = tmp['cumgood'] / (data.shape[0] - data['y'].sum())
tmp['cumbad_ratio'] = tmp['cumbad'] / data['y'].sum()
tmp['KS'] = abs(tmp['cumgood_ratio']-tmp['cumbad_ratio'])
tmp = tmp.reset_index(drop=True)
tmp = tmp[tmp['x'] != tmp['x'].max()]
maxidx = tmp['KS'].argmax()
return tmp.loc[maxidx ,'x'],tmp.loc[maxidx,'KS']
分箱效果如下: IV=0.0281
总结
本文主要记录了变量分箱、WOE和IV值计算,其中包括了有监督分箱的几种方法的代码实现,代码写的仓促可能其中有一些疏漏,在未来的学习和研究中可能会优化其中代码。
参考资料
【风控算法】一、变量分箱、WOE和IV值计算的更多相关文章
- 数据分箱:等频分箱,等距分箱,卡方分箱,计算WOE、IV
转载:https://zhuanlan.zhihu.com/p/38440477 转载:https://blog.csdn.net/starzhou/article/details/78930490 ...
- 特征工程之分箱--Best-KS分箱
变量的KS值 KS(Kolmogorov-Smirnov)用于模型风险区分能力进行评估,指标衡量的是好坏样本累计部分之间的差距 .KS值越大,表示该变量越能将正,负客户的区分程度越大.通常来说,KS& ...
- 【转】风控中的特征评价指标(一)——IV和WOE
转自:https://zhuanlan.zhihu.com/p/78809853 1.IV值的用途 IV,即信息价值(Information Value),也称信息量. 目前还只是在对LR建模时用到过 ...
- 特征重要度 WoE、IV、BadRate
1.IV的用途 IV的全称是Information Value,中文意思是信息价值,或者信息量. 我们在用逻辑回归.决策树等模型方法构建分类模型时,经常需要对自变量进行筛选.比如我们有200个候选自变 ...
- 信息熵、信息增益、信息增益率、gini、woe、iv、VIF
整理一下这几个量的计算公式,便于记忆 采用信息增益率可以解决ID3算法中存在的问题,因此将采用信息增益率作为判定划分属性好坏的方法称为C4.5.需要注意的是,增益率准则对属性取值较少的时候会有偏好,为 ...
- 对数据集进行最优分箱和WOE转换
对数据集分箱的方式三种,等宽等频最优,下面介绍对数据集进行最优分箱,分箱的其他介绍可以查看其他的博文,具体在这就不细说了: 大体步骤: 加载数据: 遍历所有的feature, 分别处理离散和连续特征: ...
- 笔记+R︱风控模型中变量粗筛(随机森林party包)+细筛(woe包)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 本内容来源于CDA-DSC课程内容,原内容为& ...
- 【ArchSummit干货分享】个推大数据金融风控算法实践
作者:个推高级数据工程师 晓骏 众所周知,金融是数据化程度最高的行业之一,也是人工智能和大数据技术重要的应用领域.随着大数据收集.存储.分析和模型技术日益成熟,大数据技术逐渐应用到金融风控的各个环节. ...
- R语言︱噪声数据处理、数据分组——分箱法(离散化、等级化)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 分箱法在实际案例操作过程中较为常见,能够将一些 ...
随机推荐
- 字符串与模式匹配算法(六):Needleman–Wunsch算法
一.Needleman-Wunsch 算法 尼德曼-翁施算法(英语:Needleman-Wunsch Algorithm)是基于生物信息学的知识来匹配蛋白序列或者DNA序列的算法.这是将动态算法应用于 ...
- amba web
arm amba doc https://developer.arm.com/docs
- nginx 支持https访问
1,先确认nginx安装时已编译http_ssl模块. 就是执行nginx -V命令查看是否存在--with-http_ssl_module.如果没有,则需要重新编译nginx将该模块加入.yum安装 ...
- docker中镜像的作用
镜像原理镜像 镜像到底是什么?镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码.运行时.库.环境变量和配置文件.1.Uni ...
- k8s入坑之路(13)服务迁移(定时任务 微服务 传统服务)
定时任务迁移kubernetes 服务迁移步骤 1.安装好java 2.安装好maven 项目打包 mvn package 测试传参运行 java -cp cronjob-demo-1.0-SNAPS ...
- Qt分析:Qt中的两种定时器
QTimer类的定时器 QTimer类定时器是QObject类定时器的扩展版或者说升级版,因为它可以提供更多的功能.比如说,它支持单次触发和多次触发. 使用QTimer类定时器的步骤: (1)创建一个 ...
- postman调试工具介绍及常用的快捷键收集
关于Postman postman基础功能介绍 使用postman进行接口自动化测试 快捷键大全 简单操作 请求 工具栏 接口 窗口 数据编辑 关于Postman Postman是一款功能强大的网页调 ...
- 架构小试之IDL
本文转载自我自己的博客,感兴趣的老爷们可以关注~:https://www.miaoerduo.com/2021/11/16/arch-idl/ 为什么IDL的介绍也放在这里呢?一方面是我想不到放哪里, ...
- ECharts + jsp 图表
... <%@ page language="java" pageEncoding="UTF-8"%> <%@page import=&quo ...
- php 递推 递归
思想:如何利用数学模式,来解决对应的需求问题,然后利用代码实现对应的数据模型(逻辑) 算法:使用代码实现对应的数学模型,从而解决对应的业务问题 递推算法是一种简单的算法,级通过已知条件,利用特定关系得 ...