特徵離散化(二) 之 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章