参考:机器学习实战Peter Harrington
十、利用k-均值聚类算法对未标注的数据分组
聚类是一种无监督学习,它将相似的对象归到同一个簇中。簇内对象越相似,聚类的效果越好。
→簇识别:假定有一些数据,现在将相似数据归到一起,簇识别会告诉我们这些簇到底是什么。
聚类与分类的最大不同在于,分类的目标事先已知,而聚类则不同,虽然其产生的结果与分类相同,但类别没有预先定义。
(1)K-均值算法:是发现给定的数据集的K个簇的算法,簇的个数K是用户给定的,每一个簇通过其质心来描述。首先,随机确定K个初始点为质心,然后为每个点找距其最近的质心,并将其分给该质心所对应的簇。这一步完成后,每个簇的质心更新为该簇所有点的平均值并将均值作为质心。
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢
使用数据类型:数值型数据
示例1:
#-*- coding:utf-8 -*-
import numpy as np
def loadDataSet(fileName):
dataMat = []
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float,curLine)) #转化为float类型
dataMat.append(fltLine)
return dataMat
def distEclud(vecA,vecB): #计算两个向量的欧氏距离
return np.sqrt(sum(np.power(vecA - vecB,2)))
def randCent(dataSet,k): #构建一个包含K个随机质心的集合
n = np.shape(dataSet)[1]
centroids = np.mat(np.zeros((k,n)))
for j in range(n): #生成0到1.0之间的随机数并通过取值范围和最小值,以确保随机质心在数据集的边界之内
minJ = min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j]) - minJ)
centroids[:,j] = minJ + rangeJ * np.random.rand(k,1)
return centroids
def kMeans(dataSet,k,distMeas = distEclud,createCent = randCent):
m = np.shape(dataSet)[0] #确定数据集中数据点的总数
clusterAssment = np.mat(np.zeros((m,2))) #创建一个矩阵来存储每个点的簇分配结果,一列记录簇索引值,一列存储误差(点到簇质心的距离)
centroids = createCent(dataSet,k)
clusterChanged = True #标志变量,若为true,则继续迭代
while clusterChanged:
clusterChanged = False
for i in range(m): #遍历所有数据找到距离每个点最近的质心
minDist = float('inf');minIndex = -1
for j in range(k):
disJI = distMeas(centroids[j,:],dataSet[i,:]) #计算距离
if disJI < minDist:
minDist = disJI;minIndex = j
if clusterAssment[i,:] != minIndex: clusterChanged = True
clusterAssment[i,:] = minIndex,minDist ** 2
print(centroids)
for cent in range(k):
ptsInClust = dataSet[np.nonezero(clusterAssment[:,0].A==cent)[0]] #通过数组过滤来获得给定簇的所有点
centroids[cent,:] = np.mean(ptsInClust,axis = 0) #计算所有点的均值,axis=0表示沿矩阵的列方向进行均值计算
return centroids,clusterAssment
使用后处理来提高聚类性能:在K-均值聚类中簇的数目是一个用户预先定义的参数,如何才能知道K的选择是否能让生成的簇比较好呢?我们可以利用误差(在包含簇分配结果的矩阵中保存着每个点的误差,即该点到簇质心的距离平方值)来评价聚类质量 。
一种用于度量聚类效果的指标是SSE(误差平方和),SSE越小表示数据点越接近于他们的质心,聚类效果也越好。因为对误差取了平方,因此更重视那些远离中心的点。
改进的方法:可以对生成的簇进行后处理,。将具有最大的SSE值的簇划分为两个簇,具体实现时可以将最大簇包含的点过滤出来并在这些点上运行K-均值算法。
(2)二分K-均值算法
为了克服K-均值算法收敛于局部最小值的问题,有人提出了二分K-均值算法。
该方法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分(选择哪一个簇进行划分取决于对其划分是否可以最大程度降低SSE的值)。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。
另一种做法是,选择SSE最大的簇进行划分,直到得到用户指定的簇数目为止。
示例2:
#-*- coding:utf-8 -*-
import numpy as np
def loadDataSet(fileName):
dataMat = []
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = list(map(float,curLine)) #转化为float类型
dataMat.append(fltLine)
return dataMat
def distEclud(vecA,vecB): #计算两个向量的欧氏距离
return np.sqrt(sum(np.power(vecA - vecB,2)))
def randCent(dataSet,k): #构建一个包含K个随机质心的集合
n = np.shape(dataSet)[1]
centroids = np.mat(np.zeros((k,n)))
for j in range(n): #生成0到1.0之间的随机数并通过取值范围和最小值,以确保随机质心在数据集的边界之内
minJ = min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j]) - minJ)
centroids[:,j] = minJ + rangeJ * np.random.rand(k,1)
return centroids
def kMeans(dataSet,k,distMeas = distEclud,createCent = randCent):
m = np.shape(dataSet)[0] #确定数据集中数据点的总数
clusterAssment = np.mat(np.zeros((m,2))) #创建一个矩阵来存储每个点的簇分配结果,一列记录簇索引值,一列存储误差(点到簇质心的距离)
centroids = createCent(dataSet,k)
clusterChanged = True #标志变量,若为true,则继续迭代
while clusterChanged:
clusterChanged = False
for i in range(m): #遍历所有数据找到距离每个点最近的质心
minDist = float('inf');minIndex = -1
for j in range(k):
disJI = distMeas(centroids[j,:],dataSet[i,:]) #计算距离
if disJI < minDist:
minDist = disJI;minIndex = j
if clusterAssment[i,:] != minIndex: clusterChanged = True
clusterAssment[i,:] = minIndex,minDist ** 2
print(centroids)
for cent in range(k):
ptsInClust = dataSet[np.nonezero(clusterAssment[:,0].A==cent)[0]] #通过数组过滤来获得给定簇的所有点
centroids[cent,:] = np.mean(ptsInClust,axis = 0) #计算所有点的均值,axis=0表示沿矩阵的列方向进行均值计算
return centroids,clusterAssment
def biKmeans(dataSet,k,distMeas = distEclud):
m = np.shape(dataSet)[0]
clusterAssment = np.mat(np.zeros((m,2))) #创建一个矩阵来存储数据集中每个点的簇分配结果及平方误差
centroid0 = np.mean(dataSet,axis = 0).tolist()[0] #计算整个数据集的质心
centList = [centroid0] #使用一个列表保留所有的质心
for j in range(m):
clusterAssment[j,1] = distMeas(np.mat(centroid0),dataSet[j,:]) ** 2
while (len(centList) < k):
lowestSSE = float('inf') #一开始将SSE设置为无穷大
for i in range(len(centList)): #遍历列表centList中的每一个簇
ptsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0].A == i)[0],:] #对每个簇,将该簇中的所有点看成一个小的数据集ptsInCurrCluster
centroidMat,splitClustAss = kMeans(ptsInCurrCluster,2,distMeas) #将ptsInCurrCluster输入到函数kMeans()中处理(k=2),算法会生成2个质心(簇),同时给出每个簇的误差值
sseSplit = sum(splitClustAss[:,1])
sseNotSplit = sum(clusterAssment[np.nonzero(clusterAssment[:,0].A != i)[0],1])
print("sseSplit,and notSplit:",sseSplit,sseNotSplit)
if (sseSplit + sseNotSplit) < lowestSSE: #如果该划分的SSE值小,则本次划分被保存,
bestCentToSplit = i
bestNewCents = centroidMat
bestClustAss = sseSplit
lowestSSE = sseSplit + sseNotSplit
#新的簇分配结果更新
bestClustAss[np.nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)
bestClustAss[np.nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
print('the bestCentToSplit is:',bestCentToSplit)
print('the len of bestClustAss is:',len(bestClustAss))
centList[bestCentToSplit] = bestNewCents[0,:] #更新后,新的质心会被添加到centList中
centList.append(bestNewCents[1,:])
clusterAssment[np.nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:] = bestClustAss
return np.mat(centList),clusterAssment
示例3:对地图上的点进行聚类
示例4:对地理坐标进行聚类
十一、使用Apriori算法进行关联分析
从大规模数据集中寻找物品间的隐含关系被称作关联性分析或关联规则学习。寻找物品的不同组合是一项很耗时的任务,蛮力搜索不能解决该问题,所以需要更智能的方法在合理的时间范围内找到频繁项集。本章将介绍如何使用Apriori算法解决上述问题。
(1)关联分析:从大规模数据集中寻找物品间的隐含关系被称作关联性分析或关联规则学习。这种关系可以有两种形式:频繁项集或关联规则。频繁项集是经常出现在一块的物品的集合,关联规则暗示两种物品之间可能存在很强的关系。
当寻找频繁项集的时候,频繁的定义中,最重要的是支持度和可信度。
①一个项集的支持度被定义为数据集中包含该项集的记录所占的比例。支持度是针对项集来说的,因此可以定义一个最小支持度,只保留满足最小支持度的项集。在5条交易记录中有3条包含{豆奶,尿布},因此{豆奶,尿布}的支持度为3/5。
②可信度或置信度是针对诸如{尿布}→{葡萄酒}的关联规则来定义的。这条规则的可信度被定义为“支持度({尿布,葡萄酒})/支持度({尿布})"
支持度和可信度是用来量化关联分析是否成功的方法,而生成一个物品所有可能的清单,然后对每一种组合统计他出现的频繁程度将会有很大的计算量。而Apriori原理会减少关联学习所需的计算量。
(2)Apriori原理:如果说某个项集是频繁的,那么他的所有子集也是频繁的。同样如果一个项集是非频繁集,那么它的所有超集也是非频繁的。
已知{2,3}是非频繁的,即可以知道{0,2,3},{1,2,3},{0,1,2,3}也是非频繁的。
Apriori算法的两个输入参数分别是最小支持度和数据集,该算法首先会生成所有单个物品的项集列表,接着查看交易记录查看哪些项集满足最小支持度的要求,去除不满足最小支持度的部分。然后对剩下的集合进行组合以生成包含两个元素的项集,去除不满足最小支持度的项集。该过程重复到直到所有项集都被去掉。
首先构建集合C1,C1是大小为1的所有候选集的集合,然后扫描数据集来判断这些只有一个元素的项集是否满足最小支持度,这些满足的构成C1,然后L1中的元素互相构成C2,C2再进一步过滤为L2。
#-*- coding:utf-8 -*-
import numpy as np
def loadDataSet():
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]
def createC1(dataSet):
C1 = [] #C1是大小为1的所有候选项集的集合
for transaction in dataSet: #遍历数据集中所有交易记录
for item in transaction: #遍历记录中的每一个项
if not [item] in C1: #C1存储不重复值,如果该物品项没有在C1中出现则添加到C1中
C1.append([item])
C1.sort()
return np.map(frozenset,C1) #用frozenset将这些集合作为字典键值使用
def scanD(D,Ck,minSupport): #三个参数分别为数据集、候选项集列表、最小支持度,该函数用于从C1生成L1
ssCnt = {} #创建一个空字典
for tid in D: #遍历数据集中所有交易记录
for can in Ck: #遍历Ck中所有候选集
if can.issubset(tid): #如果C1中的集合是记录的一部分,那么增加字典中对应的计数值
if not ssCnt.has_key(can):ssCnt[can] = 1
else:ssCnt[can] += 1
numItems = float(len(D))
retList = [] #创建一个空列表,如果支持度满足最小支持度的要求,则将字典元素添加到retList中
supportData = {} #最频繁项集的支持度
for key in ssCnt:
support = ssCnt[key]/numItems #计算支持度
if support >= minSupport:
retList.insert(0,key) #满足支持度最小要求则添加retList中,在首部插入
supportData[key] = support
return retList,supportData
示例1:完整的Apriori算法
#-*- coding:utf-8 -*-
import numpy as np
def loadDataSet():
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]
def createC1(dataSet):
C1 = [] #C1是大小为1的所有候选项集的集合
for transaction in dataSet: #遍历数据集中所有交易记录
for item in transaction: #遍历记录中的每一个项
if not [item] in C1: #C1存储不重复值,如果该物品项没有在C1中出现则添加到C1中
C1.append([item])
C1.sort()
return np.map(frozenset,C1) #用frozenset将这些集合作为字典键值使用
def scanD(D,Ck,minSupport): #三个参数分别为数据集、候选项集列表、最小支持度,该函数用于从C1生成L1
ssCnt = {} #创建一个空字典
for tid in D: #遍历数据集中所有交易记录
for can in Ck: #遍历Ck中所有候选集
if can.issubset(tid): #如果C1中的集合是记录的一部分,那么增加字典中对应的计数值
if not ssCnt.has_key(can):ssCnt[can] = 1
else:ssCnt[can] += 1
numItems = float(len(D))
retList = [] #创建一个空列表,如果支持度满足最小支持度的要求,则将字典元素添加到retList中
supportData = {} #最频繁项集的支持度
for key in ssCnt:
support = ssCnt[key]/numItems #计算支持度
if support >= minSupport:
retList.insert(0,key) #满足支持度最小要求则添加retList中,在首部插入
supportData[key] = support
return retList,supportData
def aprioriGen(Lk,k): #输入参数为频繁项集列表、输出为Ck
retList = []
lenLk = len(Lk) #计算Lk的元素数目
for i in range(lenLk): #比较Lk中的每一个元素与其他元素
for j in range(i + 1,lenLk):
L1 = list(Lk[i])[:k - 2]; L2 = list(Lk[j])[:k - 2] #取列表中两个集合进行比较,如果这两个集合的前k-2个元素都相等,则将这两个集合合成一个大小为k的集合
L1.sort();L2.sort()
if L1 == L2:
retList.append(Lk[i] | Lk[j]) #将这两个集合合成一个大小为k的集合
return retList
def apriori(dataSet,minSupport = 0.5): #上述操作都封装在apriori()中,给该函数传递一个数据集和一个支持度
C1 = createC1(dataSet)
D = np.map(set,dataSet)
L1,supportData = scanD(D,C1,minSupport)
L = [L1]
k = 2
while (len(L[k - 2]) > 0):
Ck = aprioriGen(L[k - 2],k)
Lk,supK = scanD(D,Ck,minSupport)
supportData.update(supK)
L.append(Lk)
k += 1
return L,supportData
示例2:从频繁项集中挖掘关联规则
前一节已经计算了所有频繁项集支持度,现在想获得可信度,只需取出那些支持度做一次除法运算。同理,如果某条规则不满足最小置信度的要求,那该规则的所有子集也不会满足要求。假设规则0,1,2→3不满足最小可信度要求,那么就知道任何左部为{0,1,2}子集的规则也不会满足最小可信度要求。因此可以利用关联规则的上述性质属性来减少需要测试的规则数目。
首先从一个频繁项集开始,接着创建一个规则列表,其中规则右部只包含一个元素,然后对这些规则进行测试,接下来合并所有剩余规则来创建一个新的规则列表,其中规则右部包含两个元素。
#-*- coding:utf-8 -*-
import numpy as np
def loadDataSet():
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]
def createC1(dataSet):
C1 = [] #C1是大小为1的所有候选项集的集合
for transaction in dataSet: #遍历数据集中所有交易记录
for item in transaction: #遍历记录中的每一个项
if not [item] in C1: #C1存储不重复值,如果该物品项没有在C1中出现则添加到C1中
C1.append([item])
C1.sort()
return np.map(frozenset,C1) #用frozenset将这些集合作为字典键值使用
def scanD(D,Ck,minSupport): #三个参数分别为数据集、候选项集列表、最小支持度,该函数用于从C1生成L1
ssCnt = {} #创建一个空字典
for tid in D: #遍历数据集中所有交易记录
for can in Ck: #遍历Ck中所有候选集
if can.issubset(tid): #如果C1中的集合是记录的一部分,那么增加字典中对应的计数值
if not ssCnt.has_key(can):ssCnt[can] = 1
else:ssCnt[can] += 1
numItems = float(len(D))
retList = [] #创建一个空列表,如果支持度满足最小支持度的要求,则将字典元素添加到retList中
supportData = {} #最频繁项集的支持度
for key in ssCnt:
support = ssCnt[key]/numItems #计算支持度
if support >= minSupport:
retList.insert(0,key) #满足支持度最小要求则添加retList中,在首部插入
supportData[key] = support
return retList,supportData
def aprioriGen(Lk,k): #输入参数为频繁项集列表、输出为Ck
retList = []
lenLk = len(Lk) #计算Lk的元素数目
for i in range(lenLk): #比较Lk中的每一个元素与其他元素
for j in range(i + 1,lenLk):
L1 = list(Lk[i])[:k - 2]; L2 = list(Lk[j])[:k - 2] #取列表中两个集合进行比较,如果这两个集合的前k-2个元素都相等,则将这两个集合合成一个大小为k的集合
L1.sort();L2.sort()
if L1 == L2:
retList.append(Lk[i] | Lk[j]) #将这两个集合合成一个大小为k的集合
return retList
def apriori(dataSet,minSupport = 0.5): #上述操作都封装在apriori()中,给该函数传递一个数据集和一个支持度
C1 = createC1(dataSet)
D = np.map(set,dataSet)
L1,supportData = scanD(D,C1,minSupport)
L = [L1]
k = 2
while (len(L[k - 2]) > 0):
Ck = aprioriGen(L[k - 2],k)
Lk,supK = scanD(D,Ck,minSupport)
supportData.update(supK)
L.append(Lk)
k += 1
return L,supportData
def generateRules(L,supportData,minConf = 0.7): #三个参数分别是频繁项集列表、包含那些频繁项集支持数据的字典、最小可信度阈值
bigRuleList = [] #要存放一个包含可信度的规则列表存放在该列表中
for i in range(1,len(L)): #遍历函数中的每一个频繁项集
for freqSet in L[i]:
H1 = [frozenset([item]) for item in freqSet] #对每个频繁项集创建只含单个元素集合的列表H1
if (i > 1):
rulesFromConseq(freqSet,H1,supportData,bigRuleList,minConf)
else:
calcConf(freqSet,H1,supportData,bigRuleList,minConf)
return bigRuleList
def calcConf(freqSet,H,supportData,brl,minConf = 0.7): #用于对规则进行评估
prunedH = [] #用于保存一个满足最小可信度要求的规则列表
for conseq in H: #遍历H中的所有项集并计算它们的可信度
conf = supportData[freqSet]/supportData[freqSet - conseq]
if conf >= minConf:
print(freqSet - conseq,'--->',conseq,conf) #满足则输出
prunedH.append(conseq)
return prunedH
def rulesFromConseq(freqSet,H,supportData,brl,minConf = 0.7): #为最初的项集生成更多的关联规则,参数为频繁项集、可以出现在规则右部的元素列表H
m = len(H[0])
if (len(freqSet) > (m + 1)): # 接下来查看该频繁项集是否大到可以移除大小为m的子集,如果可以则移除
Hmp1 = aprioriGen(H, m + 1) # 使用aprioriGen函数来生成H中元素的无重复组合,也是下一次迭代的H列表
Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf) # 用calcConf()来测试他们的可信度是否满足要求
if (len(Hmp1) > 1): # 如果不止一条满足,则调用函数rulesFromConseq()来判断是否可以进一步合并
rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)
示例3:发现国会投票中的模式
示例4:发现毒蘑菇的相似特征
十二、使用FP-growth算法来高效发现频繁项集
FP-growth算法:首先构建FP树,然后利用它来挖据频繁项集。为构建FP树,需要对原始数据集扫描两遍。第一次对所有元素项的出现次数进行计数。同上章,若某元素是不频繁的,那么包含该元素的超集也是不频繁的,即不需要考虑这些超集,第二遍扫描只考虑那些频繁元素。
优点:一般要快于Apriori
缺点:实现比较困难,在某些数据集上性能会下降
适用数据类型:标称型数据
创建FP树的数据结构:
#-*- coding:utf-8 -*-
import numpy as np
class treeNode: #创建一个类来保存树的每一个节点
def __init__(self,nameValue,numOccur,parentNode):
self.name = nameValue
self.count = numOccur
self.nodeLink = None
self.parent = parentNode
self.children = {}
def inc(self,numOccur):
self.count += numOccur
def disp(self,ind = 1):
print(''*ind,self.name,'',self.count)
for child in self.children.values():
child.disp(ind + 1)
构建FP树:
除了上图给出的FP树之外,还需要一个头指针来指向给定类型的第一个示例,利用头指针表,可以快速访问FP树中一个给定类型的所有元素。这里使用一个字典作为数据结构,来保存头指针列表,除了存放指针外,头指针表还可以用来保存FP树中每类元素的总数。
第一次遍历数据集会获得每个元素的出现频率,然后去掉不满足最小支持度的元素项。再下一步构建FP树,构建时读入每个项集并将其添加到一条已经存在的路径中,如果该路径不存在则创建一条新路径。每个事务是一个无序集合,在FP中相同项只表示一次,所以在将集合添加到树之前需要对每个集合进行排序。
对事务记录过滤排序之后,就可以构建FP树了,从空集符号开始,向其中不断添加频繁项集。将事务依次添加到树中,如果树中已存在现有元素,则增加现有元素的值;如果不存在,则向树添加一个分支。
#-*- coding:utf-8 -*-
import numpy as np
class treeNode: #创建一个类来保存树的每一个节点
def __init__(self,nameValue,numOccur,parentNode):
self.name = nameValue
self.count = numOccur
self.nodeLink = None
self.parent = parentNode
self.children = {}
def inc(self,numOccur):
self.count += numOccur
def disp(self,ind = 1):
print(''*ind,self.name,'',self.count)
for child in self.children.values():
child.disp(ind + 1)
def createTree(dataSet,minSup = 1): #参数为数据集、最小支持度
headerTable = {}
for trans in dataSet:
for item in trans:
headerTable[item] = headerTable.get(item,0) + dataSet[trans] #遍历数据集并统计每个元素项出现的频度,存储在头指针表中
for k in headerTable.keys(): #扫描头指针表,删除那些不满足最小支持度的项
if headerTable[k] < minSup:
del(headerTable[k])
freqItemSet = set(headerTable.keys())
if len(freqItemSet) == 0: return None,None #如果没有元素项满足要求,则退出
for k in headerTable:
headerTable[k] = [headerTable[k],None]
retTree = treeNode('Null Set',1,None)
for tranSet,count in dataSet.items(): #根据全局频率对每个事务中的元素进行排序
localD = {}
for item in freqItemSet:
localD[item] = headerTable[item][0]
if len(localD) > 0:
orderedItems = [v[0] for v in sorted(localD.items(),key = lambda p : p[1],reverse = True)]
updateTree(orderedItems,retTree,headerTable,count) #使用排序后的频率项集对树进行填充
return retTree,headerTable
def updateTree(items,inTree,headerTable,count): #输入参数为一个项集
if items[0] in inTree.children: #测试事务中的第一个元素项是否作为子节点存在
inTree.children[items[0]].inc(count) #存在则更新该元素项的计数
else:
inTree.children[items[0]] = treeNode(items[0],count,inTree) #不存在则创建一个新的treeNode并将其作为一个子节点添加到树中
if headerTable[items[0]][1] == None: #此时头指针表也要更新指向新的节点
headerTable[items[0]][1] = inTree.children(items[0])
else:
updateHeader(headerTable[items[0]][1],inTree.children[items[0]])
if len(items) > 1:
updateTree(items[1::],inTree.children[items[0]],headerTable,count)
def updateHeader(nodeToTest,targetNode): #确保节点链接指向树中该元素项的每一个实例,从头指针表的nodeLink开始到链表末尾
while (nodeToTest.nodeLink != None):
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
def loadSimpDat():
simpDat = [['r','z','h','j','p'],['z','y','x','w','v','u','t','s'],['z'],['r','x','n','o','s'],['y','r','x','z','q','t','p'],['y','z','x','e','q','s','t','m']]
return simpDat
def createInitSet(dataSet):
retDict = {}
for trans in dataSet:
retDict[frozenset(trans)] = 1
return retDict
从一棵树中挖掘频繁项集:
从FP树中抽取频繁项集的三个基本步骤如下:①从FP树中获得条件模式基②利用条件模式基,构建一个条件FP树③迭代重复步骤①②,直到树包含一个元素为止。
首先从已经保存在头指针表中的单个频繁元素项开始,对于每一个元素项,获得其对应的条件模式基,条件模式基是以查找元素项为结尾的路径集合。每一条路径都是一条前缀路径,一条前缀路径是介于所查找元素项与树根节点之间的所有内容。每一条前缀路径都与一个计数值关联,该计数值等于起始元素项的计数值。
前缀路径将被用于构建条件FP树,可以利用先前创建的头指针表,头指针表包含相同类型元素链表的起始指针。一旦到达了每一个元素项,就可以上溯这棵树直到根节点为止。
#-*- coding:utf-8 -*-
import numpy as np
class treeNode: #创建一个类来保存树的每一个节点
def __init__(self,nameValue,numOccur,parentNode):
self.name = nameValue
self.count = numOccur
self.nodeLink = None
self.parent = parentNode
self.children = {}
def inc(self,numOccur):
self.count += numOccur
def disp(self,ind = 1):
print(''*ind,self.name,'',self.count)
for child in self.children.values():
child.disp(ind + 1)
def createTree(dataSet,minSup = 1): #参数为数据集、最小支持度
headerTable = {}
for trans in dataSet:
for item in trans:
headerTable[item] = headerTable.get(item,0) + dataSet[trans] #遍历数据集并统计每个元素项出现的频度,存储在头指针表中
for k in headerTable.keys(): #扫描头指针表,删除那些不满足最小支持度的项
if headerTable[k] < minSup:
del(headerTable[k])
freqItemSet = set(headerTable.keys())
if len(freqItemSet) == 0: return None,None #如果没有元素项满足要求,则退出
for k in headerTable:
headerTable[k] = [headerTable[k],None]
retTree = treeNode('Null Set',1,None)
for tranSet,count in dataSet.items(): #根据全局频率对每个事务中的元素进行排序
localD = {}
for item in freqItemSet:
localD[item] = headerTable[item][0]
if len(localD) > 0:
orderedItems = [v[0] for v in sorted(localD.items(),key = lambda p : p[1],reverse = True)]
updateTree(orderedItems,retTree,headerTable,count) #使用排序后的频率项集对树进行填充
return retTree,headerTable
def updateTree(items,inTree,headerTable,count): #输入参数为一个项集
if items[0] in inTree.children: #测试事务中的第一个元素项是否作为子节点存在
inTree.children[items[0]].inc(count) #存在则更新该元素项的计数
else:
inTree.children[items[0]] = treeNode(items[0],count,inTree) #不存在则创建一个新的treeNode并将其作为一个子节点添加到树中
if headerTable[items[0]][1] == None: #此时头指针表也要更新指向新的节点
headerTable[items[0]][1] = inTree.children(items[0])
else:
updateHeader(headerTable[items[0]][1],inTree.children[items[0]])
if len(items) > 1:
updateTree(items[1::],inTree.children[items[0]],headerTable,count)
def updateHeader(nodeToTest,targetNode): #确保节点链接指向树中该元素项的每一个实例,从头指针表的nodeLink开始到链表末尾
while (nodeToTest.nodeLink != None):
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
def loadSimpDat():
simpDat = [['r','z','h','j','p'],['z','y','x','w','v','u','t','s'],['z'],['r','x','n','o','s'],['y','r','x','z','q','t','p'],['y','z','x','e','q','s','t','m']]
return simpDat
def createInitSet(dataSet):
retDict = {}
for trans in dataSet:
retDict[frozenset(trans)] = 1
return retDict
#发现以给定元素项结尾的生成一个条件模式基
def ascendTree(leafNode,prefixPath): #迭代上溯整棵树
if leafNode.parent != None:
prefixPath.append(leafNode.name)
ascendTree(leafNode.parent,prefixPath)
def findPrefixPath(basePat,treeNode): #遍历链表直到到达结尾
condPats = {}
while treeNode != None:
prefixPath = []
ascendTree(treeNode,prefixPath) #每遇到一个元素项都会调用ascendTree()来上溯FP树,并收集所有遇到的元素项的名称
if len(prefixPath) > 1:
condPats[frozenset(prefixPath[1:])] = treeNode.count
treeNode = treeNode.nodeLink
return condPats #返回之后添加到条件模式基字典conPats
def mineTree(inTree,headerTable,minSup,preFix,freqItemList): #递归查找频繁项集
bigL = [v[0] for v in sorted(headerTable.items(),key = lambda p : p[1])] #首先对头指针表中的元素项按其出现频率进行排序
for basePat in bigL:
newFreqSet = preFix.copy()
newFreqSet.add(basePat)
freqItemList.append(newFreqSet) #将每一个频繁项添加到频繁项集列表freqItemList中
condPattBases = findPrefixPath(basePat,headerTable[basePat][1]) #调用findPrefixPath()创建条件基
myCondTree,myHead = createTree(condPattBases,minSup) #该条件基被当成一个新数据集输送给createTree()函数
if myHead != None:
mineTree(myCondTree,myHead,minSup,newFreqSet,freqItemList) #如果树中有元素项,递归调用mineTree()函数
示例1:在Twitter源中发现一些共现词
示例2:从新闻网站点击流中挖掘