【分箱操作】决策树、卡方、分位数、等距和映射分箱操作代码实现

Table of Contents

import numpy as np
import pandas as pd
from sklearn.datasets import load_breast_cancer
import numpy as np 
import pandas as pd
import matplotlib.pyplot as plt 
import seaborn as sns
%matplotlib inline
plt.rcParams["font.sans-serif"] = ["FangSong"] 
plt.rcParams["axes.unicode_minus"] = False 
import warnings
warnings.filterwarnings("ignore")
from sklearn.tree import DecisionTreeClassifier

数据准备

使用x作为待分箱数据
y为分箱的目标标签
data = load_breast_cancer()
df = pd.DataFrame(data.data, columns=data.feature_names)
variable = "mean radius"
x = df[variable].values
y = data.target
test = pd.DataFrame({'x':x,'y':y})
test.head()
xy
017.990
120.570
219.690
311.420
420.290
sns.kdeplot(test.x)
<matplotlib.axes._subplots.AxesSubplot at 0x298b39e7390>

在这里插入图片描述

sns.boxplot(test.x)
<matplotlib.axes._subplots.AxesSubplot at 0x298b3c79b00>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtQ0F2dI-1601345323110)(output_10_1.png)]

woe和iv的计算过程和理解可见:
https://blog.youkuaiyun.com/xiezhen_zheng/article/details/82888653

定义计算函数

def woe_iv(data, x_col='x', y_col='y'):
    '''
    data  为含有y和分箱标签的dataframe
    x_col 为分箱标签
    y_col 为分类标签
    '''
    rate_table = test.groupby(x_col)[y_col].agg(['count', 'sum']).rename(
        columns={'count': 'total', 'sum': 'bad'})  # 对X进行加总,计算等到每一类型下的总数量和坏样本数量
    rate_table['good'] = rate_table['total'] - \
        rate_table['bad']  # 计算每一类型下好样本的数量
    total_bad = rate_table.sum()['bad']  # 计算坏样本总量
    total_good = rate_table.sum()['good']  # 计算好样本总量
    rate_table['p_bad'] = rate_table['bad']/total_bad  # 就是每一类型下坏样本的概率
    rate_table['p_good'] = rate_table['good']/total_good  # 就是每一类型下好样本的概率
    woe = np.log(rate_table['p_bad']/rate_table['p_good'])  # 就是每一类型的WOE值
    iv = (rate_table['p_bad'] - rate_table['p_good'])*woe  # 就是每一类的IV值
    rate_table['iv'] = iv
    rate_table['woe'] = woe
    return rate_table

决策树分箱

def optimal_binning_boundary(data, x_col = 'x',y_col = 'y',criterion='gini') -> list:
    '''
    利用决策树进行分箱
    data  为含有y和分箱标签的dataframe
    x_col 为分箱标签
    y_col 为分类标签
    criterion 为决策树分类准则
    '''
    x0 = data[x_col]
    x = data[x_col].values
    y = data[y_col].values
    boundary = []  # 待return的分箱边界值列表
    clf = DecisionTreeClassifier(criterion=criterion,  # 决策树分类准则
                                 max_leaf_nodes=6,       # 最大叶子节点数
                                 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()  
    boundary = [min_x] + boundary + [max_x]
    data['bins_dtc'] = pd.cut(x0, bins=boundary, include_lowest=True, labels=False)
    return boundary, data
boundary, bins = optimal_binning_boundary(test)
woe_iv(bins,'bins_dtc')
totalbadgoodp_badp_goodivwoe
bins_dtc
015014730.4117650.0141511.3402253.370671
1115105100.2941180.0471700.4519701.830226
24939100.1092440.0471700.0521310.839827
38355280.1540620.1320750.0033850.153979
45410440.0280110.2075470.359566-2.002754
511811170.0028010.5518872.900997-5.283323

分位数分箱

使用pd.qcut函数
bins = pd.qcut(df[variable] ,5,labels=False,)
test['bins_q'] = bins
woe_iv(test,'bins_q')
totalbadgoodp_badp_goodivwoe
bins_q
011411220.3137250.0094341.0662993.504202
111410680.2969190.0377360.5346552.062848
211391220.2549020.1037740.1358140.898668
311447670.1316530.3160380.161465-0.875695
411411130.0028010.5330192.782868-5.248537

等距和等距log映射

np.floor_divide(a,b) a除b再取整
bins = np.floor() 向下取整
bins = np.floor_divide(df[variable] ,6)
test['bins_divide'] = bins 
woe_iv(test,'bins_divide')
totalbadgoodp_badp_goodivwoe
bins_divide
1.016916360.4565830.0283021.1909812.780841
2.03081941140.5434170.5377360.0000600.010510
3.0850850.0000000.400943inf-inf
4.07070.0000000.033019inf-inf
bins = np.floor(np.log2(df[variable]))
test['bins_log'] = bins 
woe_iv(test,'bins_log')
totalbadgoodp_badp_goodivwoe
bins_log
2.04400.0112040.000000infinf
3.0424347770.9719890.3632080.5992660.984370
4.014161350.0168070.6367922.253440-3.634665

卡方分箱

卡方分箱的具体思路为:
https://mp.weixin.qq.com/s?__biz=MzA5Njc1MDA2Ng%3D%3D&idx=1&mid=2651650083&sn=a24381efa404500ae96ccfcc3716a614

def Chi2(df, total_col, bad_col,overallRate):
    '''
     #此函数计算卡方值
     :df dataFrame
     :total_col 每个值得总数量
     :bad_col 每个值的坏数据数量
     :overallRate 坏数据的占比
     : return 卡方值
    '''
    df2=df.copy()
    df2['expected']=df[total_col].apply(lambda x: x*overallRate)
    combined=zip(df2['expected'], df2[bad_col])
    chi=[(i[0]-i[1])**2/i[0] for i in combined]
    chi2=sum(chi)
    return chi2
def chiMerge(data,x_col = 'x',y_col = 'y',max_bins = 5):
    gro = data.groupby(x_col)[y_col].agg(['mean', 'count'])
    gro['bad'] = gro['count']*gro['mean']
    total_rate_bad = gro.sum()['bad']/gro.sum()['count']
    gro['exp_bad'] = gro['count']*total_rate_bad
    gro['chi2'] = ((gro['exp_bad'] - gro['bad']) ** 2)/gro['exp_bad']
    gro.drop('mean', 1, inplace=True)
    bad = list(gro['bad'])
    exp_bad = list(gro['exp_bad'])
    chi_c = np.array((bad,exp_bad)).T
    interval = [[i] for i in list(gro.index)]
    chi2 = list(gro['chi2'].values)
    while len(interval) >max_bins:
        between_sum = [chi2[i] + chi2[i+1] for i in range(len(chi2)-1)]
        chi2_min_index = between_sum.index(min(between_sum))
        interval[chi2_min_index] = interval[chi2_min_index] + interval[chi2_min_index+1]
        interval.pop(chi2_min_index+1)
        chi_c[chi2_min_index] = chi_c[chi2_min_index] + chi_c[chi2_min_index + 1]
        chi_c = np.delete(chi_c,chi2_min_index + 1,0)
        chi2[chi2_min_index] = ((chi_c[chi2_min_index][0] - chi_c[chi2_min_index][1])**2)/chi_c[chi2_min_index][1]
        chi2.pop(chi2_min_index+1)
    interval_ = [min(i) for i in interval] + [data[x_col].max()]
    bins = pd.cut(data[x_col],interval_,labels=False,include_lowest=True)
    return bins,interval_
inter = chiMerge(test)
bins = inter[0]
test['bins_kafang'] = bins
woe_iv(test,'bins_kafang')
totalbadgoodp_badp_goodivwoe
bins_kafang
0425351740.9831930.3490570.6566941.035572
1556490.0168070.2311320.561792-2.621210
2290290.0000000.136792inf-inf
3310310.0000000.146226inf-inf
4290290.0000000.136792inf-inf

<think>好的,我现在需要帮用户解决关于如何在数据分析或机器学习中将BMI水平定义为有序分类变量的问题。首先,我需要理解用户的需求。用户可能在进行数据分析或模型构建时,遇到了需要将BMI这样的连续变量转换为有序分类变量的情况,比如为了简化模型或满足特定算法的需求。 首先,我应该回顾什么是有序分类变量。有序分类变量(Ordinal Categorical Variable)是指类别之间有明确的顺序或层次关系,但类别之间的差距不一定相等。例如,BMI水平可以分为“体重不足”、“正常”、“超重”“肥胖”,这些类别有明显的顺序,但每个类别之间的BMI数值差距可能不同。 接下来,我需要考虑如何将连续的BMI数值分箱(binning)成有序类别。分箱法通常有等宽分箱等频分箱。等宽分箱是按照数值范围均匀划分,比如BMI<18.5是体重不足,18.5-24.9是正常,25-29.9是超重,≥30是肥胖。这种法简单,但可能各类别样本数量不均衡。等频分箱则是根据数据的分布情况,让每个类别有相同数量的样本,这在数据分布不均匀时可能更合适,但需要计算分位数。 然后,需要考虑在分箱后如何处理这些有序类别。在机器学习模型中,有些算法如决策树、随机森林可以直接处理类别变量,但需要将类别编码成有序的数值,比如使用序数编码(Ordinal Encoding),即给每个类别一个有序的数值,如体重不足=0,正常=1,超重=2,肥胖=3。不过需要注意,这种编码可能会被某些算法误解为连续变量,因此可能需要使用独热编码(One-Hot Encoding),但这会丢失顺序信息。因此,要根据具体模型选择合适的编码式。 在数据分析中,将BMI作为有序分类变量可以简化分析,比如进行检验或有序逻辑回归(Ordinal Logistic Regression),这些法可以考虑类别之间的顺序关系,比普通的多分类模型更有效。例如,在研究BMI与某种疾病风险的关系时,有序分类能更好地捕捉到风险随BMI增加而上升的趋势。 还需要注意分箱的合理性。分界点通常基于医学标准,比如WHO的BMI分类标准,这样结果更具解释性实际意义。同时,分箱可能导致信息丢失,比如同一类别内的差异被忽略,因此需要权衡简化模型保留信息之间的平衡。 另外,用户可能想知道具体的实现步骤。例如,在Python中使用pandas的cut函数进行分箱,或者使用scikit-learn的OrdinalEncoder进行编码。可能需要给出代码示例,比如: ```python import pandas as pd # 示例数据 data = pd.DataFrame({'BMI': [17, 22, 26, 31, 19, 28]}) # 定义分箱边界标签 bins = [0, 18.5, 25, 30, float('inf')] labels = ['体重不足', '正常', '超重', '肥胖'] # 分箱操作 data['BMI_category'] = pd.cut(data['BMI'], bins=bins, labels=labels, right=False) # 序数编码 data['BMI_ordinal'] = data['BMI_category'].cat.codes ``` 这样用户可以看到如何将BMI数值转换为有序类别,并进行编码。同时,要提醒用户验证分箱后的分布是否合理,避免某些类别样本过少。 最后,可能需要考虑在实际应用中的注意事项,比如模型选择、评估指标是否需要调整(例如使用加权准确率或Kappa系数),以及解释结果时要考虑类别顺序的影响。例如,在特征重要性分析中,有序变量可能比连续变量更容易解释每个类别的影响程度。 总结一下,回答的结构应该是先解释什么是有序分类变量,然后分步骤说明如何将BMI转换为有序分类变量,包括分箱编码式,再讨论应用场景注意事项,最后给出代码示例相关问题。这样用户能够清晰地理解整个流程,并应用到实际项目中。</think>### 逐步解答:如何将BMI水平定义为有序分类变量 #### **1. 理解有序分类变量的特点** 有序分类变量(Ordinal Categorical Variable)的类别具有明确的顺序关系,但类别间的差异不一定相等。例如,BMI水平可分为: - **体重不足**(BMI < 18.5) - **正常**(18.5 ≤ BMI < 25) - **超重**(25 ≤ BMI < 30) - **肥胖**(BMI ≥ 30) 这种分类既保留了医学标准,又符合顺序逻辑[^1]。 --- #### **2. 数据分箱(Binning)** 将连续的BMI数值划分为离散的区间: - **等宽分箱**:按固定间隔划分(如WHO标准)。 $$ \text{BMI类别} = \begin{cases} \text{体重不足} & \text{if } x < 18.5 \\ \text{正常} & \text{if } 18.5 \leq x < 25 \\ \text{超重} & \text{if } 25 \leq x < 30 \\ \text{肥胖} & \text{if } x \geq 30 \end{cases} $$ - **等频分箱**:按分位数划分,保证每个类别样本量均衡。 --- #### **3. 编码为有序变量** 在机器学习中需将类别映射为数值,同时保留顺序信息: - **序数编码(Ordinal Encoding)**: ```python from sklearn.preprocessing import OrdinalEncoder encoder = OrdinalEncoder(categories=[['体重不足', '正常', '超重', '肥胖']]) data['BMI_encoded'] = encoder.fit_transform(data[['BMI_category']]) ``` - 避免使用独热编码(One-Hot Encoding),否则会丢失顺序信息。 --- #### **4. 应用场景** - **统计建模**:如有序逻辑回归(Proportional Odds Model),分析BMI与疾病风险的关系。 - **特征工程**:简化复杂模型(如决策树)的输入特征。 - **解释性增强**:在可解释性法(如LIME)中,有序变量更易描述特征影响[^1]。 --- #### **5. 注意事项** - **分箱合理性**:需参考医学标准,避免主观划分。 - **信息损失**:分箱会丢失连续变量的细节,可能影响模型精度。 - **模型适配性**:部分模型(如线性回归)需显式声明变量为有序类型。 --- ### 代码示例(Python) ```python import pandas as pd # 生成示例数据 data = pd.DataFrame({'BMI': [17.8, 22.3, 28.7, 31.2, 19.5]}) # 分箱操作 bins = [0, 18.5, 25, 30, float('inf')] labels = ['体重不足', '正常', '超重', '肥胖'] data['BMI_category'] = pd.cut(data['BMI'], bins=bins, labels=labels, right=False) # 序数编码 data['BMI_ordinal'] = data['BMI_category'].cat.codes print(data) ``` --- ### 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值