【有监督分箱】方法一:卡方分箱

1.前言

评分卡建模在金融行业应用得比较广泛,比如对客户的信贷诚信度进行评分。在建模过程中,对连续变量的分箱是一个必不可少的过程。正好我最近的项目也是要做一个积分卡,因此想对分箱做一个较全面的总结。

2.定义

何谓分箱,简单地说,分箱就是将连续变量离散化,将多状态的离散变量合并成少状态。

3.分箱的用处

  1. 离散特征的增加和减少都很容易,易于模型的快速迭代;
  2. 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
  3. 列表内容离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
  4. 列表内容逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;
  5. 离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
  6. 列表内容特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
  7. 特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。 可以将缺失作为独立的一类带入模型。
  8. 将所有变量变换到相似的尺度上。

4.分箱方法

分箱方法分为无监督分箱和有监督分箱。常用的无监督分箱方法有等频分箱,等距分箱和聚类分箱。有监督分箱主要有best-ks分箱和卡方分箱。基于我的项目中重点应用了卡方分箱,所以这里重点对卡方分箱做些总结。

5.卡方分箱的原理

卡方分箱是自底向上的(即基于合并的)数据离散化方法。它依赖于卡方检验:具有最小卡方值的相邻区间合并在一起,直到满足确定的停止准则。
基本思想:对于精确的离散化,相对类频率在一个区间内应当完全一致。因此,如果两个相邻的区间具有非常类似的类分布,则这两个区间可以合并;否则,它们应当保持分开。而低卡方值表明它们具有相似的类分布。
分箱步骤:
这里写图片描述
这里需要注意初始化时需要对实例进行排序,在排序的基础上进行合并。

卡方阈值的确定:
  根据显著性水平和自由度得到卡方值
  自由度比类别数量小1。例如:有3类,自由度为2,则90%置信度(10%显著性水平)下,卡方的值为4.6。

阈值的意义
  类别和属性独立时,有90%的可能性,计算得到的卡方值会小于4.6。
  大于阈值4.6的卡方值就说明属性和类不是相互独立的,不能合并。如果阈值选的大,区间合并就会进行很多次,离散后的区间数量少、区间大。

6.分完箱之后评估指标

分为箱之后,需要评估。在积分卡模型中,最常用的评估手段是计算出WOE和IV值。对于WOE和IV值的含义,我推荐博客:
https://blog.csdn.net/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
  • 23
    点赞
  • 258
    收藏
    觉得还不错? 一键收藏
  • 33
    评论
卡方分箱是一种离散化方法,目的是将连续变量离散化为若干个分段,使得每个分段内变量的分布差异较小,不同分段之间的分布差异较大。在python中可以使用以下步骤实现: 1. 导入需要的库 ```python import pandas as pd import numpy as np import scipy.stats as ss ``` 2. 读入数据 ```python data = pd.read_csv('data.csv') ``` 3. 确定分箱个数 可以通过经验法则、等频分箱或等距分箱方法确定分箱个数。 ```python # 等频分箱 data['rank'] = pd.qcut(data['feature'], 10, labels=False) ``` 4. 计算卡方卡方值可以衡量变量在不同分箱之间的差异程度。 ```python def chi2(df, total_col, bad_col, overall_rate): df2 = df.copy() df2['expected'] = df[total_col].apply(lambda x: x * overall_rate) combined = zip(df2['expected'], df2[bad_col]) chi = [(i[0]-i[1])**2/i[0] for i in combined] return sum(chi) total = data.groupby('rank').count()['feature'] bad = data.groupby('rank').sum()['target'] overall_rate = np.sum(bad) / np.sum(total) chi2_value = chi2(pd.concat([total, bad], axis=1), 'feature', 'target', overall_rate) ``` 5. 合并分箱 根据卡方值,将相邻的分箱合并,直到满足预设的最小卡方值。 ```python def merge(data, col, target, max_interval, min_chi2): total = data.groupby(col).count()[target] bad = data.groupby(col).sum()[target] overall_rate = np.sum(bad) / np.sum(total) group_intervals = [[i] for i in total.index] while len(group_intervals) > max_interval: chi2_values = [] for i in range(len(group_intervals)-1): new_interval = group_intervals[i] + group_intervals[i+1] df1 = pd.concat([total.loc[new_interval], bad.loc[new_interval]], axis=1) df2 = pd.concat([total.loc[group_intervals[i]], bad.loc[group_intervals[i]]], axis=1) df3 = pd.concat([total.loc[group_intervals[i+1]], bad.loc[group_intervals[i+1]]], axis=1) chi2_values.append(chi2(df1, 'feature', 'target', overall_rate) - chi2(df2, 'feature', 'target', overall_rate) - chi2(df3, 'feature', 'target', overall_rate)) if min(chi2_values) < min_chi2: merge_index = chi2_values.index(min(chi2_values)) group_intervals[merge_index] = group_intervals[merge_index] + group_intervals.pop(merge_index+1) else: break return group_intervals intervals = merge(data, 'rank', 'target', 10, 3.841) ``` 6. 将分箱结果应用到数据中 ```python def apply_intervals(x, intervals): for i in range(len(intervals)): if x in intervals[i]: return i data['rank_new'] = data['rank'].apply(lambda x: apply_intervals(x, intervals)) ``` 以上就是使用python实现卡方分箱的步骤。需要注意的是,卡方分箱并不是一种万能的离散化方法,应根据具体情况选择合适的方法
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值