分箱_卡方分箱

时间:2025-05-08 07:35:37

我们都知道:卡方、best-ks、最优分箱等都是比较常用的有监督分箱,那么他们都是如何实现的呢?今天我们就先来学习一下卡方分箱吧,之后会再出其他分箱代码,期待一下吧~图片

1.分箱逻辑:
  将变量排序后,计算每一对相邻区间的卡方值,然后将卡方值最小的两个相邻区间进行合并,直至达到箱子上限数、或者卡方阈值

2.核心思想:
  如果两个区间合并,那么需要两个区间的样本分布相似,卡方值小,说明两个区间分布相似。(卡方值:用于衡量实际值与理论值的差异程度)

3.实现代码:

############################ 1. 提取样本数据
import numpy as np
import pandas as pd
from pandas import DataFrame as df
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
X = df(iris.data, columns=iris.feature_names).rename(columns=lambda x:x.replace(' (cm)','').replace(' ','_'))
y = pd.Series(iris.target).replace(2,1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1) 

train = pd.concat([X_train,y_train],axis=1).rename(columns={0:'y'})

  • 调用toad包
################################### 调用toad包
import toad

###### 单个字段分箱 ######
import toad
toad.ChiMerge(X_train['sepal_length'],y_train,5)  #array([4.9, 5.1, 5.5, 5.8])
toad.ChiMerge(X_train['sepal_width'],y_train,5)   #array([2.4, 2.9, 3. , 3.4])
toad.ChiMerge(X_train['petal_length'],y_train,5)  #array([3.3])
toad.ChiMerge(X_train['petal_width'],y_train,5)   #array([1.])

###### 所有字段分箱 ######
# 初始化一个combiner类
combiner = toad.transform.Combiner()
# 训练数据并指定分箱方法,其它参数可选
combiner.fit(train,y='y',method='chi',min_samples =  0.05)
# 以字典形式保存分箱结果
bins = combiner.export()
  • 手写分箱
############################ 计算卡方值
def cal_chi(regroup_per):
    '''
    args:
        regroup_per:计算卡方值的两行数的坏好值,array
    return:
        chi_value:卡方值
    '''
       
    R_N = regroup_per.sum(axis=1) # 按行求和
    C_N = regroup_per.sum(axis=0) # 按列求和
    N = regroup_per.sum()         # 总数
    
    E = np.ones(regroup_per.shape)*C_N/N 
    E = (E.T*R_N).T               # 期望频数
    
    square = (regroup_per-E)**2/E
    square[E==0] = 0   # 当期望频数为0时,作为分母无意义,不计入卡方值
    
    chi_value = square.sum() # 卡方值
    
    return chi_value

① 箱子个数


############################ 卡方分箱(箱子个数)  
def ChiMerge_MaxInterval(data,cols_num,label,max_interval=5):
    '''
    args:        
        data:数据源,df
        cols_num:数值分箱字段,列表
        label:标签,str
        max_interval:最大分箱数,int
    return:
        cols_num_points:各变量的切点,字典
    '''
    
    cols_num_points = {}
    for col in cols_num:
        print(col)
        
        if len(data[col].unique()) <= max_interval: # 如果数据的有限值个数 < 最大分箱数,则不分箱,反之分箱
            cols_num_points[col] = data[col].unique()
        else:
            regroup = data.groupby([col])[label].agg(['count','sum']).reset_index().rename(columns={'count':'CntRec','sum':'CntBad'})
            regroup['CntGood'] = regroup['CntRec'] - regroup['CntBad']
            
            while len(regroup) > max_interval:
                
                ###### 1.计算相邻两个箱子之间的卡方值,并找到卡方值最小的索引位置
                minchi_value = np.nan
                for i in range(len(regroup)-1):
                    print(i)            
                    regroup_per = regroup.iloc[i:i+2,2:].values  # 提取相邻两个箱子的统计数据
                    chi_value = cal_chi(regroup_per)             # 计算卡方值
                    if (minchi_value is np.nan) or (minchi_value > chi_value):  # 如果卡方值小于最小卡方值,则更新
                        minchi_value = chi_value
                        minchi_index = i
                        
                ##### 2.将卡方值最小的两个箱子进行合并
                regroup.iloc[minchi_index,1:] = regroup.iloc[minchi_index,1:] + regroup.iloc[minchi_index+1,1:] # 与后面的箱子进行合并
                regroup = regroup.drop(index = minchi_index+1) # 删除后面箱子的索引
                regroup.index = range(len(regroup))
                               
            cols_num_points[col] = regroup[col].unique().tolist()
    
    return cols_num_points

② 阈值

############################ 卡方分箱(阈值)  
## 根据置信度、*度计算卡方值
# from  import chi2    
# threshold = (0.05,4) # 置信度:95%,*度:4

def ChiMerge_MaxThreshold(data,cols_num,label,max_threshold=9.49,max_interval=5):
    '''
    args:        
        data:数据源,df
        cols_num:数值分箱字段,列表
        label:标签,str
        max_threshold:最大阈值,float
        max_interval:最大分箱数,int
    return:
        cols_num_points:各数值变量的切点,字典
    '''
    
    cols_num_points = {}
    for col in cols_num:
        print(col)
        
        regroup = data.groupby([col])[label].agg(['count','sum']).reset_index().rename(columns={'count':'CntRec','sum':'CntBad'})
        regroup['CntGood'] = regroup['CntRec'] - regroup['CntBad']
        
        minchi_value = np.nan
        while True:
            
            ###### 1.计算相邻两个箱子之间的卡方值,并找到卡方值最小的索引位置
            for i in range(len(regroup)-1):
                print(i)
                regroup_per = regroup.iloc[i:i+2,2:].values  # 提取相邻两个箱子的统计数据
                chi_value = cal_chi(regroup_per)             # 计算卡方值
                if (minchi_value is np.nan) or (minchi_value > chi_value):  # 如果卡方值小于最小卡方值,则更新
                    minchi_value = chi_value
                    minchi_index = i
                    
            ##### 2.将卡方值最小的两个箱子进行合并
            if (minchi_value < max_threshold) & (len(regroup)>max_interval):
                regroup.iloc[minchi_index,1:] = regroup.iloc[minchi_index,1:] + regroup.iloc[minchi_index+1,1:] # 与后面的箱子进行合并
                regroup = regroup.drop(index = minchi_index+1) # 删除后面箱子的索引
                regroup.index = range(len(regroup))
            else:
                break
            
        cols_num_points[col] = regroup[col].unique().tolist()
    
    return cols_num_points