特徵離散化(二) 之 Chi2分箱
話接上回,基於ChiMerge的卡方分箱可大致分爲四個部分:1. 排序(連續型根據值大小排序,離散型根據給定的標準(如正例樣本佔比)排序);2. 自底向上計算相鄰兩項的卡方值(這一部分的計算尤其需要注意);3. 合併卡方值最小的兩項;4. 重複步驟2&3直至滿足給定的停止條件。如果有不清楚的地方,可以參見上篇博客。
ChiMerge分箱有一個致命的問題是:迭代結束的卡方閾值取決於顯著性水平(sigLevel),該值由用戶指定。對於不同的場景,該如何確定一個合適的sigLevel呢?基於此,Chi2分箱橫空出世。
1. 卡方分箱 之 Chi2
Chi2分箱與ChiMerge分箱都是基於卡方值的自底向上分箱方法。Chi2分箱是對ChiMerge分箱的一種優化,不同於ChiMerge,通過引入不一致性檢驗(原文對應consistency check),Chi2的分箱效果不嚴重依賴於用戶指定的卡方閾值。其僞碼如下:
從僞碼可知,Chi2分箱可分爲兩個階段:第一個階段,相當於在ChiMerge分箱(圖中紅色框框)的外層增加了一層循環。逐步降低顯著性水平(sigLevel),直至數據的不一致性度量小於指定閾值,從而提高卡方閾值。第二個階段,在第一階段確定的sigLevel基礎上,進行更細緻的劃分。
注意*:這裏的討論都只考慮針對單個變量的分箱。
這裏面有三個關鍵的地方論文裏講的不是很清楚:
- 不一致性(Inconsistency Rate)如何計算。modified_chi2.pdf
- 卡方值計算(坑最深的地方)。這個上篇博客已經介紹了,此處不再贅述。
- 顯著性水平(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