Python 实现 FP-growth 的学习
1 FP-growth
FP-growth只需要对数据扫描两次,而Apriori需要对每个潜在的频繁项都会扫描一次数据,因此Apriori在大数据集中比较慢,而FP-growth速度较快
分为两步骤:1.将数据集构建为FP数,2.从FP树中挖掘频繁项
2 FP树的定义
FP树定义:
'''
FP树的定义
addnum 增加项出现次数的函数
display 显示FP树的结构
'''
class treeNode:
def __init__(self,name,num,parent):
self.name=name #项的名称
self.num=num #项出现的次数
self.parent=parent #项的父节点
self.child={} #项的子节点
self.link=None #项关联的节点,即与该项名称相同的节点
def addnum(self,num):
self.num+=num
def display(self,ind=1):
print(' '*ind,self.name,' ',self.num)
for child in self.child.values():
child.display(ind+1)
原始数据如下:
对数据中出现的元素项进行扫描,获取每个元素的支持度
剔除其中支持度小于最小支持度的元素
最后,根据过滤之后元素的支持度,对事务进行重排序,支持度大的元素在前
然后根据重排后的事务,不断扩充FP树,过程如下:
将所有数据添加进入FP树:
FP树还需要一个头指针表来指向给定类型的第一个实例。利用头指针表,可快速访问FP树中一个给定类型的所有元素
并且在同一元素用 link指针连接
3 FP树的构建
'''
原始数据集
'''
def loaddata():
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']]
dataset={}
for data in simpDat:
dataset[frozenset(data)]=1
return dataset
'''
根据数据集创建FP树
updatetree 用来将数据添加到FP树中
'''
def createtree(dataset,minsup=1):
head={} #构建头指针表
for data,num in dataset.items():
for item in data:
head[item]=head.get(item,0)+num
a=list(head.keys()).copy()
for k in a:
if head[k]<minsup:
del(head[k])
freqset=set(head.keys()) #获得频繁项
if len(freqset)==0:
return None,None
for key in head:
head[key]=[head[key],None] #为指针留下存储空间
mytree=treeNode('tree head',0,None) #定义FP树,根节点
for data,num in dataset.items():
tmpdata={} #对数据进行过滤,只留下频繁项
for item in data:
if item in freqset:
tmpdata[item]=head[item][0]
if len(tmpdata)>0:
order=[v[0] for v in sorted(tmpdata.items(),key=lambda p : p[1],reverse=True)]
#根据支持度,对过滤后的数据的元素进行排序
updatetree(order,mytree,head,num)
mytree.display() #显示FP树
return mytree,head
'''
更新FP树
'''
def updatetree(item,intree,head,num):
if item[0] in intree.child: #如果根节点的子节点中包含数据第一个元素,直接将该子节点的数量加一
intree.child[item[0]].addnum(num)
else: #如果没有,则新创建一个子树,以该数据第一个元素作为根节点
intree.child[item[0]]=treeNode(item[0],num,intree)
if head[item[0]][1]==None: #确定头指针表中该元素,是否已经存在指针
head[item[0]][1]=intree.child[item[0]] #如果没有相连,定义好指针
else: #如果存在指针,证明该元素已经在FP树出现过,通过链表link找到最后一个出现该元素,并将最后元素与新出现元素相连
cur=head[item[0]][1]
while cur.link != None:
cur=cur.link
cur.link=intree.child[item[0]]
if len(item)>1: #当数据的元素大于1,则对后面元素进行重复操作
updatetree(item[1:],intree.child[item[0]],head,num)
测试
if __name__=='__main__':
dataset=loaddata()
mytree,head=createtree(dataset,3)
结果
tree head 0
z 5
r 1
x 3
t 3
y 3
s 2
r 1
x 1
s 1
r 1
4 从FP树中挖掘频繁项
过程:
从FP树中获取条件模式基
个根据条件模式基构建条件FP树
重复以上过程,直到树包含一个元素为止
从FP树中提取,头指针表中每个元素的前缀路径,通过指针找到FP树中对应项,然后向上传递直到找到根节点,并将对应项的数量作为前缀的数量
确定频繁项的前缀
扫描二维码关注公众号,回复:
1889107 查看本文章
![](/qrcode.jpg)
'''
根据FP树,和头指针表,确定头指针表元素的前缀
'''
def findpath(tree,head):
result={}
for key in head:
curlink=head[key][1] #将指针移动到第一个对应元素
tmp={} #创建字典存储前缀
while curlink != None:
cur=curlink
num=cur.num
path=[]
while cur.parent.name !='tree head':
cur=cur.parent
path.append(cur.name)
if len(path)!=0:
tmp[frozenset(path)]=num
curlink=curlink.link
result[key]=tmp
return result
构建条件FP树,并挖掘频繁项
'''
根据频繁项的前缀确定所有频繁项
'''
def minetree(intree,head,minsup,pre,freqlist,ind):
path=findpath(intree,head)
#获得频繁项的前缀
bigl=[v[0] for v in sorted(head.items(),key=lambda p:p[1][0])]
#根据支持度对头指针表排序,从小到大
for base in bigl:
#将已经用过的前缀复制,并加入当前头指针表元素,构建新频繁项
newpre=pre.copy()
newpre.add(base)
freqlist.append(newpre)
#将前缀作为数据,构建FP树,获得新的头指针表
basetree,basehead=createtree(path[base],minsup)
if basehead!=None:
#递归调用 直到无法再构建条件FP树
minetree(basetree,basehead,minsup,newpre,freqlist,ind+1)
测试
dataset=loaddata()
mytree,head=createtree(dataset,3)
freqlist=[]
pre=set([])
ind=1
minetree(mytree,head,3,pre,freqlist,ind)
for item in freqlist:
print(item)
print(len(freqlist))
结果
tree head 0
z 5
r 1
x 3
t 3
y 3
s 2
r 1
x 1
s 1
r 1
tree head 0
z 3
x 3
tree head 0
z 3
tree head 0
x 3
z 3
t 3
tree head 0
x 3
tree head 0
z 3
x 3
tree head 0
z 3
tree head 0
x 3
tree head 0
z 3
{'r'}
{'t'}
{'z', 't'}
{'x', 't'}
{'x', 'z', 't'}
{'y'}
{'x', 'y'}
{'z', 'y'}
{'z', 'x', 'y'}
{'t', 'y'}
{'z', 't', 'y'}
{'x', 't', 'y'}
{'x', 'z', 't', 'y'}
{'s'}
{'s', 'x'}
{'x'}
{'z', 'x'}
{'z'}
18
完整代码
'''
FP树的定义
addnum 增加项出现次数的函数
display 显示FP树的结构
'''
class treeNode:
def __init__(self,name,num,parent):
self.name=name #项的名称
self.num=num #项出现的次数
self.parent=parent #项的父节点
self.child={} #项的子节点
self.link=None #项关联的节点,即与该项名称相同的节点
def addnum(self,num):
self.num+=num
def display(self,ind=1):
print(' '*ind,self.name,' ',self.num)
for child in self.child.values():
child.display(ind+1)
'''
原始数据集
'''
def loaddata():
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']]
dataset={}
for data in simpDat:
dataset[frozenset(data)]=1
return dataset
'''
根据数据集创建FP树
updatetree 用来将数据添加到FP树中
'''
def createtree(dataset,minsup=1):
head={} #构建头指针表
for data,num in dataset.items():
for item in data:
head[item]=head.get(item,0)+num
a=list(head.keys()).copy()
for k in a:
if head[k]<minsup:
del(head[k])
freqset=set(head.keys()) #获得频繁项
if len(freqset)==0:
return None,None
for key in head:
head[key]=[head[key],None] #为指针留下存储空间
mytree=treeNode('tree head',0,None) #定义FP树,根节点
for data,num in dataset.items():
tmpdata={} #对数据进行过滤,只留下频繁项
for item in data:
if item in freqset:
tmpdata[item]=head[item][0]
if len(tmpdata)>0:
order=[v[0] for v in sorted(tmpdata.items(),key=lambda p : p[1],reverse=True)]
#根据支持度,对过滤后的数据的元素进行排序
updatetree(order,mytree,head,num)
mytree.display() #显示FP树
return mytree,head
'''
更新FP树
'''
def updatetree(item,intree,head,num):
if item[0] in intree.child: #如果根节点的子节点中包含数据第一个元素,直接将该子节点的数量加一
intree.child[item[0]].addnum(num)
else: #如果没有,则新创建一个子树,以该数据第一个元素作为根节点
intree.child[item[0]]=treeNode(item[0],num,intree)
if head[item[0]][1]==None: #确定头指针表中该元素,是否已经存在指针
head[item[0]][1]=intree.child[item[0]] #如果没有相连,定义好指针
else: #如果存在指针,证明该元素已经在FP树出现过,通过链表link找到最后一个出现该元素,并将最后元素与新出现元素相连
cur=head[item[0]][1]
while cur.link != None:
cur=cur.link
cur.link=intree.child[item[0]]
if len(item)>1: #当数据的元素大于1,则对后面元素进行重复操作
updatetree(item[1:],intree.child[item[0]],head,num)
'''
根据FP树,和头指针表,确定头指针表元素的前缀
'''
def findpath(tree,head):
result={}
for key in head:
curlink=head[key][1] #将指针移动到第一个对应元素
tmp={} #创建字典存储前缀
while curlink != None:
cur=curlink
num=cur.num
path=[]
while cur.parent.name !='tree head':
cur=cur.parent
path.append(cur.name)
if len(path)!=0:
tmp[frozenset(path)]=num
curlink=curlink.link
result[key]=tmp
return result
'''
根据频繁项的前缀确定所有频繁项
'''
def minetree(intree,head,minsup,pre,freqlist,ind):
path=findpath(intree,head)
#获得频繁项的前缀
bigl=[v[0] for v in sorted(head.items(),key=lambda p:p[1][0])]
#根据支持度对头指针表排序,从小到大
for base in bigl:
#将已经用过的前缀复制,并加入当前头指针表元素,构建新频繁项
newpre=pre.copy()
newpre.add(base)
freqlist.append(newpre)
#将前缀作为数据,构建FP树,获得新的头指针表
basetree,basehead=createtree(path[base],minsup)
if basehead!=None:
#递归调用 直到无法再构建条件FP树
minetree(basetree,basehead,minsup,newpre,freqlist,ind+1)
def test1():
dataset=loaddata()
mytree,head=createtree(dataset,3)
freqlist=[]
pre=set([])
ind=1
minetree(mytree,head,3,pre,freqlist,ind)
for item in freqlist:
print(item)
print(len(freqlist))
if __name__=='__main__':
test1()