1.前言
评分卡建模在金融行业应用得比较广泛,比如对客户的信贷诚信度进行评分。在建模过程中,对连续变量的分箱是一个必不可少的过程。正好我最近的项目也是要做一个积分卡,因此想对分箱做一个较全面的总结。
2.定义
何谓分箱,简单地说,分箱就是将连续变量离散化,将多状态的离散变量合并成少状态。
3.分箱的用处
- 离散特征的增加和减少都很容易,易于模型的快速迭代;
- 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
- 列表内容离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
- 列表内容逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;
- 离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
- 列表内容特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
- 特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。 可以将缺失作为独立的一类带入模型。
- 将所有变量变换到相似的尺度上。
4.分箱方法
分箱方法分为无监督分箱和有监督分箱。常用的无监督分箱方法有等频分箱,等距分箱和聚类分箱。有监督分箱主要有best-ks分箱和卡方分箱。基于我的项目中重点应用了卡方分箱,所以这里重点对卡方分箱做些总结。
5.卡方分箱的原理
卡方分箱是自底向上的(即基于合并的)数据离散化方法。它依赖于卡方检验:具有最小卡方值的相邻区间合并在一起,直到满足确定的停止准则。
基本思想:对于精确的离散化,相对类频率在一个区间内应当完全一致。因此,如果两个相邻的区间具有非常类似的类分布,则这两个区间可以合并;否则,它们应当保持分开。而低卡方值表明它们具有相似的类分布。
分箱步骤:
这里需要注意初始化时需要对实例进行排序,在排序的基础上进行合并。
卡方阈值的确定:
根据显著性水平和自由度得到卡方值
自由度比类别数量小1。例如:有3类,自由度为2,则90%置信度(10%显著性水平)下,卡方的值为4.6。
阈值的意义
类别和属性独立时,有90%的可能性,计算得到的卡方值会小于4.6。
大于阈值4.6的卡方值就说明属性和类不是相互独立的,不能合并。如果阈值选的大,区间合并就会进行很多次,离散后的区间数量少、区间大。
6.分完箱之后评估指标
分为箱之后,需要评估。在积分卡模型中,最常用的评估手段是计算出WOE和IV值。对于WOE和IV值的含义,我推荐博客:
https://blog.youkuaiyun.com/kevin7658/article/details/50780391
对于其计算方式,我后面代码会直接给出。
7.直接代码
def Chi2(df, total_col, bad_col,overallRate):
'''
#此函数计算卡方值
:df dataFrame
:total_col 每个值得总数量
:bad_col 每个值的坏数据数量
:overallRate 坏数据的占比
: return 卡方值
'''
df2=df.copy()
df2['expected']=df[total_col].apply(lambda x: x*overallRate)
combined=zip(df2['expected'], df2[bad_col])
chi=[(i[0]-i[1])**2/i[0] for i in combined]
chi2=sum(chi)
return chi2
#基于卡方阈值卡方分箱,有个缺点,不好控制分箱个数。
def ChiMerge_MinChisq(df, col, target, confidenceVal=3.841):
'''
#此函数是以卡方阈值作为终止条件进行分箱
: df dataFrame
: col 被分箱的特征
: target 目标值,是0,1格式
: confidenceVal 阈值,自由度为1, 自信度为0.95时,卡方阈值为3.841
: return 分箱。
这里有个问题,卡方分箱对分箱的数量没有限制,这样子会导致最后分箱的结果是分箱太细。
'''
#对待分箱特征值进行去重
colLevels=set(df[col])
#count是求得数据条数
total=df.groupby([col])[target].count()
total=pd.DataFrame({
'total':total})
#sum是求得特征值的和
#注意这里的target必须是0,1。要不然这样求bad的数据条数,就没有意义,并且bad是1,good是0。
bad=df.groupby([col])[target].sum()
bad=pd.DataFrame({
'bad':bad})
#对数据进行合并,求出col,每个值的出现次数(total,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'])
overallRate=B*1.0/N
#对待分箱的特征值进行排序
colLevels=sorted(list(colLevels))
groupIntervals=[[i] for i in colLevels]
groupNum=len(groupIntervals)
while(1):
if len(groupIntervals) == 1:
break
chisqList=[]
for interval in groupIntervals:
df2=regroup.loc[regroup[col].isin(interval)]
chisq=Chi2(df2, 'total', 'bad', overallRate)
chisqList.append(chisq)
min_position=chisqList.index(min(chisqList))
if min(chisqList) >= confidenceVal:
break
if min_position==0:
combinedPosition=1
elif min_position== groupNum-1:
combinedPosition=min_position-1
else:
if chisqList[min_position-1]<=chisqList[min_position + 1]:
combinedPosition=min_position-1
else:
combinedPosition=min_position+1
groupIntervals[min_position]=groupIntervals[min_position]+groupIntervals[c