1 关联规则挖掘之FPGrowth算法实现

Apriori算法通过利用频繁集的两个特性,过滤了很多无效集合,提高了算法效率。但是算法每一次对频繁项集的筛选都需要扫描一次原始数据集,对于大规模数据集Apriori的算法效率不尽如人意。

FPGrowth算法由韩家炜[1]等人于2004年提出,其中FPTree是使得这一算法相比Apriori等算法较为高效的关键数据结构,FPTree将数据库中的所有事务(Transactions)高度压缩成树的路径,所有的频繁项(Frequent Items, FIs)都成为树的一个节点,每个节点都拥有相应的计数,代表该FIs在数据库中出现的次数,其中叶子节点的计数等于前向遍历路径中的FIs出现在数据库中的次数。因此所有的挖掘工作都以最初的FPTree为中心展开,而在构建一棵FPTree时,核心步骤在于对每一条事务进行降序排序。

1.1 FPTree的构建

  • 树结构设计
def __init__(self,idName,count=1):"""构造函数Args:               Type        MeaningidName          String      编号count           int         计数next            FpNode      下一个id号相同的结点parent          FpNode      父结点children        List[FpNode]孩子结点"""self.idName = idName    #根结点为Noneself.count = count      #根结点为-1self.next = None             self.parent = None      self.children = []
  • 算例演示

    同样我们以在关联规则挖掘之Apriori算法实现中的购物篮数据集为例演示FPTree的构造,频繁项的绝对支持度设置为3。

    TID Items
    T1 {牛奶,面包,1}
    T2 {面包,尿布,啤酒,鸡蛋,1}
    T3 {牛奶,尿布,啤酒,可乐,1}
    T4 {面包,牛奶,尿布,啤酒,1}
    T5 {面包,牛奶,尿布,可乐,1}

表中每一条记录中的数字表示记录出现的次数,如T1,{牛奶,面包,1}中的1表示同时购买牛奶,面包的记录出现了1次。

  • Step1:第一次扫描原始数据集,得到所有满足最小支持度阈值的频繁1项集,并按照项的计数降序排列;

    Item Count(desending)
    牛奶 4
    面包 4
    尿布 4
    啤酒 3
  • Step2:第二次扫描原始数据集,删除每条数据中的非频繁1项集,并按照计数降序排列每条数据中的项;

    注意:这里降序排列时,一定要保证项的相对顺序,如这里选择了频繁1项集的排序为牛奶、面包、尿布、啤酒,那么数据集中的每一条数据(这里已经去除了非频繁1项集)都应该按照频繁1项集中项的相对顺序排列。

    构建FPTree,初始时,新建一个标记为None的空节点。按顺序扫描记录,将记录中项添加到FPTree中。

    • 第1条记录:{牛奶,面包,1}降序排列够得到{牛奶,面包,1},新建一个结点,idName为**“牛奶”,将其插入到根节点下,并设置count为记录中的数字即1,然后新建一个“面包”结点,插入到“牛奶”**结点下面。插入后如下所示:


同时,为了便于候选在FPTree上挖掘频繁项集,我们构建了一个线索表,线索表中元素由频繁1项集按照计数降序排列组成。随着FPTree的构建,将同idName的结点地址相连。如示例中的线索表为{牛奶:[牛奶结点地址],面包:[面包结点地址],尿布:[尿布结点地址],啤酒:[啤酒结点地址]}。

  • 第2条记录:{面包,尿布,啤酒,鸡蛋,1},删除非频繁1项集再降序后得到:{面包,尿布,啤酒,1}。访问FPTree的根结点None,发现其子结点没有包含面包,因此新建一个面包结点,将其作为根节点None的第2个子结点,随后新建尿布结点上接新建面包结点,下接新建啤酒结点,并根据新建结点更新线索表,新建结点的计数均为所在记录的计数。

  • 第3条记录:{牛奶,尿布,啤酒,可乐,1},删除非频繁1项集再降序后得到:{牛奶,尿布,啤酒,1}。访问FPTree的根结点None,发现其子结点上含有牛奶结点,则将牛奶结点的计数+新记录中的计数(这里为1);接着访问牛奶结点的子结点,发现牛奶结点的子结点中无尿布结点,因此新建尿布结点,上接已有牛奶结点,下接新建啤酒结点,并根据新建结点更新线索表,新建结点的计数均为所在记录的计数。
  • 第4条记录:{面包,牛奶,尿布,啤酒,1},删除非频繁1项集再降序后得到{牛奶,面包,尿布,啤酒}。访问FPTree的根结点None,发现其子结点上含有牛奶结点,则牛奶结点计数+当前记录的计数;继续访问牛奶结点的子结点,子结点中含有面包结点,同样将面包结点计数+当前记录的计数;继续访问面包结点的子结点,发现子结点中并无尿布结点,因此新建尿布结点上接面包结点,下接新建啤酒结点,并根据新建结点更新线索表;同样,新建结点的计数均为所在记录的计数。

  • 第5条记录:{面包,牛奶,尿布,可乐,1},删除非频繁1项集再降序后得到:{牛奶,面包,尿布,1}。访问FPTree的根结点,发现其子结点上包含牛奶结点,将牛奶结点计数+当前记录的计数;继续访问牛奶结点的子结点,包含面包结点,则将面包结点计数+当前记录的计数;继续访问面包结点的子结点,包含尿布结点,将尿布结点的计数+当前记录的计数。

1.2 利用FPTree挖掘频繁项集

频繁项集的挖掘从线索表表头的最后一项开始,依次从后往前挖掘,如此例中,线索表的表头为[牛奶,面包,尿布,啤酒],那么挖掘顺序为啤酒→\rightarrow→尿布→\rightarrow→面包→\rightarrow→牛奶。挖掘流程如下:

  1. 此例中从啤酒开始,根据啤酒的线索链找到所有包含啤酒的结点,然后找出每个包含啤酒结点的分支:

    {牛奶,面包,尿布,啤酒,1},{牛奶,尿布,啤酒,1},{面包,尿布,啤酒,1},其中的1表示分支出现的次数为1次,由分支中啤酒的计数决定。除去分支中的啤酒,得到新的数据记录{牛奶,面包,尿布,1},{牛奶,尿布,1},{面包,尿布,1}。然后按照FPTree的构建方式构建啤酒的条件FPTree。

    • 啤酒分支对应的数据集
    TID Items
    T1 {牛奶,面包,尿布,1}
    T2 {牛奶,尿布,1}
    T3 {面包,尿布,1}
    • 啤酒的条件FPTree

构造好条件树后,对条件树进行递归挖掘,递归的终止条件为当前条件树只有一条路径,此时路径的所有组合即为条件频繁集。如当前啤酒的条件频繁集为{{},尿布},于是啤酒的频繁集为{{啤酒},{尿布,啤酒}}

  1. 接下来找线索表表头的倒数第2项尿布的频繁集,同理可以得到尿布的前缀路径为:{面包,1},{牛奶,1},{牛奶,面包,2}

    • 尿布分支对应的数据集
    TID Items
    T1 {面包,1}
    T2 {牛奶,1}
    T3 {牛奶,面包,2}
    • 尿布对应的条件FPTree

由于当前条件树中存在两条路径,因此进行递归挖掘。从尿布线索表表头的最后一项即面包项开始进行挖掘。同理面包的前缀路径为,{牛奶,2}

  • 面包分支对应的数据集

    TID Items
    T2 {牛奶,2}

由于当前数据集不满足最小绝对支持度的要求,因此面包的条件FP树为None,满足条件FPTree的路径数仅有一条的条件,因此当前面包的条件频繁项集为{},面包的频繁集为{面包}。

同理尿布线索表表头的剩余项牛奶的频繁集为{牛奶}

最终:尿布的频繁集为{{尿布},{牛奶,尿布},{面包,尿布}}(**注意:**尿布自身也是频繁集)

重复以上步骤,对线索表中的每个项进行挖掘,即可得到整个频繁项集。

1.3 参考

[1] Han jia wei, Pei Jan等 Mining Frequent Patterns without Candidate Generation: A Frequent-Pattern Tree Approach.2004.

[2] https://www.cnblogs.com/fengfenggirl/p/associate_fpgowth.html.

1.4 代码实现

  • 树结点的设计

    
    class FpNode:def __init__(self,idName,count=1):"""构造函数Args:               Type        MeaningidName          String      编号count           int         计数next            FpNode      下一个id号相同的结点parent          FpNode      父结点children        List[FpNode]孩子结点"""self.idName = idName    #根结点为Noneself.count = count      #根结点为-1self.next = None             self.parent = None      self.children = []    def addChild(self,child):"""添加一个孩子结点Args:               Typechild           FpNode"""self.children.append(child)def addCount(self,count=1):self.count += countdef setNextNode(self,next):"""设置下一个结点Args:           Typenext        FpNode"""self.next = nextdef setParent(self,parent):"""设置父结点Args:           Typeparent      FpNode"""self.parent = parentdef getChild(self,index):"""指定索引取孩子Args:           Typeindex       int"""return self.children[index]def hasChild(self,idName):"""查找是否包含id为idName的孩子Args:           TypeidName      string"""for i in range(len(self.children)):if self.children[i].idName == idName:return ireturn -1def __repe__(self):return "id: " + self.idName + "count: " + str(self.count) \+ "孩子个数" + str(len(self.children))
    
  • FPTree的构建

    
    from FpNode import FpNodeclass FpTree:def __init__(self,dataSets,min_num):self.dataSets = dataSets        #待挖掘数据集,[[...,count],[...,count],...]# self.min_num = round(min_support * FpTree.countnum(dataSets))   #最小支持数=最小支持度*数据量self.min_num = min_numdef scan1(self):"""构建项头表,也就是按照频次降序的频繁1项集Returns:L1          频繁1项集{1项集:计数}"""L1 = {}         #频繁1项集for record in self.dataSets:for item in record[:-1]:if item in L1.keys():L1[item] += record[-1]else:L1[item] = record[-1]delrecord = []      #记录不满足支持度的项,非频繁项for item,count in L1.items():if count < self.min_num:delrecord.append(item)if delrecord:for item in delrecord:del L1[item]L1List = sorted(L1.items(),key=lambda x:x[1],reverse=True) L1.clear()  for item in L1List:L1[item[0]] = item[1]return L1def scan2(self,L1):"""删除样本数据集中的非频繁1项集,并按照频繁1项集的计数排序Args:L1              频繁1项集{1项集:计数}Returnsrecord          删除非频繁1项集后的数据"""record = []for row in self.dataSets:newrow = []for key in L1.keys():       #频繁1项集if key in row[:-1]:newrow.append(key)if newrow:newrow.append(row[-1])  #加上计数record.append(newrow)return recorddef createFpTree(self,L1,Ls):"""构建Fp树Args:L1              频繁1项集Ls              去除非频繁1项集后的记录Returns:headerTable     线索表root            Fp树根"""nodeLinkTable = {idName:FpNode(idName,num) for idName,num in L1.items()} #线索表headerTable = nodeLinkTable.copy()root = FpNode(None,-1)for L in Ls:currentRoot = root                                  #根结点for idName in L[:-1]:index = currentRoot.hasChild(idName)            #当前结点是否在当前根结点的子结点中if index >= 0:currentNode = currentRoot.getChild(index)   #是,则取出currentNode.addCount(L[-1])                 #结点出现次数加上当前记录的重复次数else:                                           #否,则新建结点currentNode = FpNode(idName,L[-1])currentNode.parent = currentRootcurrentRoot.addChild(currentNode)nodeLinkTable[idName].setNextNode(currentNode)  #记录同idName的项nodeLinkTable[idName] = currentNodecurrentRoot = currentNodereturn headerTable,root@staticmethoddef levelOrderTraversal(root):"""层序遍历,检测Fp树是否构建完整"""record = [] #记录每一层的idNameQ = [root]while Q:currentNode = Q.pop(0)record.append(currentNode.idName)Q.extend(currentNode.children)return record
    
  • 频繁项集的挖掘

    
    from FpTree import FpTree
    def conditionFpTree(leafNode):"""生成指定项的条件树Args:headerTable         线索表头root                Fp树根leafNode            指定项"""###首先找到所有包含指定项的分支,并以指定项截断###newLs = []              #包含指定项的所有分支记录,以指定项截断while leafNode:currentNode = leafNodeL = []Flag = True    #标注是否与当前结点相连的仅有根结点while currentNode.parent and currentNode.parent.idName != None:L.append(currentNode.parent.idName)currentNode = currentNode.parentFlag = Falseif not Flag:L.append(leafNode.count)newLs.append(L)leafNode = leafNode.next###其次计算新的频繁1项集和去除非频繁项后的所有分支记录###ft = FpTree(newLs,3)            #创建新树实例newL1 = ft.scan1()  #新的频繁1项集newLs = ft.scan2(newL1)      #去除非频繁项后的分支记录###最后构建指定项的条件树###newheaderTable,newroot = ft.createFpTree(newL1,newLs)return newheaderTable,newroot#层次遍历树,当只有一个叶结点时,表明条件树只有一条路径
    def isSingleLeaf(root):"""层序遍历Args:root        根结点Returns:bool        树是否有分支"""record = [] #记录每一层的idNameQ = [root]while Q:currentNode = Q.pop(0)record.append(currentNode.idName)Q.extend(currentNode.children)return len(record) <= 2     #[None]或者[None,item]def frequent(condTreeItems):"""生成仅有一条路径的condTree上的所有频繁项集Args:condTreeItems    条件模式基[item1,item2,....]Returns:fks         频繁项集"""fks = [[]]for item in condTreeItems:fks += [items + [item] for items in fks]return fksdef miningFrequentSet(leafNode):"""递归挖掘指定项的频繁集Args:           TypeleafNode    FpNodeReturns:"""frequentSet = []currentHeaderTable,currentRoot = conditionFpTree(leafNode)if currentHeaderTable == dict():frequentSet.append([leafNode.idName])return frequentSetif isSingleLeaf(currentRoot):       #判断条件树是否是单树if currentRoot.children:condTreeItems = []currentNode = currentRoot.children[0]while currentNode:condTreeItems.append(currentNode.idName)if currentNode.children:currentNode = currentNode.children[0]else:breakfrequentSet = frequent(condTreeItems)if frequentSet:for s in frequentSet:s.append(leafNode.idName)else:frequentSet.append([leafNode.idName])return frequentSetelse:targetIdName = leafNode.idNameleafNodeIdNames = list(currentHeaderTable.keys())leafNodeIdNames.reverse()for leafNodeIdName in leafNodeIdNames:leafNode = currentHeaderTable[leafNodeIdName]currentFrequenSet = miningFrequentSet(leafNode)currentFrequenSet = [subset + [targetIdName] for subset in currentFrequenSet]   #以指定频繁项结尾frequentSet.extend(currentFrequenSet)frequentSet.append([targetIdName])  #加上自己return frequentSetdef loadDatas(testdata=1):if testdata==1:dataSets = [['牛奶','面包',1],['面包','尿布','啤酒',1],['牛奶','尿布','啤酒',1],['牛奶','面包','尿布','啤酒',1],['牛奶','面包','尿布',1]]else:dataSets = [['A','C','E','B','F',1],\['A','C','G',1],\['E',1],\['A','C','E','G','D',1],\['A','C','E','G',1],\['E',1],\['A','C','E','B','F',1],\['A','C','D',1],\['A','C','E','G',1],\['A','C','E','G',1]]return dataSets
    # L1 = {'牛奶':4,'面包':4,'尿布':4,'啤酒':3}
    def main():dataSets = loadDatas(1)ft = FpTree(dataSets,3)L1 = ft.scan1()Ls = ft.scan2(L1)# print(L1)# print(Ls)headerTable,root = ft.createFpTree(L1,Ls)           #创建Fp树currentHeaderTable,currentRoot = headerTable,rootnodeIdNames = list(currentHeaderTable.keys())nodeIdNames.reverse()#倒序,即按照项出现次数升序排列,先挖掘出现次数少的# print(nodeIdNames)for idName in nodeIdNames:leafNode = currentHeaderTable[idName].nextprint(miningFrequentSet(leafNode),end='\n')
    if __name__ == "__main__":main()

关联规则挖掘之FPGrowth算法实现相关推荐

  1. r语言实现关联分析--关联规则挖掘(Apriori算法) (r语言预测学习笔记)

    r语言实现关联分析–关联规则挖掘 关联分析: 引子: 我们一般把一件事情发生,对另一间事情也会产生影响的关系叫做关联.而关联分析就是在大量数据中发现项集之间有趣的关联和相关联系(形如"由于某 ...

  2. 机器学习推荐算法之关联规则Apriori与FP-Growth算法详解

    目录 Apriori算法介绍 计算菜品间的关联度 Apriori算法不足 FP-Growth算法 频繁项的挖掘

  3. 数据挖掘-关联规则挖掘之Apriori算法

    目录 一.定义 二.Apriori定律 Apriori定律1): Apriori定律2): 三.Apriori算法工作原理: 四.Apriori算法特点: 一.定义 Apriori是由a priori ...

  4. 数据挖掘之关联规则挖掘之SETM算法实现

    具体算法实现细节请查看IEEE论文:Set-oriented mining for association rules in relational databases SETM算法代码+测试用例+具体 ...

  5. 数据挖掘算法之-关联规则挖掘(Association Rule)(购物篮分析)

    在各种数据挖掘算法中,关联规则挖掘算是比较重要的一种,尤其是受购物篮分析的影响,关联规则被应用到很多实际业务中,本文对关联规则挖掘做一个小的总结. 首先,和聚类算法一样,关联规则挖掘属于无监督学习方法 ...

  6. 数据挖掘算法之-关联规则挖掘(Association Rule)

    在数据挖掘的知识模式中,关联规则模式是比较重要的一种.关联规则的概念由Agrawal.Imielinski.Swami 提出,是数据中一种简单但很实用的规则.关联规则模式属于描述型模式,发现关联规则的 ...

  7. 关联规则挖掘算法研究

    文章目录 一. 问题背景与意义: 二. 问题定义 三. 经典的关联规则挖掘算法 3.1 层次算法 3.1.1 Apriori算法描述 3.1.2 FP-Growth算法 3.2 图挖掘算法 3.3 并 ...

  8. 【数据挖掘】关联规则挖掘 Apriori 算法 ( 关联规则简介 | 数据集 与 事物 Transaction 概念 | 项 Item 概念 | 项集 Item Set | 频繁项集 | 示例解析 )

    文章目录 一. 关联规则挖掘简介 二. 数据集 与 事物 ( Transaction ) 概念 三.项 ( Item ) 概念 四.项集 ( Item Set ) 概念 五.频繁项集 六.数据集.事物 ...

  9. 数据挖掘原理与算法_技术分享|大数据挖掘算法之FPGrowth算法

    程一舰 数据技术处 我们常说我们生活在信息时代,实际上,我们更多的还是生活在数据时代.因为从过去到现在累积了大量的数据,对数据的挖掘和分析也仅是从最近几年大数据和人工智能技术的发展而兴起.我们对现有数 ...

  10. 关联规则(挖掘),数据关联(融合)

    3月9日07:49|  新浪博客|  来源:时空交互|  查看原文> 关联式规则 关联式规则(Association Rules, AR),又称关联规则,是数据挖掘的一个重要课题,用于从大量数据 ...

最新文章

  1. OpenCV+python:直方图的应用(一)
  2. python字典{:4}_Python字典101:详细的视觉介绍
  3. 第一个 Hello Worlds ,打开 opener_server.pl 的大门。OPener_Server 第 3 弹
  4. 分类系列之感知器学习算法PLA 和 口袋算法Pocket Algorithm
  5. 线性代数导论4——A的LU分解
  6. Cordova插件中JavaScript代码与Java的交互细节介绍
  7. JAVA当中Calendar类打印日历表单
  8. 163邮箱有传真服务器,网易企业邮箱邮件传真功能使用说明
  9. 封装jquery的ajax,便于加载等待提示框
  10. 刚写的代码,就变成了遗留系统?
  11. Python中的解决中文字符编码的问题
  12. excel匹配_Excel常用的关联匹配函数
  13. kafka maven 依赖_Flink的sink实战之二:kafka
  14. mysql5.7版本的新特性_MySQL 5.7版本新特性(一)
  15. (一) pyhon 基础语法(数值 字符串 元组 列表 字典)
  16. OEL修改字符集失败 -bash: /root: is a directory
  17. JavaScript高级程序设计学习笔记--BOM
  18. centos7安装有道词典
  19. echarts实现左右滑动
  20. cve-2021-22205复现

热门文章

  1. 煤改气加剧雾霾”“石油焦是祸首”等谣言,你中招了吗?
  2. 订单同步有技巧,双十一高峰不再怕
  3. 大学生创新创业训练计划项目(信息类)申报书撰写
  4. OpenCV基础(18)使用 OpenCV 和 Python 进行自动色彩校正
  5. talib python文档_talib 中文文档(三):talib 方法大全
  6. JVM内存模型和结构详解(五大模型图解)
  7. android 图片合成pdf文件,教你怎么把多张图片合成一个pdf文件
  8. iOS —— 极光推送和极光IM
  9. IEC61850——开山篇
  10. python 微信公众号发文章_Python 微信公众号文章爬取