浅析特征数据离散化的几种方法(上)

简介: 什么是离散化?离散化就是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:

什么是离散化?

离散化就是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:

  • 原数据为:1,999,100000,15;处理之后为:1,3,4,2;
  • 原数据为:{100,200},{20,50000},{1,400}; 处理之后为:{3,4},{2,6},{1,5};

数据离散化是指将连续的数据进行分段,使其变为一段段离散化的区间。离散化过程也被表述成分箱(Binning)的过程。特征离散化常应用于逻辑回归和金融领域的评分卡中,同时在规则提取,特征分类中也有对应的应用价值。

特征离散化(分箱)可以从不同的角度来进行划分。当分箱方法使用了目标y的信息,那么该分箱方法就属于有监督的分箱方法,反之为无监督的分箱方法。

本文将介绍特征离散化常见的几种方法:

  1. 距离分箱-无监督
  2. 等频分箱-无监督
  3. 利用聚类分箱-无监督
  4. 信息熵分箱-有监督
  5. 基于决策树分箱-有监督
  6. 卡方分箱-有监督


数据离散化的意义

模型中,同一线性系数应该对所有可能的计数值起作用。过大的计数值对无监督学习方法也会造成破坏,比如k-均值聚类,它使用欧氏距离作为相似度函数来测量数据点之间的相似度。数据向量某个元素中过大的计数值对相似度的影响会远超其他元素,从而破坏整体的相似度测量。

离散化除了一些计算方面等等好处,还可以引入非线性特性,模型会更稳定。具体的优点如下:

1. 算法需要

比如决策树、朴素贝叶斯等算法,都是基于离散型的数据展开的。如果要使用该类算法,必须使用离散型的数据进行。有效的离散化能减小算法的时间和空间开销,提高系统对样本的分类聚类能力和抗噪声能力。

2. 离散化的特征相对于连续型特征更易理解,更接近知识层面的表达

比如,工资收入,月薪2000和月薪20000,从连续型特征来看高低薪的差异还要通过数值层面才能理解,但将其转换为离散型数据(底薪、高薪),则可以更加直观的表达出了我们心中所想的高薪和底薪。

3. 可以有效的克服数据中隐藏的缺陷,使模型结果更加稳定,增强模型鲁棒性

4. 提升模型的泛化能力

5. 扩展数据在不同各类型算法中的应用范围

6. 增加非线性表达能力

连续特征不同区间对模型贡献或者重要程度不一样时,分箱后不同的权重能直接体现这种差异,离散化后的特征再进行特征 交叉衍生能力会进一步加强。

当然特征离散化也有其缺点,具体如下:

1. 分箱操作必定会导致一定程度的信息损失

2. 增加流程:建模过程中加入了额外的的离散化步骤

3. 影响模型稳定性

当一个特征值处于分箱点的边缘时,此时微小的偏差会造成该特征值的归属从一箱跃迁到另外一箱,影响模型的稳定性。


准备数据

加载乳腺癌数据集,并打印原始特征分布。

import seaborn as sns
import pandas as pd
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)
复制代码


运行结果如下:

网络异常,图片无法展示
|


# 查看数据的统计摘要,如均值,最大值,最小值等
df.describe()[['mean radius','target']]
复制代码


运行结果如下:

mean radius target
count 569.000000 569.000000
mean 14.127292 0.627417
std 3.524049 0.483918
min 6.981000 0.000000
25% 11.700000 0.000000
50% 13.370000 1.000000
75% 15.780000 1.000000
max 28.110000 1.000000


距离分箱(距离区间法)

距离分箱可使用等距区间自定义区间的方式对数据进行离散化,分段可以是线性的,也可以是指数性的。该方法(尤其是等距区间)可以较好地保持数据原有的分布。

等距分箱

等距分箱指的是每个分隔点或者划分点的距离一样,即等宽。在实践中一般指定分隔的箱数,等分计算后得到每个分隔点。例如,将数据序列分为n份,则分隔点的宽度计算公式为:

w=max−minnw = \frac {max- min}{n}w=nmaxmin

这样就将原始数据划分成了n个等宽的子区间,一般情况下,分箱后每个箱内的样本数量是不一致的。

下面使用pandas中的cut函数来实现等宽分箱,代码如下:

import pprint
# 等宽分箱(Equal-Width Binning)
value, cutoff = pd.cut(df['mean radius'], bins=4, retbins=True, precision=2)
radius = df['mean radius']
print(radius.head())
print("-------------------")
print(value.head())
print("-------------------")
pprint.pprint(cutoff)
复制代码


运行结果如下:

0    17.99
1    20.57
2    19.69
3    11.42
4    20.29
Name: mean radius, dtype: float64
-------------------
0    (17.55, 22.83]
1    (17.55, 22.83]
2    (17.55, 22.83]
3     (6.96, 12.26]
4    (17.55, 22.83]
Name: mean radius, dtype: category
Categories (4, interval[float64]): [(6.96, 12.26] < (12.26, 17.55] < (17.55, 22.83] < (22.83, 28.11]]
-------------------
array([ 6.959871, 12.26325 , 17.5455  , 22.82775 , 28.11    ])
复制代码


可视化:

df1 = value.to_frame()
df1.columns = ['bins']
sns.countplot(df1['bins'])
复制代码


网络异常,图片无法展示
|


等距分箱计算简单,但是当数值方差较大时,即数据离散程度很大,那么很可能出现没有任何数据的分箱,这个问题可以通过自适应数据分布的分箱方法(等频分箱)来避免。

自定义距离分箱

下面使用numpy的digitize函数和pandas的cut函数这两种方式来实现自定义距离分箱。

import numpy as np
import pandas as pd
small_counts = np.random.randint(0, 10000, 20)
df = pd.DataFrame()
df['amount'] = small_counts
# 注意:bins数据是有要求的,bins内的数据一定要是降序或者升序的数据,不能是一堆无序数据。
bins=[0,200,1000,5000,10000]
# 方法一
df['amount_cut']=pd.cut(df['amount'], bins)
# 方法二
indices=np.digitize(df['amount'],bins) # 返回值为每个值所属区间的索引。
print(indices)
df['amount_split']=[str(bins[i-1])+"~"+str(bins[i]) for i in indices] 
print(df.head())
复制代码


运行结果如下:

[2 4 4 2 3 3 4 3 1 4 3 4 4 4 4 3 3 4 3 4]
   amount     amount_cut amount_split
0     296    (200, 1000]     200~1000
1    9938  (5000, 10000]   5000~10000
2    5442  (5000, 10000]   5000~10000
3     240    (200, 1000]     200~1000
4    3811   (1000, 5000]    1000~5000
复制代码


可视化:

sns.countplot(df['amount_cut'])
复制代码


网络异常,图片无法展示
|


sns.countplot(df['amount_split'])
复制代码


网络异常,图片无法展示
|


指数性区间分箱

当数值横跨多个数量级时,最好按照10的幂(或任何常数的幂)来进行分组:0~9、10~99、100~999、1000~9999。

import pprint
large_counts = np.array([296, 8286, 64011, 80, 3, 725, 867, 2215, 7689, 11495, 91897, 44, 28, 7971,926, 122, 22222])
print(large_counts.tolist())
bins=np.floor(np.log10(large_counts))   # 取对数之后再向下取整
print(bins)
pprint.pprint(np.array(['['+str(10**i)+','+str(10**(i+1))+')' for i in bins]))
复制代码


运行结果如下:

[296, 8286, 64011, 80, 3, 725, 867, 2215, 7689, 11495, 91897, 44, 28, 7971, 926, 122, 22222]
[2. 3. 4. 1. 0. 2. 2. 3. 3. 4. 4. 1. 1. 3. 2. 2. 4.]
array(['[100.0,1000.0)', '[1000.0,10000.0)', '[10000.0,100000.0)',
       '[10.0,100.0)', '[1.0,10.0)', '[100.0,1000.0)', '[100.0,1000.0)',
       '[1000.0,10000.0)', '[1000.0,10000.0)', '[10000.0,100000.0)',
       '[10000.0,100000.0)', '[10.0,100.0)', '[10.0,100.0)',
       '[1000.0,10000.0)', '[100.0,1000.0)', '[100.0,1000.0)',
       '[10000.0,100000.0)'], dtype='<U18')
复制代码


等频分箱(分位数法)

顾名思义,等频分箱理论上分隔后的每个箱内得到数据量大小一致,但是当某个值出现次数较多时,会出现等分边界是同一个值,导致同一数值分到不同的箱内,这是不正确的。

具体的实现可以去除分界处的重复值,但这也导致每箱的数量不一致。

具体代码如下:

s1 = pd.Series([1,2,3,4,5,6])
value, cutoff = pd.qcut(s1, 3, retbins=True)
print(value)
print("------------------")
print(cutoff)
复制代码


运行结果:

0    (0.999, 2.667]
1    (0.999, 2.667]
2    (2.667, 4.333]
3    (2.667, 4.333]
4      (4.333, 6.0]
5      (4.333, 6.0]
dtype: category
Categories (3, interval[float64]): [(0.999, 2.667] < (2.667, 4.333] < (4.333, 6.0]]
------------------
[1.         2.66666667 4.33333333 6.        ]
复制代码


可视化:

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)
复制代码


运行结果:

0    (0.999, 3.667]
1    (0.999, 3.667]
2    (0.999, 3.667]
3      (3.667, 6.0]
4      (3.667, 6.0]
5      (3.667, 6.0]
6      (3.667, 6.0]
7      (3.667, 6.0]
8      (3.667, 6.0]
dtype: category
Categories (2, interval[float64]): [(0.999, 3.667] < (3.667, 6.0]]
------------------
[1.         3.66666667 6.        ]
复制代码


可视化:

sns.countplot(value)
复制代码


网络异常,图片无法展示
|


本来是要分成3个箱子的,但是由于出现同一个数值被分到了不同的箱子里,因此被合并了,所以最后只有2个箱子。

同样,我们对乳腺癌数据进行等频分箱,该数据分布正常,等频分箱后每箱数量基本一致。

value, cutoff = pd.qcut(df['mean radius'], 4, duplicates='drop', retbins=True)
print(value)
print("------------------")
print(cutoff)
复制代码


结果如下:

0                  (15.78, 28.11]
1                  (15.78, 28.11]
2                  (15.78, 28.11]
3      (6.9799999999999995, 11.7]
4                  (15.78, 28.11]
                  ...            
564                (15.78, 28.11]
565                (15.78, 28.11]
566                (15.78, 28.11]
567                (15.78, 28.11]
568    (6.9799999999999995, 11.7]
Name: mean radius, Length: 569, dtype: category
Categories (4, interval[float64]): [(6.9799999999999995, 11.7] < (11.7, 13.37] < (13.37, 15.78] < (15.78, 28.11]]
------------------
[ 6.981 11.7   13.37  15.78  28.11 ]
复制代码


可视化:

sns.countplot(value)
复制代码


网络异常,图片无法展示
|


上述的等宽和等频分箱容易出现的问题是每箱中信息量变化不大。例如,等宽分箱不太适合分布不均匀的数据集、离群值;等频方法不太适合特定的值占比过多的数据集,如长尾分布。

相关文章
|
1月前
|
机器学习/深度学习 数据挖掘 Python
时序数据的分类及质心的计算
时序数据的分类及质心的计算
|
1月前
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享(上)
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享
|
1月前
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享(下)
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享
|
8月前
为什么进行线性回归前需要对特征进行离散化处理?
为什么进行线性回归前需要对特征进行离散化处理?
145 1
|
11月前
|
vr&ar
用于非线性时间序列预测的稀疏局部线性和邻域嵌入(Matlab代码实现)
用于非线性时间序列预测的稀疏局部线性和邻域嵌入(Matlab代码实现)
用于非线性时间序列预测的稀疏局部线性和邻域嵌入(Matlab代码实现)
|
10月前
|
算法
【模型预测控制MPC】使用离散、连续、线性或非线性模型对预测控制进行建模(Matlab代码实现)
【模型预测控制MPC】使用离散、连续、线性或非线性模型对预测控制进行建模(Matlab代码实现)
239 0
|
机器学习/深度学习 算法 开发者
特征生成(特征创建)
特征生成(特征创建)
|
机器学习/深度学习 自然语言处理 算法
稀疏特征和密集特征
在机器学习中,特征是指对象、人或现象的可测量和可量化的属性或特征。特征可以大致分为两类:稀疏特征和密集特征。
175 0
|
机器学习/深度学习 算法 测试技术
适合离散值分类的多分类模型——softmax回归
适合离散值分类的多分类模型——softmax回归
适合离散值分类的多分类模型——softmax回归
|
数据采集 机器学习/深度学习 算法
浅析特征数据离散化的几种方法(下)
什么是离散化? 离散化就是把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如: