python实现FP-growth算法发现频繁项集

★ FP-growth算法的作用:

       该算法是代替Apriori算法来高效发现频繁集,但不能用于发现关联规则。

★ FP-growth算法的组成:

       该算法需要构建三部分:1. 项头表   2. FP树   3.节点链表

  ✿ 举个例子:

     现在有如下6条事务:

TID Item
1 [ 'r', 'z', 'h', 'j', 'p' ]
2 [ 'z', 'y', 'x', 'w', 'v', 'u', 't', 's' ]
3 [ 'z' ]
4 [ 'r', 'x', 'n', 'o', 's' ]
5 [ 'y', 'r', 'x', 'z', 'q', 't', 'p' ]
6 [ 'y', 'z', 'x', 'e', 'q', 's', 't', 'm' ]

     1. 筛选符合最小支持度的物品:

     第一次遍历:我们遍历这6条事务,统计每个物品出现的次数,汇总成表:

表1 各物品的频数表
物品 出现次数 物品 出现次数
z 5 q 2
x 4 p 2
y 3 w 1
t 3 v 1
s 3 u 1
r 3 o 1
    n 1
    m 1
    j 1
    h 1
    e 1

       我们设置频繁项的最小支持度为3,这里的支持度不同于Apriori的支持度,Apriori支持度是概率,而这里是频数,就是出现的次数,简单来讲就是保留出现次数不小于3的物品,作为频繁项1项集。

       根据支持度把表1筛选后,整理成表2:(这里我按照频数大小降序排列了,其实写代码的时候这个表以字典的数据结构存储,也就没顺序了,这里排序只是为了构建FP树时观看方便,因为构建树的时候我们会对输入数据按照频数大小降序排列)

表2 符合最小支持度的物品及支持度
物品 支持度
z 5
x 4
y 3
t 3
s 3
r 3

      2. 构建FP树和项头表:

      第二次遍历所有事务:我们根据支持度筛选每条事务,并让其按支持度降序排列(目的是画FP树的时候尽量多的共用祖先节点):

      比如第一条事务:

1 [ 'r', 'z', 'h', 'j', 'p' ]

      物品‘h'、'j',’p'不满足最小支持度,故删去,剩下的'r'、'z'按照支持度降序排列:[ 'z','r' ]

      但是这里有个问题:支持度一样大的物品,怎么排序呢?我们可以按照字典顺序正序排列或者倒序排列。比如:第二条事务:[ 'z', 'y', 'x', 'w', 'v', 'u', 't', 's' ],将其筛选排序可以为:[ 'z', 'x', ’y',  't', 's' ],这里 'y','t',‘s'的支持度都为3,我默认规定支持度一样大的物品按照字典顺序倒序排列(这是我自己规定的,反正无论如何你都得定一个顺序)。

      整理后的6条事务:

表3 按支持度筛选后的事务
TID item
1 [ 'z', 'r' ]
2 [ 'z', 'x', 'y', 't', 's' ]
3 [ 'z' ]
4 [ 'x', 's', 'r' ]
5 [ 'z', 'x', 'y', 't', 'r' ]
6 [ 'z', 'x', 'y', 't', 's' ]

     我们利用这6条筛选后的事务(表3)及频繁1项集(表2) 构建FP树和项头表:

python实现FP-growth算法发现频繁项集_第1张图片

      ✿ 现在我们来说明一下怎么画出图1:

       依次遍历整理后的事务(表3):

       (1.) FP树以空集为根节点,插入第一条事务: [ 'z', 'r' ]  ,它是一条独立路径,每个节点的计数为1

python实现FP-growth算法发现频繁项集_第2张图片

      在编写程序中,我们把项头表存贮到一个字典格式的数据结构中,键就是物品(如z或x或y),键对应的值是一个一维列表,列表保存的是该物品的支持度和该节点的实例,节点实例是一个类,在下面的(2.)我们做介绍。

      (2.) 现在插入第二条整理后的事务:[ 'z', 'x', 'y', 't', 's' ],我们观察在这之前已经插入的事务:[ 'z', 'r' ],发现第二条事务可以共用第一条事务的 'z',因此z的计数就变为2了,但是会在共用’z'之后接着分叉,因为第二条事务第二个元素是‘x',而第一条事务的第二个元素是’r',无法共用了,就会开辟出新的路径,除‘z'外计数都为1,于是我们画出图:

python实现FP-growth算法发现频繁项集_第3张图片

      上面提到节点信息类包括5部分,比如此时z的节点信息为:

表4 节点信息类的五个属性

z的节点信息 :

               

(代码中我们把节点信息

存贮在一个类的数据结

构中,类有五个静态属性)

self.name: 存贮这个节点的名字,这里名字是’z‘
self.count: 存贮目前为止,这个节点的计数,这里是2
self.parent: 存父节点的实例地址,这里存储的就是根(空集)的节点信息类的实例,因为每个节点信息就是以类存储的
self.children: 存储这个节点后的分叉点的实例地址,它以字典为存贮结构
self.nodeLink: 假如这个节点有相似节点,那么存贮它的相似项的节点实例

      (3.) 插入第三条事务:[ 'z' ],此时观察已经插入的事务:[ 'z', 'r' ],[ 'z', 'x', 'y', 't', 's' ],发现可以共用’z'节点,这样‘z'节点的计算就会再增加1变为3了,画出图:

python实现FP-growth算法发现频繁项集_第4张图片

    (4.) 插入第四条事务: [ 'x', 's', 'r' ],观察已经插入的事务,发现这要插入的事务第一个元素是’x',而以前已经插入的事务都是‘z'元素开头,所以这新插入的事务需要从根节点开始另开新路径:

        在插入这个新事务 [ 'x', 's', 'r' ] 时,我们发现项头表的节点指针已经存贮过'x'、's'、'r'的节点信息,我们找到项头表中这几个已经存贮的节点,分别让已保存节点的self.nodeLink属性等于新的相似节点的实例地址,效果图如下图的s节点,可以看到深蓝色箭头。

python实现FP-growth算法发现频繁项集_第5张图片

      (5.) 插入第五条事务: [ 'z', 'x', 'y', 't', 'r' ],同样观察已经插入的四条事务,发现可以共用 'z'、'x'、'y'、't'这个四个节点,因此这四个节点的计数再增加1,而在共用最后一个节点‘t’之后,需分叉出节点'r',如图:

       这里的分叉‘r'已经是第三次作为分叉出现了,项头表中已经保存了第一次出现的’r'的节点信息,然后在节点‘r'第二次出现时,我们让第一次出现的’r'节点的self.nodeLink属性保存了第二次出现的‘r'的节点信息实例,同样,我们让第二次出现的’r'的节点实例的self.nodeLink属性保存第三次出现的‘r'的节点信息实例,形成链式。

python实现FP-growth算法发现频繁项集_第6张图片

    (6.) 插入第6条事务:[ 'z', 'x', 'y', 't', 's' ],同样观察已经插入的五条事务,也可以看上图,发现待插入的这条事务可以共用   'z'、'x'、'y'、't'、's' 这五个节点,因此这五个节点的计数再增加1:

python实现FP-growth算法发现频繁项集_第7张图片

      由此,FP树便是完成了。

     3. 挖掘FP树:

python实现FP-growth算法发现频繁项集_第8张图片

      (1.) 遍历项头表的键(也就是'z','x','y','t','s','r'),比如’r',找到以‘r'为结尾以根节点(空集)为开头的所有路径(路径上每个元素的计数跟结尾元素相同)的集合,我们称为’r'对应的条件模式基,而这一条条路径我们称为‘r'的前缀路径,前缀路径一般我们不写开头和结尾,比如我们把’r'的所有路径写出来:{Ø,'z':1,'r':1}、{Ø,'x':1,'s':1,'r':1}、{Ø,'z':1,'x':1,'y':1,'t':1,'r':1}。这个所有路径怎么找出来的呢?其实就是你先找到项表头的这个频繁项’r'对应的节点指针,这个节点指针保存的是第一次出现‘r'的节点,然后顺着这个节点从下往上找到根为止,得到路径:{Ø,z:1,r:1};再由这个节点的self.nodeLink属性找到它的相似节点,也就是第二次出现的‘r'节点,再顺着第二次出现的这个’r'节点从下往上找到根,得到路径:{Ø,'x':1,'s':1,'r':1};再由这个第二次出现的‘r'节点的self.nodeLink属性得到它相似节点,也就是第三次出现的’r'节点,由这第三个‘r'节点从下往上找直到根,得到第三条路径:{Ø,'z':1,'x':1,'y':1,'t':1,'r':1}。

       因为前缀路径不写开头和结尾,所以三条前缀路径分别是:{'z':1}、{'z':1,'x':1,'y':1,'t':1}、{'x':1,'s':1},当然了,你也可以这么写:{'z'} : 1、{'x','s'} : 1和{'z','x','y','t'} : 1。接下来我们把项目头中的这6个的元素的条件模式基汇总成表:

表5 频繁项及其条件模式基
频繁项 条件模式基
z None
x {z}:3
y {z,x}:3
t {z,x,y}:3
s {z,x,y,t}:2、{x}:1
r {z}:1、{z,x,y,t}:1、{x,s}:1

      (2.)  得到每个频繁项的条件FP树:

      按照最小支持度筛选条件模式基中的元素,形成条件FP树。假如有一个频繁项的前缀路径大于2条,要把这所有的路径合起来,即对应元素的计数加起来,再去掉不符最小支持度的元素。

      比如对于频繁项's':它的条件路径是{z,x,y,t}:2、{x}:1,对应元素合并,{ ’z':2, 'x':3, 'y':2, 't':2 },最小支持度是3,所以条件模式基再根据最小值支持度筛选后,只剩下{ ’x' }:3。

     经过筛选条件模式基,得到下表:

表6 根据支持度筛选后的条件模式基
频繁项 筛选后的条件模式基
z None
x {z}:3
y {z,x}:3
t {z,x,y}:3
s {x}:3
r None

     之后只需将频繁项和其对应的筛选后的条件模式基自由组合再加上自身,即可得到所有频繁项集。

     'z'组合后是:{ 'z' }

     'x'组合后是:{ 'x’ }、{ 'x', 'z' }

     'y'组合后是:{ 'y' }、{ ’y', 'z' }、{ ’y', 'x' }、{ ’y', 'z', 'x' }

     't'组合后是:{ ‘t' }、{ ‘t', 'z' }、{ ‘t', 'x' }、{ ‘t', 'y' }、{ ‘t', 'x', 'y' }、{ ‘t', 'z', 'x' }、{ ‘t', 'z', 'y’ }、{ ‘t', 'z', 'x', 'y' }

    ‘s'组合后是:{ 's' }、{ ’s', 'x' }

     'r'组合后是:{ 'r' }

★ 代码实践:

 ✿ 算法理解:

       1. 根据上述算法,同样的输入,得到的FP树却不一样,这是为何呢?

       这跟项头表的顺序无关(因为本来这个表存贮在字典里,是无序的),其实,树的形状是跟构建树的时候的输入事务的内部顺序有关,即同样大小的支持度如何排列有关,虽然我跟《机器学习实战》第230页输入一样,得到的FP树却不一样,这是因为我对同样大小的支持度排列规则跟书上不一样。

      2. treeNode类的理解:

      该类有五个属性:

       (1.) self.name 保存的该节点的名字,如:‘x'或’z'

       (2.) self.count 保存的是该节点被事务经过的次数

       (3.) self.nodeLink 保存的是这个节点的相似节点的实例,形成链式

       (4.) self.parent 保存这个节点的父节点的实例

       (5.) self.children 它是一个嵌套字典,存贮的这个节点后的所有分叉点的实例

     3. createIniSet()函数:

     该函数作用是将事务以字典形式输出,如:{ frozenset({ 'r','z','h','j','p' }):1,... }

    4. createTree()函数:

     该函数作用是输入字典格式的事务和最小支持度,返回FP树和项头表。它的流程是:

     第一次遍历所有的字典格式的事务,统计每个物品(如‘z')出现的频数(支持度),并存入一个字典(项头表的雏形)中,字典的键是这些物品的名称,键对应的值是该物品的支持度,然后再遍历这个字典,去除不满足支持度的物品,即最后该字典留下了满足支持度的物品,把此时的字典的值改成一个一维列表,列表的0位置还是该物品的支持度,1位置是该节点的节点指针,初始化为None ,它是在遍历事务来画FP树的时候,保存第一次出现该节点的实例(因为节点信息是以类存贮,所以这里保存实例,就能访问这个节点的所有信息),这个时候的字典才是真正的项头表,基本格式是 { 'z':[5,节点地址],...}。有了项头表之后,现在我们第二次遍历所有的字典格式的事务,对每条事务作处理,即把事务中的不满足支持度的元素去掉,留下满足支持度的元素,然后再把剩下的这些元素按照支持度从大到小排列,这一步很关键,把支持度大的元素放前面是为了在画FP树的时候尽量多的共用祖先节点,而支持度一样大的元素我们可以按照字典顺序倒序排列,具体操作就是把项头表的键取出来构成set集合,这个集合其实就是频繁1项集,每条事务中满足支持度的元素一定在这个集合中,这样就能完成支持度筛选,我们把筛选后的每条事务存到一个新的字典中,这个新的字典的键是每条事务的元素的名称,键对应的值是该元素的总支持度,这个总支持度在项头表的值的0位置都保存了,直接取过来就行,为什么要把元素和其支持度再取出来放到字典中,这么做是为了我们把筛选后的每条事务内部再按支持度降序排列,排序的时候我们用到了函数sorted()和operator模块的itemgetter(),来实现上述排序,排序后的每条事务的格式是:[ 'z', 'r' ],然后我们根据筛选排序后的事务,画出FP树。具体做法是updateTree()函数。

     5. updateTree()函数

     该函数作用是接收处理好的事务列表,画出FP树。在updateTree()调用之前我们就设定好了这个FP树的根节点,根节点的属性有:self.name 为Null set,self.parent 为None,self.children 为嵌套字典,来保存接下来的子树。进来一条事务,判断第一个元素在不在根节点的self.children 字典的键中,不在的话就把这个元素的名字作为键,该元素(也可称为节点)的节点类实例作为该键对应的值,这个节点类实例的self.name 是该节点名字,self.count 是当前节点被事务路径经过的次数,现在刚开始创建树,输入的是第一条事务,因此该节点就被用过1次,所以self.count 为1 ,self.parent 是根节点的类实例,self.nodeLink 还是空,因为还没发现该节点的相似项,self.children 为空{}。从效果来看相当于根节点连接了这个新节点,因此根节点的self.children 字典保存了新节点的实例,而新节点的self.parent保存了根节点的实例,这就相当于连接起来了,该节点是构造FP树的过程中第一次出现,因此也要把这个节点的实例保存在项头表的该元素对应值的列表的1位置,0位置是该元素的支持度。此时再遍历这条事务的第二个元素来画第二个元素节点,这第二个节点就是以第一个节点为父节点了,而不是以根节点了,这也好理解,一条事务就是一条路径,这第二个节点肯定要在第一个节点后面嘛,同样的方式,判断第二个节点的名称是不是在第一个节点的self.children 字典的键中,如果不在,就把第二个节点的实例作为值存到第一个节点的slef.children 字典中,同样创造第二个节点实例的时候,该实例的self.parent 保存的是第一个节点的实例,self.nodeLink还是None,因为还没发现这个节点的相似项,从效果来看这次操作就是把第二个节点连在第一个节点的后面了,到目前位置,这棵FP树展现出来的是:根节点连第一个节点元素,第一个节点后面又连了第二个节点元素。

      刚才叙述的是输入第一条事务并画出了一条路径,更新了项头表的节点指针,但是从输入第二条事务开始,该FP树就涉及到祖先节点重用、出现节点的相似项这两个问题了,什么是相似项?相似项就是节点名称一样,却在两条独立的路径上。在输入第二条事务时,FP树已经画了一条路径,现在我们遍历这第二条事务的第一个元素,又是从根节点开始画起,如果这第一个元素不在根节点的self.children 字典的键中,那么这就意味着第二条事务会开辟出第二条独立路径(每条事务都要从根节点开始画起,但是要不要从根节点开辟新路径关键是看每条事务的第一个元素,事务的第一个元素不同,肯定是要开辟出新路径的),如果根节点开辟新路径,做法跟上面画第一条路径的步骤是一样的,但是这里就会出现相似项问题了,第二条路径是跟第一条路径相互独立的两条路径,但是假如第二条路径中有和第一条路径相同的元素,又因为此时项头表中的该元素对应的值已经保存了第一次出现该元素名称的节点实例了,人家是第一次出现所以已经先入为主了,所以当发现第二条路径有和第一条路径相同的元素的时候,找到项头表该元素名称对应的值,就会拿到第一次出现该元素名称的节点实例,让该实例的slef.nodeLink保存这第二次出现的该名称节点的实例,相当于把这第二次出现的该名称节点保存到了第一次出现该名称的节点的self.nodeLink属性中,展现的效果也相当于把它俩连起来了,假如第三条事务来了,又是一条独立路径,而刚刚那个节点又出现了第三次,那么由项头表顺着节点链找到第二次出现该名称的节点实例,让这个实例的self.nodeLink保存刚刚出现的第三次该名称的节点实例,效果相当于用self.nodeLink把这三个相似项串联起来了。而如果这第一个元素在根节点的self.children字典的键中,这说明这个元素被重用了,也就是在根节点后不用开辟新路径,因为被重用了,所以该名称的节点实例的self.count 要加1,那么接下来再遍历这第二条事务的第二个元素,这里把刚刚重用的第一个元素看作根节点,因此画这第二条事务的第二个元素节点的时候又跟上述一样,先判断这个第二个元素是不是在第一个重用元素的self.children字典的键中,在的话继续重用,不再的话就从这里开始分叉,开辟出新路径。

     6.updateHeader() 函数:

    该函数作用是:当出现两个或两个以上的相似项时,找到最后一个相似项的实例,让该实例的self.nodeLink属性保存新出现的相似项,效果如同是在一条链的最后一个节点后再接入一个节点,这些链就是self.nodeLink。

-------------- 上述几个函数是为了构建出FP树,接下来的函数来挖掘该树 ----------------

     7. findPrefixPath() 函数:

     该函数作用是找到给定元素名称的条件模式基,以字典格式存贮。我们知道项头表的键其实就是频繁1项集,而FP树的所有节点都来自这个频繁1项集,也可以这么说,频繁1项集构成了这个FP树,我们要想找一个元素(如'z')的的条件模式基,就需要找到FP树中该名称的所有节点,首先项头表中能找到第一次出现的该名称的节点实例,然后读取这个实例的self.nodeLink可以找到第二次出现的该名称的节点实例,再读取第二次出现的节点实例的self.nodeLink就能找到第三次出现的该名称的节点实例,找出来这所有相似节点的实例,只需要分别从这些相似节点实例从下往上回溯直到根节点(空集),就可以得到许多条路径,因为前缀路径我们不写开头和结尾(结尾节点就是刚才往上回溯的节点实例),故把每一条路径的结尾(也就是该元素)和开头(空集)去掉,剩下的元素组成的路径称为前缀路径,用一个列表来存贮,而所有前缀路径组成条件模式基,我们用一个字典来保存该条件模式基,字典的键就是每条前缀路径,键对应的值是刚才去掉的结尾节点实例的self.count,也就是该路径的叶子节点的计数值。

     8. ascendTree() 函数:

     该函数作用是找到给定节点往上回溯到根节点的路径。并把路径存到列表中。

     9. mineTree() 函数:

    该函数输入FP树和项头表,遍历项头表的键(频繁1项集)的每个元素,并调用findPrefixPath(),得到该元素的条件模式基,我们把该条件模式基再调用createTree()函数,创造出该元素对应的条件模式树和条件项头表,这里的条件项头表就是上面的表6,我们再把条件模式树和条件项头表再输入到mineTree(),形成递归,就会得到该元素和其对应的条件项头表(表6)的频繁1项集的自由组合,假如你不想使用递归来得到自由组合也可以,需要另外定一个函数来实现这个自由组合功能,我试过,因为在上一章Apriori算法中也有实现自由组合列表元素的这种功能的函数,但是实现起来确实麻烦,代码量大,不如书上这种用递归好。

from numpy import *
import operator

class treeNode: # 存储节点信息
    def __init__(self, nameValue, numOccur, parentNode):
        self.name = nameValue  # 自身频繁项名字,如‘z'或’x'
        self.count = numOccur  # 此时此刻这个节点被事务经过的次数
        self.nodeLink = None   # 用于保存相似项的实例地址
        self.parent = parentNode # 保存父节点的实例地址
        self.children = {}  # children是以一个嵌套字典,存贮分叉时的实例地址

    def inc(self, numOccur): # count计数加上numOccur
        self.count += numOccur

    def disp(self, ind=1): # 遍历children属性,打印出子树
        print(' '*ind,self.name,self.count)
        for child in self.children.values():
            child.disp(ind+1)

def loadDataSet():  # 加载测试事务
    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 createIniSet(dataSet): # 整理成字典形式
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = 1
    return retDict

def updateHeader(nodeToTest, targetNode): # 找到节点链接的最后一个实例,然后该实例的self.nodeLink保存刚才待被连接的节点实例,相当于节点链接末尾又增了一个节点
    while (nodeToTest.nodeLink != None):  # 如果nodeLink不是空时继续顺着节点链接走,知道找到末尾
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode    # 找到末尾之后,末尾节点的nodeLink保存新的节点实例

def updateTree(items,inTree,headerTable,count): # items是每条记录按支持度排好的特征,
    if items[0] in inTree.children:  # 如果这棵子树同一深度有这个键(特征)了,就把其对应的频数加一
        inTree.children[items[0]].inc(count)
    else:    # 如果这棵子树在同一深度没有这个键
        inTree.children[items[0]] = treeNode(items[0],count,inTree) # 就把这个键形成的节点存到这棵子树的children属性中
        if headerTable[items[0]][1] == None: # 如果项目头对应该键的节点指针指向为空
            headerTable[items[0]][1] = inTree.children[items[0]] # 那么把项目头对应该键的节点指针指向该节点,翻译到程序中就是headerTable中该键对应的值的第二位存储该节点的实例地址
        else:  # 如果已经有指向了,就在已有指向的节点的nodeLink属性添加刚才那个节点的信息
            updateHeader(headerTable[items[0]][1],inTree.children[items[0]])
    if len(items) > 1:
        updateTree(items[1::],inTree.children[items[0]],headerTable,count) # 把该记录整理的特征集合的第一个特征删掉,继续递归

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 list(headerTable.keys()):     # 把不满足支持度的特征删除
        if headerTable[k] < minSup:
            del headerTable[k]            # headerTable的键是满足支持度的特征,值是支持度
    freqItemSet = set(headerTable.keys()) # 满足支持度的特征的集合,也可以认为是频繁1项集
    if len(freqItemSet) == 0:
        return None,None
    for k in headerTable:  # 改造成真正的项头表格式
        headerTable[k] = [headerTable[k],None]  # headerTable键还是满足支持度的特征,值的第一个位置是具体支持度数,第二个是None
    retTree = treeNode('Null set',1,None)     # 创建树,根是Null set
    for tranSet,count in dataSet.items():     # 从原始事务中取一条事务
        localD = {}
        for item in tranSet:   # 开始对原始事务进行处理,即去除不满足支持度的元素
            if item in freqItemSet: # freqItemSet是满足支持度的特征的元组
                localD[item] = headerTable[item][0]   # 把一条记录的符合支持度的特征及对应的支持度存到自点的键与值中
        if len(localD) > 0:  # 如果此筛选后的事务中有符合条件的元素
            orderedItems = [v[0] for v in sorted(localD.items(),key = operator.itemgetter(1,0),reverse=True)] # orderedItems把每个记录的特征按支持度倒序排列
        # print('orderItems=',orderedItems)
        updateTree(orderedItems,retTree,headerTable,count)
    return retTree,headerTable

def ascendTree(leafNode, prefixPath):  # leafNode是节点信息,prefixPath是前缀路径,其实就是条件模式基的列表
    if leafNode.parent != None:
        prefixPath.append(leafNode.name)
        ascendTree(leafNode.parent,prefixPath)  # 如果这个节点还有父亲节点,那么递归

def findPrefixPaht(basePat, treeNode):  # basePat节点名称如‘z'或’x',treeNode是headerTable表中存的这个节点的实例
    condPats = {}
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode,prefixPath)  # prefixPath是一条前缀路径,treeNode是存入headerTable表的这个节点的实例
        if len(prefixPath) > 1: # 因为前缀路径我们不写结尾元素,也就不是不写该节点,所以假如有前缀路径,那么prefixPaht大小一定大于1,等于1说明这个频繁项这条前缀路径是空
            condPats[frozenset(prefixPath[1:])] = treeNode.count # 前缀路径的所有元素的计数值等于这个尾元素的计数值,且把这个频繁项去掉,前缀路径不写结尾元素
        treeNode = treeNode.nodeLink  # 顺着节点链接找到相似项,再查找相似项的前缀路径
    return condPats

def getFrequentSet(element,myHeadList,freqItemList):
    freqItemList.append(frozenset((myHeadList[0],element)))
    if len(myHeadList)>1:
        getFrequentSet(element,myHeadList[1:],freqItemList)

def mineTree(inTree, headerTable, minSup, preFix,freqItemList): # preFix是set(),freqItem是[]
    bigL = [v[0] for v in headerTable.items()]
    # print('bigL=',bigL)
    for basePat in bigL:
        newFreqSet = preFix.copy() # preFix是set集合,这里用了copy,即newFreqSet的变化不会影响原pareFix
        newFreqSet.add(basePat)
        freqItemList.append(newFreqSet)
        condPattBases = findPrefixPaht(basePat,headerTable[basePat][1])  # condPattBases是该值对应的条件模式基
        myCondTree,myHead = createTree(condPattBases,minSup)
        # print('myHead=是条件树的项头表',basePat,myHead)
        if myHead != None:  # myHead是条件树的项头表
            # print('conditional tree for:',newFreqSet)
            myCondTree.disp(1)
            mineTree(myCondTree,myHead,minSup,newFreqSet,freqItemList)
    return freqItemList

if __name__ == '__main__':
    simData = loadDataSet()
    initSet = createIniSet(simData)
    myFptree,myHeaderTab = createTree(initSet,3)
    myFptree.disp()
    freqItem = []
    freqItem = mineTree(myFptree,myHeaderTab,3,set([]),freqItem)
    print(freqItem)

★ 运行结果:

     ✿  FP树:

python实现FP-growth算法发现频繁项集_第9张图片

    可以看出运行结果的FP树跟咱们分析而画出的图1是完全一致的。

    ✿ 频繁项集:

[{'z'}, {'r'}, {'x'}, {'x', 'z'}, {'t'}, {'x', 't'}, {'x', 't', 'y'}, {'x', 't', 'y', 'z'}, {'x', 't', 'z'}, {'t', 'y'}, {'t', 'y', 'z'}, {'t', 'z'}, {'y'}, {'x', 'y'}, {'x', 'y', 'z'}, {'y', 'z'}, {'s'}, {'x', 's'}]

★ 参考链接:

     1.  https://www.cnblogs.com/pinard/p/6307064.html

 

你可能感兴趣的:(大数据)