数据离散化
数据离散化(也叫数据分组)是指将连续的数据进行分组,使其变为一段段离散化的区间。
根据离散化过程中是否考虑类别属性,可以将离散化算法分为有监督算法和无监督算法两类。由于有监督算法(如基于信息熵进行数据的离散化)充分利用了类别属性的信息,所以在分类中能获得较高的正确率。
以下介绍的数据分组方法均需要对数据进行排序,且假设待离散化的数据按升序排列。
1.等宽分组
等宽分组的原理是:根据分组的个数得出固定的宽度,分到每个组中的变量的宽度是相等的。
例如,将一组变量(1,7,12,12,22,30,34,38,46)分成三组
(1)宽度为15,即用变量中的最大值(46)减去变量中最小的值(1),然后用差除以组数3.
(2)每组的范围为:(1,16]、(19,31]、(31,46]
(3)分组后的结果为:(1,7,12,12)、(22,30)、(34,38,46)
2.等频分组
等频分组也叫分位数分组,即分组后每组的变量个数相同。
将上面变量分组,得到结果为(1,7,12)、(12,22,30)、(34,38,46)
注意:
等宽分组和等频分组实现起来比较简单,但都需要人为地指定分组个数。
等宽分组的缺点是:对离群值比较敏感,将属性值不均匀地分布到各个区间。有些区间包含的变量较多,有些区间包含的变量较少。
等频分组虽然能避免等宽分组的缺点,但是会将相同的变量值分到不同的组(以满足每个组内的变量个数相同)
3.单变量分组
单变量分组也叫秩分组。其原理是:将所有变量按照降序或升序,排序名次即为排序结果,即将值相同的变量划分到一组。
将上述分组结果为:(1)、(7)、(12,12)、(22)、(30)、(34)、(38)、(46)
4.基于信息熵分组
先说说信息量和熵的概念。
(1)信息量
香农(Shannon)被称为“信息论之父”,他被认为“信息是用来消除随机不确定性的东西”。即,衡量信息量大小就看这个信息消除不确定性的程度。
信息量的大小和事件发生的概率成反比。信息量可表示为:
信息量度量的是“一个具体事件发生”所带来的信息。
(2)熵
熵是在结果出来之前对可能产生的信息量的期望——考虑该随机变量的所有可能取值,即所有可能发生事件所带来的信息量的期望。
表示事件发生的概率,n为x中所有类别的个数。
按照随机变量的所有可能取值划分数据的总熵E是所有事件的熵的加权平均:
式中,是第x个事件出现的比例,是第i个可能取值出现的次数,m是所有取值出现的总次数。
注意:
熵表示样本集合的不确定性。熵越大,则样本的不确定性越大。
基于信息熵进行数据分组的具体做法是:
(1)对属性A的所有取值从小到大排序;
(2)遍历属性A的每一个值,将属性A的值分为两个区间,使得将其作为分隔点划分数据集后的熵S最小。
(3)当划分后的熵大于设置的阈(yu,四声)值且小于指定的数据个数时,递归对S1,S2执行步骤(2)中的划分。
数据如下:
# -*-coding:utf-8-*-
import numpy as np
import math
class DiscreteByEntropy:
def __init__(self, group, threshold):
self.maxGroup = group # 最大分组数
self.minInfoThreshold = threshold # 停止划分的最小熵
self.result = dict() # 保存划分结果
# 准备数据
def loadData(self):
data = np.array(
[
[56, 1], [87, 1], [129, 0], [23, 0], [342, 1],
[641, 1], [63, 0], [2764, 1], [2323, 0], [453, 1],
[10, 1], [9, 0], [88, 1], [222, 0], [97, 0],
[2398, 1], [592, 1], [561, 1], [764, 0], [121, 1],
]
)
return data
# 该步骤是计算数据的信息熵,是为下一步分割数据集做准备。
# 计算按照数据指定数据分组后的香农熵
def calEntropy(self, data):
numData = len(data)
labelCounts = {}
for feature in data:
# 获得标签
oneLabel = feature[-1]
# 如果标签不在新定义的字典里创建该标签值
labelCounts.setdefault(oneLabel, 0)
# 该类标签下含有数据的个数
labelCounts[oneLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
# 同类标签出现的概率
prob = float(labelCounts[key]) / numData
# 以2为底求对数
shannonEnt -= prob * math.log(prob, 2)
return shannonEnt
# 寻找一组数据最佳分割点的方法是:遍历所有属性值,数据按照该属性分割,使得平均熵最小。
# 按照调和信息熵最小化原则分割数据集
def split(self, data):
# inf为正无穷大
minEntropy = np.inf
# 记录最终分割索引
index = -1
# 按照第一列对数据进行升序排序
sortData = data[np.argsort(data[:, 0])]
# 初始化最终分割数据后的熵
lastE1, lastE2 = -1, -1
# 返回的数据结构,包含数据和对应的熵
S1 = dict()
S2 = dict()
for i in range(len(sortData)):
# 分割数据集
splitData1, splitData2 = sortData[: i + 1], sortData[i + 1:]
entropy1, entropy2 = (
self.calEntropy(splitData1),
self.calEntropy(splitData2),
) # 计算信息熵
entropy = entropy1 * len(splitData1) / len(sortData) + \
entropy2 * len(splitData2) / len(sortData)
# 如果调和平均熵小于最小值
if entropy < minEntropy:
minEntropy = entropy
index = i
lastE1 = entropy1
lastE2 = entropy2
S1["entropy"] = lastE1
S1["data"] = sortData[: index + 1]
S2["entropy"] = lastE2
S2["data"] = sortData[index + 1:]
return S1, S2, entropy
# 对数据进行离散化处理
# 对数据进行分组
def train(self, data):
# 需要遍历的key
needSplitKey = [0]
# 将整个数据作为一组
self.result.setdefault(0, {})
self.result[0]["entropy"] = np.inf
self.result[0]["data"] = data
group = 1
for key in needSplitKey:
S1, S2, entropy = self.split(self.result[key]["data"])
# 如果满足条件
if entropy > self.minInfoThreshold and group < self.maxGroup:
self.result[key] = S1
newKey = max(self.result.keys()) + 1
self.result[newKey] = S2
needSplitKey.extend([key])
needSplitKey.extend([newKey])
group += 1
else:
break
if __name__ == "__main__":
dbe = DiscreteByEntropy(group=6, threshold=0.5)
data = dbe.loadData()
dbe.train(data)
print("result is {}".format(dbe.result))