一、原理
數據離散化(也稱,數據分組),指將連續的數據進行分組,使其變為一段離散化的區間。
根據離散化過程中是否考慮類別屬性,可以將離散化算法分為:有監督算法和無監督算法。事實證明,由於有監督算法充分利用了類別屬性的信息,所以再分類中能獲得較高的正確率。
常用的數據離散化方法:
- 等寬分組
- 等頻分組
- 單變量分組
- 基於信息熵分組
數據離散化所使用的方法需要事先對數據進行排序,且假設待離散化的數據是按照升序排序。
1、等寬分組
原理:根據分組的個數得出固定的寬度,分到每個組中的變量的寬度是相等的。
如:現在有一個待離散化的數組[1, 7, 12, 12, 22, 30, 34, 38, 46],需要分成三組,
那么,,即寬度 =( 46 - 1)/3 = 15
分組后結果范圍:[1,16],(16, 31],(31, 46],第一個分組取的是全閉區間,
分組后結果:[1, 7, 12, 12],[22, 30],[34, 38, 46]
2、等頻分組
原理:等頻分組也叫分位數分組,即分組后,每個分組的元素個數是一樣的。
如:現在有一個待離散化的數組[1, 7, 12, 12, 22, 30, 34, 38, 46],需要分成三組,
那么,,即每組元素的個數 = 9 / 3 = 3
分組后的結果:[1, 7, 12],[12, 22, 30], [34, 38, 46]
3、單變量分組
原理:單變量分組,也叫秩分組。將所有元素按照降序或者升序排序,排序名次即為排序結果,即將相同的元素划分到同一個組。
如:現在有一個待離散化的數組[1, 7, 12, 12, 22, 30, 34, 38, 46],
分組后的結果:[1], [7], [12, 12], [22], [30], [34], [38], [46]
4、基於信息熵分組
概念:
(1)信息量
Shannon認為,信息是用來消除隨機不確定性的東西。即,衡量信息量大小就看這個消息消除不確定性的程度。
信息量的大小和事件發生的概率成反比。可以用公式表示為:
式中,p(x)表示x發生的概率。
(2)熵
熵,是在結果出來之前對可能產生的信息量的期望——考慮該隨機變量的所有可能取值,即所有可能發生事件所帶來的信息量的期望。
可以表示為:
按照隨機變量的所有可能取值划分數據的總熵E是所有事件的熵的加權平均:
式中,是第x個事件出現的比例,是第個可能取值出現的次數,是所有取值出現的總次數。
熵表示的是樣本集合的不確定性。熵越大,則樣本的不確定性越大。
所以,基於信息熵進行數據分組的具體做法是:
- 對屬性A的所有取值從小到大進行排序;
- 遍歷屬性A的每個值,將屬性A的值分為兩個區間、,使得將其作為分隔點划分數據集后的熵最小;
- 當划分后的熵大於設置的閾值且小於指定的數據分組個數時,遞歸對、執行步驟2中的划分。
總結:
上述分組方法中,等寬分組和等頻分組實現起來比較簡單,但需要人為指定分組個數。
等寬分組的缺點:對離散值比較敏感,將屬性值不均勻地分布到各個區間。有些區間的元素個數較多,有些則較少,容易導致數據傾斜。
等頻分組雖然能避免等寬分組的缺點,但是會將相同的元素分到不同的組,如例子中的“12”元素。
二、基於信息熵的數據離散化實現
import numpy as np import math class DiscreateByEntropy: 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 # 計算按照數據指定數據分組后的Shannon熵
def calEntropy(self, data): numData = len(data) labelCounts = {} for feature in data: # 獲得標簽,這里只有0或者1
oneLabel = feature[-1] # 設置字典中,標簽的默認值
if labelCounts.get(oneLabel,-1) == -1: labelCounts[oneLabel] = 0 # 統計同類標簽的數量
labelCounts[oneLabel] += 1 shannoEnt = 0.0
for key in labelCounts: # 同類標簽出現的概率,某一標簽出現的次數除以所有標簽的數量
prob = float(labelCounts[key])/numData # 求熵,以2為底,取對數
shannoEnt -= prob * math.log2(prob) return shannoEnt # 按照調和信息熵最小化原則分割數據集
def split(self, data): # inf為正無窮
minEntropy = np.inf # 記錄最終分割的索引
index = -1
# 按照第一列對數據進行排序
sortData = data[np.argsort(data[:,0])] # print(sortData)
# 初始化最終分割數據后的熵
lastE1,lastE2 = -1, -1
# 返回的數據區間,包括數據和對應的熵
S1 = dict() S2 = dict() for i in range(len(data)): 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 = DiscreateByEntropy(group=6,threshold=0.5) data = dbe.loadData() dbe.train(data) print("result is {}".format(dbe.result))
運行結果:
可見,將商品價格分為了5份,下標分別對應了0,1,2,3,4.