数据处理实战: Chimerge和决策树分箱

本文是对《数据挖掘概念与技术》第三章的补充,详细展开分箱技术的细节

1、Chimerge 分箱

Chimerge分箱虽然在书中只是寥寥几行,但却瞬间吸引了我的兴趣, 因为它的方式比较特别, 属于自下而上的分箱方式 首先将变量值排序, 初始化时每个值作为一组, 对相邻组做卡方检验,具有最小卡方值的组合并在一起(卡方值小,说明两组值的差别与目标变量不独立,可以参考小说和男女的关系),循环合并,直到满足预先设定的终止条件(满足分组数或卡方值大于某个阈值)。

Chimerge原理虽然很简单, 但实现起来还是有点麻烦的, 这里笔者整理了一份Chimerge分箱的代码

def cal_Chi2(df):

    """从列联表计算出卡方值""" 

    res = [] 

    # 计算values的和 

    num_sum = sum(df.values.flatten()) 

    for i in range(df.shape[0]):

        for j in range(df.shape[1]):

            # 计算位置i,j上的期望值

            e = sum(df.iloc[i,:])*sum(df.iloc[:,j])/num_sum

            tt = (df.iloc[i,j]-e)**2/e

            res.append(tt)

    return sum(res)



def line_merge(df,i,j):

    """将i,j行合并"""

    df.iloc[i, 1] = df.iloc[i, 1] + df.iloc[j, 1]

    df.iloc[i, 2] = df.iloc[i, 2] + df.iloc[j, 2]

    df.iloc[i, 0] = df.iloc[j, 0]

    df = pd.concat([df.iloc[:j,:],df.iloc[j+1:,:]])

    return df 


# 定义一个卡方分箱(可设置参数置信度水平与箱的个数)停止条件为大于置信水平且小于bin的数目

def ChiMerge(df, variable, flag, confidenceVal=3.841, bin=10): 

    '''   

    df:传入一个数据框仅包含一个需要卡方分箱的变量与正负样本标识(正样本为1,负样本为0)

    variable:需要卡方分箱的变量名称(字符串)

    flag:正负样本标识的名称(字符串)

    confidenceVal:置信度水平(默认是不进行抽样95%)

    bin:最多箱的数目

    ''' 

    #进行数据格式化录入   

    regroup = df.groupby([variable])[flag].agg(["size","sum"])

    regroup.columns = ['total_num','positive_class']

    regroup['negative_class'] = regroup['total_num'] - regroup['positive_class']  # 统计需分箱变量每个值负样本数

    regroup = regroup.drop('total_num', axis=1).reset_index()

    col_names = regroup.columns 

    print('已完成数据读入,正在计算数据初处理')

  #处理连续没有正样本或负样本的区间,并进行区间的合并(以免卡方值计算报错)

    i = 0

    while (i <= regroup.shape[0] - 2):

        # 如果正样本(1)列或负样本(2)列的数量求和等于0 (求和等于0,说明i和i+1行的值都等于0) 

        if sum(regroup.iloc[[i,i+1],[1,2]].sum()==0) >0 :   

            # 合并两个区间

            regroup = line_merge(regroup,i,i+1)           

            i = i - 1

        i = i + 1


    # 对相邻两个区间进行卡方值计算

    chi_ls = []  # 创建一个数组保存相邻两个区间的卡方值

    for i in np.arange(regroup.shape[0] - 1):

        chi = cal_Chi2(regroup.iloc[[i,i+1],[1,2]])       

        chi_ls.append(chi) 


    print('已完成数据初处理,正在进行卡方分箱核心操作')

    #把卡方值最小的两个区间进行合并(卡方分箱核心)

    while True:

        if (len(chi_ls) <= (bin - 1) and min(chi_ls) >= confidenceVal):

            break 

        min_ind = chi_ls.index(min(chi_ls))  # 找出卡方值最小的位置索引

#      合并两个区间

        regroup = line_merge(regroup,min_ind,min_ind+1) 


        if (min_ind == regroup.shape[0] - 1):  # 最小值是最后两个区间的时候

            # 计算合并后当前区间与前一个区间的卡方值并替换           

            chi_ls[min_ind - 1] = cal_Chi2(regroup.iloc[[min_ind,min_ind-1],[1,2]]) 

            # 删除替换前的卡方值

            del chi_ls[min_ind]           

        else:

            # 计算合并后当前区间与前一个区间的卡方值并替换

            chi_ls[min_ind - 1] = cal_Chi2(regroup.iloc[[min_ind,min_ind-1],[1,2]])


            # 计算合并后当前区间与后一个区间的卡方值并替换

            chi_ls[min_ind] = cal_Chi2(regroup.iloc[[min_ind,min_ind+1],[1,2]])


            # 删除替换前的卡方值

            del chi_ls[min_ind+ 1] 


    print('已完成卡方分箱核心操作,正在保存结果')

    # 把结果保存成一个数据框


    list_temp = []

    for i in np.arange(regroup.shape[0]):

        if i == 0:

            x = '-inf'+'~'+ str(regroup.iloc[i,0])

        elif i == regroup.shape[0] - 1:

            x = str(regroup.iloc[i-1,0])+'+' 

        else:

            x =str(regroup.iloc[i-1,0])+ '~'+str(regroup.iloc[i,0])

        list_temp.append(x)

    regroup[variable] = list_temp  # 结果表第二列:区间

    return regroup

import pandas as pd

import numpy as np

pd.set_option('display.max_columns',None)

import matplotlib.pyplot as plt 

%matplotlib inline

df = pd.read_csv('BankChurners.csv')

df['target'] = df['Attrition_Flag'].map({"Existing Customer":0,"Attrited Customer":1})

df.drop('Attrition_Flag',axis=1,inplace=True) 

df.rename(columns={"Customer_Age":"age"},inplace=True)

#调用函数参数示例

bins = ChiMerge(df, 'Credit_Limit','target', confidenceVal=3.841, bin=6)

bins

以上代码是对数值型变量的操作。 对于序数型变量, 需要先将根据变量顺序将变量值转换为0,1,2,3...的数值, 再根据以上代码操作。至于标称型属性, 没有明确的排序依据, 但是Chimerge方法有需要先排序, 所以有一个办法可以作为参考, 即每个离散值作为一组,根据每组中正样本的比例排序, 有了排序后, 将属性映射为0,1,2,3..., 后续的处理方式可以和数值型变量一致。

关于变量类型的介绍,可以参考另一篇文章 数据挖掘概念与技术-第2章 - ,关于卡方检验的详细探讨可以参考另一篇文章 卡方检验、相关系数、协方差和数据标准化 -

2、决策树分箱

不同于Chimerge的自下而上, 决策树是自顶向下划分的, 但两者都是监督式分箱方法, 即都需要使用到标签变量。由于分箱时使用了类信息, 因此区间的边界更有可能定义在有帮助于提高分类准确率的地方。

from sklearn.tree import DecisionTreeClassifier 

def decision_tree_bins(df:pd.DataFrame,x_name:str,y_name:str,max_leaf_num:int=6):

    """利用决策树获得最优分箱的边界值""" 

    boundary = [] 

    x = df[x_name].values 

    y = df[y_name].values

    clf = DecisionTreeClassifier(criterion='entropy',  # 信息熵最小化准则划分

                                max_leaf_nodes=max_leaf_num,  # 最大叶子节点数 

                                min_samples_leaf = 0.05)  # 叶子节点样本数量最小占比

    clf.fit(x.reshape(-1,1),y)  # 训练决策树


    n_nodes = clf.tree_.node_count 

    children_left = clf.tree_.children_left 

    children_right = clf.tree_.children_right 

    threshold = clf.tree_.threshold 


    for i in range(n_nodes):

        if children_left[i] != children_right[i] : # 获的决策时节点上的划分边界

            boundary.append(threshold[i])

    boundary.sort()

    min_x = x.min() 

    max_x = x.max() + 0.1 # 加0.1是为了考虑后续groupby操作时, 能包含特征最大值得样本

    boundary = boundary +[max_x] 


    # 根据得到的边界值, 得到分箱结果

    df[x_name] = pd.cut(df[x_name],bins=boundary)   


    # 查看分箱结果

    df = df.groupby(x_name,as_index=False)[y_name].agg(['size','sum'])

    df = df.reset_index()

    df.columns = [x_name,'num_all','positive_class']

    df['negative_class'] = df['num_all'] - df['positive_class']


    return df[[x_name,'positive_class','negative_class']]   

import pandas as pd

import numpy as np

pd.set_option('display.max_columns',None)

import matplotlib.pyplot as plt 

%matplotlib inline

df = pd.read_csv('BankChurners.csv')

df['target'] = df['Attrition_Flag'].map({"Existing Customer":0,"Attrited Customer":1})

df.drop('Attrition_Flag',axis=1,inplace=True) 

decision_tree_bins(df.copy(),'Months_on_book','target',max_leaf_num=6)

3、等宽分箱

将属性的值域从最小值到最大值分成具有相同宽度的n个区间,等宽分箱比较简单, 一个函数np.linspace就可以很方便的得到分箱的边界, 再使用pd.cut() 就可以对数值进行方便的分箱转换了

import pandas as pd

import numpy as np

pd.set_option('display.max_columns',None)

import matplotlib.pyplot as plt

%matplotlib inline

df = pd.read_csv('BankChurners.csv')

df['target'] = df['Attrition_Flag'].map({"Existing Customer":0,"Attrited Customer":1})

df.drop('Attrition_Flag',axis=1,inplace=True)

np.linspace(df['Credit_Limit'].min(),df['Credit_Limit'].max(),10)

pd.cut(df['Credit_Limit'],bins=np.linspace(df['Credit_Limit'].min(),df['Credit_Limit'].max(),10))

4、等频分箱

等频法是将相同数量的记录放在每个区间,保证每个区间的数量基本一致, 等频分箱比较简单, 一个pd.qcut就解决问题了

import pandas as pd

import numpy as np

pd.set_option('display.max_columns',None)

import matplotlib.pyplot as plt

%matplotlib inline

df = pd.read_csv('BankChurners.csv')

df['target'] = df['Attrition_Flag'].map({"Existing Customer":0,"Attrited Customer":1})

df.drop('Attrition_Flag',axis=1,inplace=True)

df['qq'] = pd.qcut(df['Credit_Limit'],q=np.linspace(0,1,11))

df.groupby('qq')['qq'].size().plot(kind='bar',figsize=(18,10))

本文涉及到python代码、数据集以及思维导图可以在我们的公众号'数据臭皮匠'后台回复'第三章2'获取。有任何疑问可以在评论区提出,我们会及时答复。

关注公众号:数据臭皮匠;获得更多精彩内容

作者:范小匠

审核:灰灰匠

编辑:森匠

你可能感兴趣的:(数据处理实战: Chimerge和决策树分箱)