机器学习小组知识点27:数据预处理之数据离散化(Data Discretization)

离散化和概念分层产生

通过将属性域划分为区间,离散化技术可以用来减少给定连续属性值的个数。区间的标号可以替代实际的数据值。如果使用基于判定树的分类挖掘方法,减少属性值的数量特别有好处。通常,这种方法是递归的,大量的时间花在每一步的数据排序上。因此,待排序的不同值越少,这种方法就应当越快。许多离散化技术都可以使用,以便提供属性值的分层或多维划分——概念分层

对于给定的数值属性,概念分层定义了该属性的一个离散化。通过收集并用较高层的概念(对
于年龄属性,如young, middle-age 和senior)替换较低层的概念(如,年龄的数值值),概念分层可以用来归约数据。通过这种泛化,尽管细节丢失了,但泛化后的数据更有意义、更容易解释,并且所需的空间比原数据少。在归约的数据上进行挖掘,与在大的、未泛化的数据上挖掘相比,所需的I/O 操作更少,并且更有效

对于用户或领域专家,人工地定义概念分层可能是一项令人乏味、耗时的任务。幸而,许多分层蕴涵在数据库模式中,并且可以在模式定义级定义。概念分层常常自动地产生,或根据数据分布的统计分析动态地加以提炼。

数值属性的概念分层可以根据数据分布分析自动地构造。五种数值概念分层产生方法:分
箱、直方图分析、聚类分析、基于熵的离散化和通过“自然划分”的数据分段。

分箱

分箱方法。这些方法也是离散化形式。例如,通过将数据分布到箱中,并用箱中的平均值或中值替换箱中的每个值,可以将属性值离散化。就象用箱的平均值或箱的中值平滑一样。这些技术可以递归地作用于结果划分,产生概念分层。

直方图分析

直方图分析算法递归地用于每一部分,自动地产生多级概念分层,直到到达一个预先设定的概念层数,过程终止。也可以对每一层使用最小区间长度来控制递归过程。最小区间长度设定每层每部分的最小宽度,或每层每部分中值的最少数目。

聚类分析

聚类算法可以用来将数据划分成聚类或群。每一个聚类形成概念分层的一个结点,而所有的结点在同一概念层。每一个聚类可以进一步分成若干子聚类,形成较低的概念层。聚类也可以聚集在一起,以形成分层结构中较高的概念层

基于熵的离散化

一种基于信息的度量称作熵,可以用来递归地划分数值属性A 的值,产生分层的离散化。这种离散化形成属性的数值概念分层。给定一个数据元组的集合S,基于熵对A 离散化的方法如下:
A 的每个值可以认为是一个潜在的区间边界或阈值T。例如,A 的值v 可以将样本S划分成分别满足条件A<vAv 的两个子集,这样就创建了一个二元离散化。
给定S,所选择的阈值是这样的值,它使其后划分得到的信息增益最大。
其中,S1S2 分别对应于S 中满足条件A<TAT 的样本。对于给定的集合,它的熵函数根据集合中样本的类分布来计算

算法描述如下:

STEP 1:

(1)初始化分裂点集合为空,属性所有取值集合为全集。

(2)按照属性值的大小顺序,将属性值集合和对应的类标号集合排序。

(3)选择最佳分类点(熵值最小)将属性分类为两个区间。

(4)递归处理所得到的两个区间。

STEP 2:

(1)合并所有的相邻、区间类信息熵为0且区间里属性类别相同的区间。

(2)计算所有的相邻区间合并后的区间类信息嫡,合并计算得到的区间类信息嫡最小且不超过阈值心的相邻区间

(3)重复(2)直到不再满足条件

具体实现如下:

#coding=utf-8
#以下为需要调节的参数
######################################################
ClassNum = 33 #设置类的总数,以生成统计不同类样本数量
######################################################
FeatureNum = 145 #生成需要离散化的属性数组
#######################################################
InfoThreshold = 0.5 #熵值大于此值时不再合并
#######################################################
MaxGroup = 70 #最大离散化成多少组(分割点数加1)
#######################################################
MinGroup = 5 #最少离散化成多少组(分割点数加1)
#######################################################
BlockSize = 5 #抽样大小,每BlockSize个数据抽取一个
#######################################################
MaxDeep = 7 #对大递归深度
#######################################################
MinSplit = 3000 #叶节点小于此值时不再分裂
#######################################################
MinLeaf = 100 #控制最小叶节点大小
#######################################################
NeedDisCol = [....] #需要离散化的维列表,默认维数从1开始(0对应id号)
######################################################
#训练集及类标
fTrain = open("...")
fLabel = open("...") #转化后的单列类标
######################################################
#写入文件
fDis = open("...", "a+")

#将NeedDisCol转变为0开始
for i in range(len(NeedDisCol)):
 NeedDisCol[i] = NeedDisCol[i] - 1

from pandas import Series, DataFrame
import pandas as pd
import random
import math
import numpy as np

ItTrain = pd.read_csv(fTrain, chunksize = BlockSize)
ItLabel = pd.read_csv(fLabel, chunksize = BlockSize)

#定义同时迭代两个文件的函数,返回list
def getLine(It, rd):
 for df in It:
 aList = df.values[:, 1:].tolist() #去掉id列,返回指定行
 if rd >= len(aList):
 return aList[0]
 else:
 return aList[rd]
 return False

#抽取样本数据
TrainSet = []
LabelSet = []
count = 0
while True:
 #产生随机数,并更新已读取文件的行数
 rd = int(random.random() * BlockSize)
 count = count + BlockSize

 TrainList = getLine(ItTrain, rd)
 if not TrainList:
 break
 else:
 TrainSet.append(TrainList)
 LabelList = getLine(ItLabel, rd)
 if not LabelList:
 break
 else:
 LabelSet.append(LabelList)
 ########################################
 if count % 100000 == 0:
 print "读取了%d行" % count
 ########################################



#将类标按照特征升序排列
def SortTogether(theFeature , theLabel):
 arr = np.array(theFeature)
 NumSort = np.argsort(arr)
 #复制thelabel\theFeature
 copyOfFeature = [0]*len(theFeature)
 copyOfLabel = [0]*len(theLabel)
 for i in range(len(theFeature)):
 copyOfFeature[i] = theFeature[i]
 copyOfLabel[i] = theLabel[i]
 #按获得的索引NumSort排序
 for i in range(len(theFeature)):
 theFeature[i] = copyOfFeature[NumSort[i]]
 theLabel[i] = copyOfLabel[NumSort[i]]


#将每个类标标上对应的tip(表示其(包含)前面的各类统计)
def TipTheLabel(theLabel):
 TipLabel = []
 for i in range(len(theLabel)):
 inTip = [0] * (ClassNum + 1) #从一开始
 TipLabel.append(inTip)
 TipLabel[0][theLabel[0]] = 1
 for i in range(1, len(theLabel)):
 for j in range(1, ClassNum + 1):
 TipLabel[i][j] = TipLabel[i - 1][j]
 TipLabel[i][theLabel[i]] = TipLabel[i][theLabel[i]] + 1
 return TipLabel


#获取对应区间的类的数量列表
def GetPointNumList(theLabel, start, end, TipLabel):
 result = []
 if start >= end or start < 0 or end >= len(theLabel):
 return [0] * ClassNum
 for i in range(1,ClassNum + 1):
 classCounter = TipLabel[end][i] - TipLabel[start][i]
 result.append(classCounter)
 return result

#计算特点的熵值
def GetEntropyOfCertainPoint(theLabel, start, end, thePoint, TipLabel):
 if start == end:
 return 0
 LList = GetPointNumList(theLabel, start, thePoint - 1, TipLabel)
 RList = GetPointNumList(theLabel, thePoint, end, TipLabel)
 NL = float(thePoint - start)
 NR = float(end - thePoint + 1)
 EntropyL = 0
 EntropyR = 0
 for aClassNum in LList:
 if aClassNum == 0:
 continue
 else:
 p = aClassNum / NL
 EntropyL = EntropyL - p * math.log(p)
 for aClassNum in RList:
 if aClassNum == 0:
 continue
 else:
 p = aClassNum / NR
 EntropyR = EntropyR - p * math.log(p)
 result = (NL * EntropyL) / (NL + NR) + (NR * EntropyR) / (NL + NR)
 return result


#获取有最大熵值的点,越小越好
def GetMaxEntropyPoint(theFeature, theLabel, start, end, TipLabel, ThisEntropy):
 MinPoint = start + MinLeaf
 MinEntropy = GetEntropyOfCertainPoint(theLabel, start, end, MinPoint, TipLabel)

 #i = MinPoint + 20
 #while True: 

 for i in range(MinPoint + 1, end - MinLeaf):
 theEntropy = GetEntropyOfCertainPoint(theLabel, start, end, i, TipLabel)
 if MinEntropy >= theEntropy:
 MinEntropy = theEntropy
 MinPoint = i
 #print "执行到球maxpoint", MaxPoint
 ThisEntropy[0] = MinEntropy
 return MinPoint

#合并左右分裂列表及本分裂点
def GetCombinList(LList, thePoint, RList):
 theList = []
 for aNum in LList:
 theList.append(aNum)
 theList.append(thePoint)
 for aNum in RList:
 theList.append(aNum)
 return theList

#递归离散化函数
def GetDisPointList(theFeature , theLabel, start, end, Deep, TipLabel):
 #大于最大递归深度,返回
 if Deep > MaxDeep:
 return []
 #小于最小分裂数,返回
 if end - start < MinSplit:
 return []
 #分割之前的信息熵
 #BeforeEntropy = GetEntropyOfCertainPoint( theLabel, start, end, start + 1, TipLabel)
 #分割
 ThisEntropy = [0] #留作扩展
 thePoint = GetMaxEntropyPoint(theFeature, theLabel, start, end, TipLabel, ThisEntropy)
 LList = GetDisPointList(theFeature, theLabel, start, thePoint - 1, Deep + 1, TipLabel)
 RList = GetDisPointList(theFeature, theLabel, thePoint, end, Deep + 1, TipLabel)
 CombinList = GetCombinList(LList, thePoint, RList)
 return CombinList

#最大熵离散化函数
def DisACol(theFeature , theLabel, ToTipLabel): 
 SortTogether(theFeature, theLabel)
 TipLabel = TipTheLabel(theLabel)
 DisPointList = GetDisPointList(theFeature , theLabel, 0, len(theFeature) - 1, 0, TipLabel)
 #print 'DisACol'
 ToTipLabel[0] = TipLabel
 return DisPointList

#由分裂点下表获取分裂点值
def GetDisNumFromPoint(theDisPointList, theFeature):
 result = []
 for aPoint in theDisPointList:
 result.append(theFeature[aPoint])
 return result

#获取列对应的拷bei
def getCertainCol(theSet, theCol):
 result = []
 for item in theSet:
 result.append(item[theCol])
 return result

#删除相同的点
def DelEqual(alist, theFeature):
 i = len(alist) - 1
 while True:
 if i<1:
 break
 #如果相邻分割点对应的值相同,则删除一个
 if theFeature[alist[i]] == theFeature[alist[i-1]]:
 del alist[i-1]
 i = i - 1
 return alist

#获取列表中临近分割区间的最小合并信息熵
def GetMinSplitPoint(List, theLabel, MinEntropy, TipLabel):
 MinPoint = 0
 TheEntropy = GetEntropyOfCertainPoint(theLabel, 0, List[1], 1, TipLabel)
 for i in range(1, len(List)-1):
 iEntropy = GetEntropyOfCertainPoint(theLabel, List[i-1], List[i+1], List[i-1] + 1, TipLabel)
 if iEntropy < TheEntropy:
 MinPoint = i
 TheEntropy = iEntropy
 iEntropy = GetEntropyOfCertainPoint(theLabel, List[-2], len(theLabel) - 1, List[-2] + 1, TipLabel)
 if iEntropy < TheEntropy:
 MinPoint = len(List) - 1
 TheEntropy = iEntropy
 MinEntropy[0] = TheEntropy
 return MinPoint

#合并信息熵小于阈值的区间InfoThreshold = 0.5,MinGroup = 20
def CombineResult(List, theLabel, TipLabel):
 MinEntropy = [0]
 while True:
 if len(List) <= MinGroup:
 break
 MinPoint = GetMinSplitPoint(List, theLabel, MinEntropy, TipLabel)
 if MinEntropy[0] >= InfoThreshold and len(List) < MaxGroup:
 break
 else:
 del List[MinPoint]
 print "结束合并,信息熵= %f" % MinEntropy[0]
 return List

#逐列检查,判断离散点(用于分割离散区间)
SaveAllColDisPoint = []
for i in range(FeatureNum):
 newlist = [-100000]
 SaveAllColDisPoint.append(newlist)
for aCol in NeedDisCol:
 theFeature = getCertainCol(TrainSet, aCol) #深度拷贝
 theLabel = getCertainCol(LabelSet, 0)
 TipLabel = [0]
 theDisPointList = DisACol(theFeature , theLabel, TipLabel)
 TipLabel = TipLabel[0]
 theDisPointList = DelEqual(theDisPointList, theFeature)
 theDisPointList = CombineResult(theDisPointList, theLabel, TipLabel)
 theDisNumList = GetDisNumFromPoint(theDisPointList, theFeature)
 SaveAllColDisPoint[aCol] = theDisNumList
 print SaveAllColDisPoint[aCol]
 print "判断%d列完成" % aCol

#由列表获取写入行
count = 1
def getLineFromList(aItem, count):
 theLine = 'X' + str(count) + ','
 for num in aItem:
 theLine = theLine + str(num) + ','
 return theLine[:-1] + '\n'

for aItem in SaveAllColDisPoint:
 fDis.write(getLineFromList(aItem, count))
 count = count + 1

fTrain.close()
fDis.close()
fLabel.close()

通过自然划分分段

3-4-5 规则可以用于将数值数据划分成相对一致、“自然的”区间。一般地,该规则根据最重要的数字上的值区域,递归地、逐层地将给定的数据区域划分为3、4 或5 个等长的区间。该规则如下:
如果一个区间在最重要的数字上包含3、6、7 或9 个不同的值,则将该区间划分成3 个区间(对于3、6 和9,划分成3 个等宽的区间;而对于7,按2-3-2 分组,划分成3 个区间);
如果它在最重要的数字上包含2、4 或8 个不同的值,则将区间划分成4 个等宽的区间;
如果它在最重要的数字上包含1、5 或10 个不同的值,则将区间划分成5 个等宽的区间。
该规则可以递归地用于每个区间,为给定的数值属性创建概念分层。由于在数据集中可能有特别大的正值和负值,最高层分段简单地按最小和最大值可能导致扭曲的结果。例如,在资产数据集中,少数人的资产可能比其他人高几个数量级。按照最高资产值分段可能导致高度倾斜的分层。这样,顶层分段可以根据代表给定数据大多数的数据区间(例如,第5 个百分位数到第95 个百分位数)进行。越出顶层分段的特别高和特别低的值将用类似的方法形成单独的区间。此处类似于分位数划分区间。

你可能感兴趣的:(机器学习,数据挖掘)