特征离散化(二) 之 Chi2分箱

特征离散化(二) 之 Chi2分箱

话接上回,基于ChiMerge的卡方分箱可大致分为四个部分:1. 排序(连续型根据值大小排序,离散型根据给定的标准(如正例样本占比)排序);2. 自底向上计算相邻两项的卡方值(这一部分的计算尤其需要注意);3. 合并卡方值最小的两项;4. 重复步骤2&3直至满足给定的停止条件。如果有不清楚的地方,可以参见上篇博客
ChiMerge分箱有一个致命的问题是:迭代结束的卡方阈值 δ \delta δ取决于显著性水平(sigLevel),该值由用户指定。对于不同的场景,该如何确定一个合适的sigLevel呢?基于此,Chi2分箱横空出世。

1. 卡方分箱 之 Chi2

Chi2分箱与ChiMerge分箱都是基于卡方值的自底向上分箱方法。Chi2分箱是对ChiMerge分箱的一种优化,不同于ChiMerge,通过引入不一致性检验(原文对应consistency check),Chi2的分箱效果不严重依赖于用户指定的卡方阈值。其伪码如下:
chi2分箱伪码
从伪码可知,Chi2分箱可分为两个阶段:第一个阶段,相当于在ChiMerge分箱(图中红色框框)的外层增加了一层循环。逐步降低显著性水平(sigLevel),直至数据的不一致性度量小于指定阈值 δ \delta δ,从而提高卡方阈值。第二个阶段,在第一阶段确定的sigLevel基础上,进行更细致的划分。
注意*:这里的讨论都只考虑针对单个变量的分箱。

这里面有三个关键的地方论文里讲的不是很清楚:

  1. 不一致性(Inconsistency Rate)如何计算。modified_chi2.pdf
  2. 卡方值计算(坑最深的地方)。这个上篇博客已经介绍了,此处不再赘述。
  3. 显著性水平(Significance Level)如何衰减。

下面我们将具体讨论这三个问题。

2. 不一致性衡量

根据论文中的解释,不一致性的度量标准如下:
在这里插入图片描述
相应的代码如下:

def calc_inconsistency_rate(count, group):
    """
    计算分组的不一致性,参考论文《Feature Selection via Discretizations》
    :param count: DataFrame 待分箱变量的分布统计
    :param group: list 分组信息
    :return: float 该分组的不一致性
    """
    inconsistency_rate = 0.0
    for intv in group:
        count_intv = count.loc[count.index.isin(intv)].sum(axis = 0)
        inconsistency_rate += count_intv.sum() - max(count_intv)
    inconsistency_rate = inconsistency_rate / count.sum().sum()
    # print(inconsistency_rate)
    return inconsistency_rate

3. 显著性水平衰减

这一块的参考忘了在哪里看到的了o(╥﹏╥)o。等找到了再补吧。欢迎大神在评论里补充哈。

def Chi2(count, max_interval=6, sig_level=0.5, inconsistency_rate_thresh = 0.05, sig_level_desc = 0.1):
    """
    基于Chi2的卡方离散化方法
    :param count: DataFrame 待分箱变量的分布统计
    :param max_interval: int 最大分箱数量
    :param sig_level: 显著性水平(significance level) = 1 - 置信度
    :param inconsistency_rate_thresh: 不一致性阈值
    :return: 分组信息(group)
    """
    print("Chi2分箱开始:")
    deg_freedom = len(count.columns) - 1 # 自由度(degree of freedom)= y类别数-1

    group = np.array(count.index).reshape(-1, 1).tolist()  # 分组信息
    # 2. 阶段1:
    print("Chi2分箱第一阶段开始:")
    while calc_inconsistency_rate(count, group) < inconsistency_rate_thresh: # 不一致性检验
        # 2. 计算相邻分组的卡方值
        chi2_threshold = chi2.ppf(1-sig_level, deg_freedom) # 卡方阈值
        chi2_list = [calc_chi2(count, group[idx], group[idx + 1]) for idx in range(len(group) - 1)]
    
        # 3. 合并相似分组并更新卡方值
        while 1:
            if min(chi2_list) >= chi2_threshold:
                print("最小卡方值%.3f大于卡方阈值%.3f,分箱合并结束!!!" % (min(chi2_list), chi2_threshold))
                break
            if len(group) <= max_interval:
                print("分组长度%s等于指定分组数%s" % (len(group), max_interval))
                break
            chi2_list, group = merge_adjacent_intervals(count, chi2_list, group)
    
        # 阈值更新
        sig_level = sig_level - sig_level_desc # 降低显著性水平,提高卡方阈值
    print("Chi2分箱第一阶段完成!!!")
    
    # 3. 阶段2:
    print("Chi2分箱第二阶段开始:")
    sig_level = sig_level + sig_level_desc  # 回到上一次的值
    while True:
        # 2. 计算相邻分组的卡方值
        chi2_threshold = chi2.ppf(1 - sig_level, deg_freedom)  # 卡方阈值
        chi2_list = [calc_chi2(count, group[idx], group[idx + 1]) for idx in range(len(group) - 1)]
        
        # 3. 合并相似分组并更新卡方值
        while 1:
            if min(chi2_list) >= chi2_threshold:
                print("最小卡方值%.3f大于卡方阈值%.3f,分箱合并结束!!!" % (min(chi2_list), chi2_threshold))
                break
            if len(group) <= max_interval:
                print("分组长度%s等于指定分组数%s" % (len(group), max_interval))
                break
            chi2_list, group = merge_adjacent_intervals(count, chi2_list, group)

        # 阈值更新
        in_consis_rate = calc_inconsistency_rate(count, group)
        if in_consis_rate < inconsistency_rate_thresh:  # 不一致性检验
            sig_level = sig_level - sig_level_desc  # 降低显著性水平,提高卡方阈值
        else:
            print("分组的不一致性(%.3f)大于阈值(%.3f),无法继续合并分箱!!!" % (in_consis_rate, inconsistency_rate_thresh))
            break
    print("Chi2分箱第二阶段完成!!!")
    return group

4. 完整代码参见:

完整代码参见:
https://github.com/Lucky-Bone/Discretization

猜你喜欢

转载自blog.csdn.net/SkullSky/article/details/100898862