卡方分箱是依賴於卡方檢驗的分箱方法,在統計指標上選擇卡方統計量(chi-Square)進行判別,分箱的基本思想是判斷相鄰的兩個區間是否有分佈差異,基於卡方統計量的結果進行自下而上的合併,直到滿足分箱的限制條件爲止。
卡方分箱的實現步驟:
卡方統計量衡量了區間內樣本的頻數分佈與整體樣本的頻數分佈的差異性,在做分箱處理時可以使用兩種限制條件:
(1)分箱個數:限制最終的分箱個數結果,每次將樣本中具有最小卡方值的區間與相鄰的最小卡方區間進行合併,直到分箱個數達到限制條件爲止。
(2)卡方閾值:根據自由度和顯著性水平得到對應的卡方閾值,如果分箱的各區間最小卡方值小於卡方閾值,則繼續合併,直到最小卡方值超過設定閾值爲止。
在鋪上代碼前再補充兩點,
1、由於卡方分箱是思想是相鄰區間合併,在初始化時對變量屬性需先進行排序,要注意名義變量的排序順序
2、卡方閾值的自由度爲 分箱數-1,顯著性水平可以取10%,5%或1%
python代碼實現:舉例:卡方對二分類問題進行變量分箱
import pandas as pd
import numpy as np
from scipy.stats import chi2
def calc_chiSquare(sampleSet, feature, target):
'''
計算某個特徵每種屬性值的卡方統計量
params:
sampleSet: 樣本集
feature: 目標特徵
target: 目標Y值 (0或1) Y值爲二分類變量
return:
卡方統計量dataframe
feature: 特徵名稱
act_target_cnt: 實際壞樣本數
expected_target_cnt:期望壞樣本數
chi_square:卡方統計量
'''
# 計算樣本期望頻率
target_cnt = sampleSet[target].sum()
sample_cnt = len(sampleSet[target])
expected_ratio = target_cnt * 1.0/sample_cnt
# 對變量按屬性值從大到小排序
df = sampleSet[[feature, target]]
col_value = list(set(df[feature]))
# 計算每一個屬性值對應的卡方統計量等信息
chi_list = []; target_list = []; expected_target_list = []
for value in col_value:
df_target_cnt = df.loc[df[feature] == value, target].sum()
df_cnt = len(df.loc[df[feature] == value, target])
expected_target_cnt = df_cnt * expected_ratio
chi_square = (df_target_cnt - expected_target_cnt)**2 / expected_target_cnt
chi_list.append(chi_square)
target_list.append(df_target_cnt)
expected_target_list.append(expected_target_cnt)
# 結果輸出到dataframe, 對應字段爲特徵屬性值, 卡方統計量, 實際壞樣本量, 期望壞樣本量
chi_stats = pd.DataFrame({feature:col_value, 'chi_square':chi_list,
'act_target_cnt':target_list, 'expected_target_cnt':expected_target_list})
return chi_stats[[feature, 'act_target_cnt', 'expected_target_cnt', 'chi_square']]
var 表示需要分箱的變量,函數返回卡方統計結果,包括樣本實例區間,卡方統計量,響應頻率和期望響應頻率。
def chiMerge_maxInterval(chi_stats, feature, maxInterval=5):
'''
卡方分箱合併--最大區間限制法
params:
chi_stats: 卡方統計量dataframe
feature: 目標特徵
maxInterval:最大分箱數閾值
return:
卡方合併結果dataframe, 特徵分割split_list
'''
group_cnt = len(chi_stats)
split_list = [chi_stats[feature].min()]
# 如果變量區間超過最大分箱限制,則根據合併原則進行合併
while(group_cnt > maxInterval):
min_index = chi_stats[chi_stats['chi_square']==chi_stats['chi_square'].min()].index.tolist()[0]
# 如果分箱區間在最前,則向下合併
if min_index == 0:
chi_stats = merge_chiSquare(chi_stats, min_index+1, min_index)
# 如果分箱區間在最後,則向上合併
elif min_index == group_cnt-1:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
# 如果分箱區間在中間,則判斷與其相鄰的最小卡方的區間,然後進行合併
else:
if chi_stats.loc[min_index-1, 'chi_square'] > chi_stats.loc[min_index+1, 'chi_square']:
chi_stats = merge_chiSquare(chi_stats, min_index, min_index+1)
else:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
group_cnt = len(chi_stats)
chiMerge_result = chi_stats
split_list.extend(chiMerge_result[feature].tolist())
return chiMerge_result, split_list
def chiMerge_minChiSquare(chi_stats, feature, dfree=4, cf=0.1, maxInterval=5):
'''
卡方分箱合併--卡方閾值法
params:
chi_stats: 卡方統計量dataframe
feature: 目標特徵
maxInterval: 最大分箱數閾值, default 5
dfree: 自由度, 最大分箱數-1, default 4
cf: 顯著性水平, default 10%
return:
卡方合併結果dataframe, 特徵分割split_list
'''
threshold = get_chiSquare_distuibution(dfree, cf)
min_chiSquare = chi_stats['chi_square'].min()
group_cnt = len(chi_stats)
split_list = [chi_stats[feature].min()]
# 如果變量區間的最小卡方值小於閾值,則繼續合併直到最小值大於等於閾值
while(min_chiSquare < threshold and group_cnt > maxInterval):
min_index = chi_stats[chi_stats['chi_square']==chi_stats['chi_square'].min()].index.tolist()[0]
# 如果分箱區間在最前,則向下合併
if min_index == 0:
chi_stats = merge_chiSquare(chi_stats, min_index+1, min_index)
# 如果分箱區間在最後,則向上合併
elif min_index == group_cnt-1:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
# 如果分箱區間在中間,則判斷與其相鄰的最小卡方的區間,然後進行合併
else:
if chi_stats.loc[min_index-1, 'chi_square'] > chi_stats.loc[min_index+1, 'chi_square']:
chi_stats = merge_chiSquare(chi_stats, min_index, min_index+1)
else:
chi_stats = merge_chiSquare(chi_stats, min_index-1, min_index)
min_chiSquare = chi_stats['chi_square'].min()
group_cnt = len(chi_stats)
chiMerge_result = chi_stats
split_list.extend(chiMerge_result[feature].tolist())
return chiMerge_result, split_list
分箱主體部分包括兩種分箱方法的主體函數,其中merge_chiSquare()是對區間進行合併,get_chiSquare_distribution()是根據自由度和置信度得到卡方閾值。我在這裏設置的是自由度爲4,置信度爲10%。兩個自定義函數如下
def get_chiSquare_distuibution(dfree=4, cf=0.1):
'''
根據自由度和置信度得到卡方分佈和閾值
params:
dfree: 自由度, 最大分箱數-1, default 4
cf: 顯著性水平, default 10%
return:
卡方閾值
'''
percents = [0.95, 0.90, 0.5, 0.1, 0.05, 0.025, 0.01, 0.005]
df = pd.DataFrame(np.array([chi2.isf(percents, df=i) for i in range(1, 30)]))
df.columns = percents
df.index = df.index+1
# 顯示小數點後面數字
pd.set_option('precision', 3)
return df.loc[dfree, cf]
def merge_chiSquare(chi_result, index, mergeIndex, a = 'expected_target_cnt',
b = 'act_target_cnt', c = 'chi_square'):
'''
params:
chi_result: 待合併卡方數據集
index: 合併後的序列號
mergeIndex: 需合併的區間序號
a, b, c: 指定合併字段
return:
分箱合併後的卡方dataframe
'''
chi_result.loc[mergeIndex, a] = chi_result.loc[mergeIndex, a] + chi_result.loc[index, a]
chi_result.loc[mergeIndex, b] = chi_result.loc[mergeIndex, b] + chi_result.loc[index, b]
chi_result.loc[mergeIndex, c] = (chi_result.loc[mergeIndex, b] - chi_result.loc[mergeIndex, a])**2 /chi_result.loc[mergeIndex, a]
chi_result = chi_result.drop([index])
chi_result = chi_result.reset_index(drop=True)
return chi_result
補充個例子,特徵是identity_age, Y值是target(0, 1)
根據卡方分箱結果得到最後的分割點list是[1, 19, 21, 23, 24, 26, 59]