简介
特征离散化指的是将连续特征划分离散的过程:将原始定量特征的一个区间一一映射到单一的值。离散化过程也被表述成分箱(Binning)的过程。特征离散化常应用于逻辑回归和金融领域的评分卡中,同时在规则提取,特征分类中也有对应的应用价值。本文主要介绍几种常见的分箱方法,包括等宽分箱、等频分箱、信息熵分箱、基于决策树分箱、卡方分箱等。
分箱原理介绍
数据分箱是一种数据预处理技术,用于减少次要观察误差的影响,是一种将多个连续值分为相对较少的若干分组的方法。下面举个例子来具体说明一下,假设我们有下面一组数据,表示花瓣的宽度,分布如下:- 有助于模型部署和应用,加快模型迭代
- 增强模型鲁棒性
- 增加非线性表达能力:连续特征不同区间对模型贡献或者重要程度不一样时,分箱后不同的权重能直接体现这种差异,离散化后的特征再进行特征 交叉衍生能力会进一步加强。
- 提升模型的泛化能力
- 扩展数据在不同各类型算法中的应用范围
当然特征离散化也有其缺点,总结如下:
- 分箱操作必定会导致一定程度的信息损失
- 增加流程:建模过程中加入了额外的的离散化步骤
- 影响模型稳定性: 当一个特征值处于分箱点的边缘时,此时微小的偏差会造成该特征值的归属从一箱跃迁到另外一箱,影响模型的稳定性。
特征离散化(分箱)可以从不同的角度来进行划分。当分箱方法使用了目标y的信息,那么该分箱方法就属于有监督的分箱方法,反之为无监督的分箱方法。
这里选择sklearn中自带的乳腺癌数据集,下文提及的分箱方法大多是基于此数据集,主要是取其中的‘mean radius’字段,如下:
import seaborn as sns
from sklearn.datasets import load_breast_cancer
bc = load_breast_cancer()
df = pd.DataFrame.from_records(data=bc.data, columns=bc.feature_names)
df['target'] = bc.target
sns.distplot(df['mean radius'], kde=False)
等宽分箱(Equal-Width Binning)
等宽分箱指的是每个分隔点或者划分点的距离一样,即等宽。实践中一般指定分隔的箱数,等分计算后得到每个分隔点。例如将数据序列分为n份,则 分隔点的宽度计算公式为:
这样就将原始数据划分成了n个等宽的子区间,一般情况下,分箱后每个箱内的样本数量是不一致的。使用pandas中的cut函数来实现等宽分箱,代码如下:
value, cutoff = pd.cut(df['mean radius'], bins=4, retbins=True, precision=2)
cutoff
可以轻易计算得出任意两个相邻分隔点之间的距离为5.30。按照上述分隔点对数据进行划分后,数据的分布如下:
df1 = value.to_frame()
df1.columns = ['bins']
sns.countplot(df1['bins'])
等宽分箱计算简单,但是当数值方差较大时,即数据离散程度很大,那么很可能出现没有任何数据的分箱,这个问题可以通过自适应数据分布的分箱方法--等频分箱来避免
等频分箱(Equal-Frequency Binning)
顾名思义,等频分箱理论上分隔后的每个箱内得到数据量大小一致,但是当某个值出现次数较多时,会出现等分边界是同一个值,导致同一数值分到不同的箱内,这是不正确的。具体的实现可以去除分界处的重复值,但这也导致每箱的数量不一致。如下代码:
s1 = pd.Series([1,2,3,4,5,6])
value, cutoff = pd.qcut(s1, 3, retbins=True)
sns.countplot(value)
每个区间分别是2个数,这没有问题,但是如果某个数字出现的次数较多,则可能出现下面的情况:
s1 = pd.Series([1,2,3,4,5,6,6,6,6])
value, cutoff = pd.qcut(s1, 3, duplicates='drop', retbins=True)
sns.countplot(value)
本来是要分成3个箱子的,但是由于出现同一个数值被分到了不同的箱子里,因此被合并了,所以最后只有2个箱子。
同样,我们对乳腺癌数据进行等频分箱,该数据分布正常,等频分箱后每箱数量基本一致。
value, cutoff = pd.qcut(df['mean radius'], 4, duplicates='drop', retbins=True)
sns.countplot(value)
上述的等宽和等频分箱容易出现的问题是每箱中信息量变化不大。例如,等宽分箱不太适合分布不均匀的数据集、离群值;等频方法不太适合特定的值占比过多的数据集,如长尾分布。
信息熵分箱
上面介绍的分箱方法对建模的优化有限。如果分箱后箱内样本对y的区分度好,那么这是一个好的分箱。通过信息论理论,我们可知信息熵衡量了这种区分能力。当特征按照某个分隔点划分为上下两部分后能达到最大的信息增益,那么这就是一个好的分隔点。由上可知,信息熵分箱是有监督的分箱方法。其实决策树的节点分裂原理也是基于信息熵。
首先我们需要明确信息熵和信息增益的计算方式,分别如下:
在二分类问题中,。
信息增益的物理含义表达为:x的分隔带来的信息对y的不确定性带来的增益。
对于二值化的单点分隔,如果我们找到一个分隔点将数据一分为二,分成和两部分,那么划分后的信息熵的计算方式为:
下面以一个实例来介绍一下信息熵分箱的计算过程,假如我们有以下数据,我们以特征'x=3'来作为特征x的分隔点,则其带来的信息增益为0.420,如下:
data = [[1,0],[2,1],[3,0],[4,1],[5,1]]
df = pd.DataFrame(data, columns=['x','y'])
df
接下来计算划分后的信息熵:
故信息增益为:
类似的可以计算其他分隔点的信息增益,最终选取信息增益最大时对应的分隔点。同时也可以看出,当分箱后,某个箱中的标签y的类别(0或者1)的比例相等时,其熵值最大,表明此特征划分几乎没有区分度。而当某个箱中的数据的标签y为单个类别时,那么该箱的熵值达到最小的0,即纯度最纯,最具区分度。从结果上来看,最大信息增益对应分箱后的总熵值最小。
决策树分箱
其实上一节中已经说到,由于决策树的结点选择和划分也是根据信息熵来计算的,因此我们其实可以利用决策树算法来进行特征选择,具体做法如下:
还是以乳腺癌数据为例,首先取其中‘mean radius’字段,和标签字段‘target’来拟合一棵决策树,代码如下:
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(criterion='entropy', max_depth=3) # 树最大深度为3
dt.fit(df['mean radius'].values.reshape(-1, 1), df['target'])
接着我们取出这课决策树的所有叶节点的分割点的阈值,如下:
qts = dt.tree_.threshold[np.where(dt.tree_.children_left > -1)]
qts = np.sort(qts)
res = [np.round(x, 3) for x in qts.tolist()]
res
注意这里只给出了6个点,但是相当于分了7个箱子,分别设为a-g,我们可以将划分后的效果绘制出来:
l = df['mean radius'].values.tolist()
r = []
for i in l:
if i < res[0]:
r.append('a')
elif i >= res[-1]:
r.append('g')
else:
for j in range(0, 5):
if i > res[j] and i <= res[j+1]:
r.append(chr(98+j))
break
ax = sns.countplot(r)
ax
卡方分箱
在了解卡方分箱之前,我们需要先了解几个关键概念,比如卡方分布,卡方检验等。
卡方分布是概率统计常见的一种概率分布,是卡方检验的基础。
卡方分布定义为:若n个独立的随机变量满足标准正态分布,则n个随机变量的平方和为服从自由度为k的卡方分布,记为。参数n称为自由度(样本中独立或能自由变化的自变量的个数),不同的自由度是不同的分布。例如,一个标准正态分布的平方就是自由度为1的卡方分布。卡方分布的概率密度函数如下:
- 卡方拟合 优度检验:用于检验样本是否来自于某一个分布,比如检验某样本是否为正态分布
- 独立性卡方检验,查看两组类别变量分布是否有差异或者相关,以列联表的形式比较。以列联表形式的卡方检验中,卡方统计量由上式给出。
其中,表示观察到的频数(即实际出现的次数),表示期望的频数。很明显,越小的卡方值,表明两者相差越小,反之,越大的卡方值表明两者差异越大。下面以一个具体的例子来说明卡方值的计算方式。
下面是观察到的两组样本分布:
观察 | bad | good | 合计 |
---|---|---|---|
组一 | 25 | 50 | 75 |
组二 | 30 | 15 | 45 |
合计 | 55 | 65 | 120 |
接着是期望频数的计算过程:
期望 | bad | good |
---|---|---|
组一 | 55*(75/120)=34.375 | 65*(75/120)=40.625 |
组二 | 55*(45/120)=20.625 | 65*(45/120)=24.375 |
最后是卡方值的计算过程:
a = [25, 50, 30, 15]
b = [34.375, 40.625, 20.625, 24.375]
r= []
for i in zip(a,b):
r.append((i[0]-i[1])**2 / i[1])
np.sum(r)
科学计算库SciPy中包含了卡方检验的实现,例子如下:
from scipy.stats import chi2_contingency
obs = np.array([[25,50],[30,15]])
chi2, p, dof, ex = chi2_contingency(obs, correction=False)
chi2
其中dof代表自由度,ex代表期望概率。在得到卡方值以后查询卡方分布表并比较p_value值,继而做出接受或者拒绝原假设的判断。下段代码是输出卡方分布表的实现:
from scipy.stats import chi2
def chi2_table(freedom=10, alpha=None):
if alpha is None:
alpha = [0.99, 0.95, 0.9, 0.5, 0.1, 0.05, 0.025, 0.01, 0.005]
df = pd.DataFrame([chi2.isf(alpha, df=i) for i in range(1, freedom)])
df.columns = alpha
df.index = df.index + 1
return df
chi2_table()
由表可知,当自由度为1、置信水平为0.05时,对应的卡方值为3.84,而上述例子计算出来的卡方值为12.587,大于3.841。说明在0.05的显著性水平是可以拒绝原假设的,即观察频数与期望频数有差异。换个角度描述卡方检验的物理含义:当两个分箱中,好坏(正负)分布是一致时,卡方为0,相似是接近0,对应到卡方分箱算法中,应该合并这两个分箱。
卡方分箱步骤
卡方分箱是自底向上的(即基于合并的)数据离散化方法。它依赖于卡方检验:具有最小卡方值的相邻区间合并在一起,直到满足确定的停止准则。基本思想: 对于精确的离散化,相对类频率在一个区间内应当完全一致。因此,如果两个相邻的区间具有非常类似的类分布,则这两个区间可以合并;否则,它们应当保持分开。而低卡方值表明它们具有相似的类分布。
卡方检验可以用来评估两个分布的相似性,因此可以将这个特性用到数据分箱的过程中。