特征离散化(一) 之 卡方分箱

特征离散化(一) 之 卡方分箱
特征离散化(二) 之 Chi2分箱
特征离散化(三) 之 最小熵分箱
特征离散化(四) 之 bestKS分箱
特征离散化(五) 之 评分卡最优分箱

离散特征在数据挖掘的过程中具有重要作用,因此特征离散化是构建特征工程的一个很常见、也很重要的环节。

卡方分箱作为最经典的离散化方法之一,最近做项目需要用到时,却发现这么经典的功能python竟然没有官方的封装库。找了许多资料,感觉讲的都比较杂(一会chiMerge,一会chi2,一会单调性检验 O__O”… ),看的怀疑人生。最后实在不得已,只能翻出原论文 ChiMerge: Discretization of Numeric Attributes 出来拜读一下。

看完论文后发现最原始的卡方分箱思想还是挺简单的,只是网上很多资料讲的层次不清晰,让很多初学者看昏了头。因此写篇博客记录复现时的一些想法和踩过的坑。

1. 分箱

首先,介绍一下什么是分箱。分箱是将连续变量离散化,多状态的离散变量合并成少状态的过程。这句话里包含两个要点,第一点:分箱的对象可以是连续变量,也可以是离散变量。第二点:分箱的目的是将变量的可取值变少(更便于分析)。目前主流的分箱方法可以分为两大类:1)自底向上的基于合并(merge)机制的方法,如卡方分箱;2)自上向下的基于分割(split)机制的方法,如基于决策树的分箱、bestKS分箱。后续,博主将一一开设博客介绍这些方法,敬请期待。

2. 卡方分箱 之 ChiMerge

前面说过,卡方分箱是典型的基于合并机制的自底向上离散化方法。其基于如下假设:如果两个相邻的区间具有非常类似的类分布,则这两个区间可以合并;否则,它们应当保持分开。此处衡量分布相似性的指标就是卡方值。卡方值越低,类分布的相似度越高。

因此,ChiMerge分箱的主要思想归结为一句话就是:将具有最小卡方值的相邻区间合并在一起,直到满足确定的停止准则。其通用流程如下:
ChiMerge流程图
这里面包含四个关键点:
1. 离散变量如何排序:大部分关于卡方分箱的介绍都是针对连续变量,对于离散变量,应该如何处理
2. 卡方值计算(坑最深的地方):如何计算相邻两项的卡方值
3. 区间合并:如何合并卡方值最小的两项
4. 停止条件:何时结束循环,停止分箱
下面我们将具体讨论这些问题。

2.1. 排序

卡方分箱的第一步即对数据排序。对于连续变量,直接根据变量数值大小排序即可。对于离散变量,由于取值不存在大小关系,无法直接排序。这里一般采用的排序依据是:正例样本的比例,即待分箱变量每个取值中正例样本的比重,对应代码中的pos_ratio属性。

那么具体如何排序呢?我们前面提到过,卡方分箱是基于合并机制的离散化方法。因此,初始的分箱状态为:将待分箱变量的每个取值视为一个单独的箱体,后续分箱的目的就是将这些箱体合并为若干个箱体。首先,我们统计待分箱变量的可选取值,以及各个取值的正负样本数量(count),然后判断变量类型确定排序依据。

代码如下:其中,var_name_bf 表示需要分箱的变量,函数返回排序后的待分箱变量的统计分布,包括样本取值,正例样本,负例样本。

def dsct_init(data, var_name_bf, var_name_target, feature_type):
    """
    特征离散化节点初始化:统计各取值的正负样本分布 [正例样本个数,负例样本个数] 并排序
    :param data: DataFrame 输入数据
    :param var_name_bf: str 待分箱变量
    :param var_name_target: str 标签变量(y)
    :param feature_type: 特征的类型:0(连续) 1(离散)
    :return: DataFrame 排好序的各组中正负样本分布 count
    """
    # 统计待离散化变量的取值类型(string or digits)
    data_type = data[var_name_bf].apply(lambda x: type(x)).unique()
    var_type = True if str in data_type else False # 实际取值的类型:false(数字) true(字符)
    
    # 是否需要根据正例样本比重排序,True:需要,False:不需要
    #                   0(连续)    1(离散)
    #     false(数字)    0              0(离散有序)
    #     true(字符)     ×             1(离散无序)
    if feature_type == var_type:
        ratio_indicator = var_type
    elif feature_type == 1:
        ratio_indicator = 0
        print("特征%s为离散有序数据,按照取值大小排序!" % (var_name_bf))
    elif feature_type == 0:
        exit(code="特征%s的类型为连续型,与其实际取值(%s)型不一致,请重新定义特征类型!!!" % (var_name_bf, data_type))

    # 统计各分箱(group)内正负样本分布[累计样本个数,正例样本个数,负例样本个数]
    count = pd.crosstab(data[var_name_bf], data[var_name_target])
    total = count.sum(axis=1)
    
    # 排序:离散变量按照pos_ratio排序,连续变量按照index排序
    if ratio_indicator:
        count['pos_ratio'] = count[1].sum(axis=1) * 1.0 / total #计算正例比例
        count = count.sort_values('pos_ratio') #离散变量按照pos_ratio排序
        count = count.drop(columns = ['pos_ratio'])
    else:
        count = count.sort_index() # 连续变量按照index排序
    return count, ratio_indicator

需要注意的是,如果待分箱变量为离散变量,该方法只能使用于二分类模型。因为计算pos_ratio时,要求 y ∈ [ 0 , 1 ] y \in [0,1] y[0,1]。当然,这里可以根据个人需要调整pos_ratio的计算方式,以适应多分类问题。

2.2. 卡方值计算

大多数介绍卡方分箱的文章都没有具体解释相邻区间的卡方值如何计算。在原论文中对于卡方值计算也比较简略,这部分将着重讨论这一内容。首先,给出卡方值的计算公式,如下图所示(左边:数据,右边:对应的卡方值计算公式)。从公式来看,卡方值的计算其实并不复杂。对于四联表中的每一项,分别计算每一项的期望值(分母部分),并计算实际值与期望值之间的差异。不太了解的同学可以参考这篇博客卡方分箱中卡方值的计算
卡方值计算公式
然后,给出原文中关于卡方分箱方法中卡方值计算的介绍,内容如下(左边为原文,右边是从某篇中文文献中截取出来的中文解释)。对于卡方分箱中卡方值的计算,这里有个需要注意的地方:重点观察下标 i i i j j j的取值变化
卡方分箱中卡方值计算公式
假设我们的待分箱矩阵A如下图右边所示,为了方便表示,矩阵中的数值用字母a,b,…表示。 R i R_i Ri C j C_j Cj分别是第 i i i行数据的和以及第 j j j列数据的和,其中, j ∈ [ 0 , k ] j \in[0, k] j[0,k], k k k是类别数(这里y只有两个取值,所以 k = 2 k=2 k=2), i ∈ [ 0 , z ] i \in[0, z] i[0,z] z

  • 21
    点赞
  • 130
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
方分箱是一种离散方法,目的是将连续变量离散为若干个分段,使得每个分段内变量的分布差异较小,不同分段之间的分布差异较大。在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实现方分箱的步骤。需要注意的是,方分箱并不是一种万能的离散方法,应根据具体情况选择合适的方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值