【Python】python实现Apriori算法和FP-growth算法(附源代码)

使用一种你熟悉的程序设计语言,实现(1)Apriori算法和(2)FP-growth算法。

目录

  • 1、Apriori算法
  • 2、F-Growth算法
  • 3、两种算法比较

1、Apriori算法

def item(dataset):  # 求第一次扫描数据库后的 候选集,(它没法加入循环)c1 = []  # 存放候选集元素for x in dataset:  # 就是求这个数据库中出现了几个元素,然后返回for y in x:if [y] not in c1:c1.append([y])c1.sort()# print(c1)return c1def get_frequent_item(dataset, c, min_support):cut_branch = {}  # 用来存放所有项集的支持度的字典for x in c:for y in dataset:if set(x).issubset(set(y)):  # 如果 x 不在 y中,就把对应元素后面加 1cut_branch[tuple(x)] = cut_branch.get(tuple(x),0) + 1  # cut_branch[y] = new_cand.get(y, 0)表示如果字典里面没有想要的关键词,就返回0# print(cut_branch)Fk = []  # 支持度大于最小支持度的项集,  即频繁项集sup_dataK = {}  # 用来存放所有 频繁 项集的支持度的字典for i in cut_branch:if cut_branch[i] >= min_support:  # Apriori定律1  小于支持度,则就将它舍去,它的超集必然不是频繁项集Fk.append(list(i))sup_dataK[i] = cut_branch[i]return Fk, sup_dataKdef get_candidate(Fk, K):  # 求第k次候选集ck = []  # 存放产生候选集for i in range(len(Fk)):for j in range(i + 1, len(Fk)):L1 = list(Fk[i])[:K - 2]L2 = list(Fk[j])[:K - 2]L1.sort()L2.sort()  # 先排序,在进行组合if L1 == L2:if K > 2:  # 第二次求候选集,不需要进行减枝,因为第一次候选集都是单元素,且已经减枝了,组合为双元素肯定不会出现不满足支持度的元素new = list(set(Fk[i]) ^ set(Fk[j]))  # 集合运算 对称差集 ^ (含义,集合的元素在t或s中,但不会同时出现在二者中)# new表示,这两个记录中,不同的元素集合# 为什么要用new? 比如 1,2     1,3  两个合并成 1,2,3   我们知道1,2 和 1,3 一定是频繁项集,但 2,3呢,我们要判断2,3是否为频繁项集# Apriori定律1 如果一个集合不是频繁项集,则它的所有超集都不是频繁项集else:new = set()for x in Fk:if set(new).issubset(set(x)) and list(set(Fk[i]) | set(Fk[j])) not in ck:  # 减枝 new是 x 的子集,并且 还没有加入 ck 中ck.append(list(set(Fk[i]) | set(Fk[j])))# print(ck)return ckdef Apriori(dataset, min_support=2):c1 = item(dataset)  # 返回一个二维列表,里面的每一个一维列表,都是第一次候选集的元素f1, sup_1 = get_frequent_item(dataset, c1, min_support)  # 求第一次候选集F = [f1]  # 将第一次候选集产生的频繁项集放入 F ,以后每次扫描产生的所有频繁项集都放入里面sup_data = sup_1  # 一个字典,里面存放所有产生的候选集,及其支持度K = 2  # 从第二个开始循环求解,先求候选集,在求频繁项集while (len(F[K - 2]) > 1):  # k-2是因为F是从0开始数的     #前一个的频繁项集个数在2个或2个以上,才继续循环,否则退出ck = get_candidate(F[K - 2], K)  # 求第k次候选集fk, sup_k = get_frequent_item(dataset, ck, min_support)  # 求第k次频繁项集F.append(fk)  # 把新产生的候选集假如Fsup_data.update(sup_k)  # 字典更新,加入新得出的数据K += 1return F, sup_data  # 返回所有频繁项集, 以及存放频繁项集支持度的字典if __name__ == '__main__':n = int(input("共有几组数据?"))testList = [[] for i in range(n)]for i in range(n):testList[i] = list(map(str, input().split(' ')))  # 装入数据 二维列表print(testList)F, sup_data = Apriori(testList, min_support=2)  # 最小支持度设置为2print("具有关联的商品是{}".format(F))  # 带变量的字符串输出,必须为字典符号表示print('------------------')print("对应的支持度为{}".format(sup_data))

在这里插入图片描述

2、F-Growth算法

import copyclass FpNode():def __init__(self, name='', childs={}, parent={}, nextCommonId={}, idCount=0):self.idName = name  # 名字self.childs = childs  # 所有孩子结点self.parent = parent  # 父节点self.nextCommonId = nextCommonId  # 下一个相同的 id名字 结点self.idCount = idCount  # id 计数def getName(self):  # 获取该节点名字return self.idNamedef getAllChildsName(self):  # 获取该节点所有孩子节点的名字ch = self.childskeys = list(ch.keys())names = []for i in keys:names.append(list(i))return namesdef printAllInfo(self):  # 打印该节点所有信息print(self.idName, self.idCount, list(self.childs.keys()), list(self.parent.keys()), self.nextCommonId.items())@classmethoddef checkFirstTree(cls, rootNode):  # 前序遍历整个树(这不是二叉树,没有中序遍历)if rootNode is None:return ''# parent1 = rootNode.parent.keys()      #要加一个 强转 ,否则它会变成 Nopetype 型,rootNode.printAllInfo()  # print(rootNode.idName, type(rootNode.parent))  报错 root <class 'NoneType'>if rootNode.childs is not None:keys = list(rootNode.childs.keys())for i in keys:cls.checkFirstTree(rootNode.childs[i])@classmethoddef checkBehindTree(cls, rootNode):  # 后序遍历整个树if rootNode is None:return ''if rootNode.childs is not None:keys = list(rootNode.childs.keys())for i in keys:cls.checkBehindTree(rootNode.childs[i])rootNode.printAllInfo()def scan1_getCand1(database):  # 第一次扫描统计出现的次数c1 = {}  # 候选集for i in database:for j in i:c1[j] = c1.get(j, 0) + 1  # 表示如果字典里面没有想要的关键词,就返回0# print(c1)return c1# 返回排好序的字典# 对数据进行排序,按支持度由大到小排列
def sortData(**d):  # 形参前添加两个 '*'——字典形式  形参前添加一个 '*'——元组形式sortKey = list(d.keys())  # 直接使用sorted(my_dict.keys())就能按key值对字典排序sortValue = list(d.values())length = len(sortKey)for i in range(length - 1):  # 按照支持度大小,由大到小排序的算法for j in (i, length - 1 - 1):  # 必须 -1 (1,len)虽然不包含 len本身 但是数组【len-1】时最后一个元素,必须减去这个元素if sortValue[i] < sortValue[j + 1]:sortValue[i], sortValue[j + 1] = sortValue[j + 1], sortValue[i]  # 如果它的支持度小与另一个,交换位置sortKey[i], sortKey[j + 1] = sortKey[j + 1], sortKey[i]new_c1 = {}  # 存放排完序的数据记录for i in range(length):new_c1[sortKey[i]] = sortValue[i]return new_c1  # 返回排好序的字典# 得到 database 的频繁项集
def getFreq(database, minSup=3, **c1):  # 返回频繁项集,和频繁项集的支持度c1 = scan1_getCand1(database)  # 第一次扫面数据库,求第一次候选集,返回的是字典new_c1 = sortData(**c1)  # 排序,大到小keys = list(new_c1.keys())for i in keys:if new_c1[i] < minSup:  # 若支持度小于最小支持度,则删除该商品del new_c1[i]f1 = []  # 第一次频繁项集new_keys = list(new_c1.keys())for i in new_keys:if [i] not in f1:f1.append([i])  # 每个元素自成一项# print(f1,new_c1)return f1, new_c1def createRootNode():  # 创建一个根节点rootNode = FpNode('root', {}, {}, {}, -1)  # name, childs, parent, nextCommonId, idCountreturn rootNodedef buildTree(database, rootNode, f1):  # 构建频繁模式树 FpTreefor i in database:  # 第二次扫描数据库present = rootNode  # 指向当前节点next = FpNode(name='', childs={}, parent={}, nextCommonId={}, idCount=0)  # 创建一个新节点,并初始化for j in f1:  # 按支持度从大到小的顺序进行构建节点if set(j).issubset(set(i)):  # j如果在i里面if (present.getName() == 'root') and j not in rootNode.getAllChildsName():next.idName = str(j[0])  # 对新创建的节点进行赋值next.idCount = next.idCount + 1next.nextCommonId = {str(j[0]): 0}next.parent.update({rootNode.idName: rootNode})temp = copy.copy(next)rootNode.childs.update({str(j[0]): temp})  # 往它插入父亲节点##print(temp.parent)present = temp  # present = next 这样直接赋值是 引用 ,一定要注意next = FpNode(name='', childs={}, parent={}, nextCommonId={}, idCount=0)  # 创建并初始化下一个新节点else:if j in present.getAllChildsName():  # 如果需要插入的节点已经存在temp2 = present.childs[str(j[0])]present = temp2present.idCount = present.idCount + 1  # count+1即可else:next.idName = str(j[0])  # 对新插入的节点赋值next.idCount = next.idCount + 1next.nextCommonId = {str(j[0]): 0}next.parent.update({present.idName: present})# temp3 = copy.copy(next)present.childs.update({str(j[0]): next})  # 往它插入父亲节点# temp3.childs = {}present = nextnext = FpNode(name='', childs={}, parent={}, nextCommonId={}, idCount=0)# present = next# next = FpNode()# print(rootNode.getAllChildsName())# print('前序遍历如下:')# FpNode.checkFirstTree(rootNode)# print('后序遍历如下:')# FpNode.checkBehindTree(rootNode)return None# 构建线索,填节点的nextCommonId这个属性
def buildIndex(rootNode, d1):  # 传 列表或字典时,列表前,加*, 字典前加 ** 表示传给函数的是一个地址,在函数内部改变这个参数,不会影响到函数外的变量if rootNode is None:return ''next = rootNode  # 指向下一个节点,当前赋值为根节点value = rootNode.idName# print(value)# print(d1[str(value)])             #d1[value] {KeyError}'a'???????????????   如果value是根节点root,就会出错,表中本来就没有root这个值# print(d1)if value != 'root':indexAds1 = {value: d1[value]}if d1[value] == 0:  # 线索构造   我已经把初始化了所有的 nextCommonId 为 {'': 0}# 所以后面只要 这个节点的 nextCommonId字典的值为0,就说明这个字典就是构建的链表链尾d1[value] = next# print(indexAds1)else:while indexAds1[value] != 0:indexAds1 = indexAds1[value].nextCommonId  # 以链表形式把最后一个 表尾元素找出来# print(indexAds1)indexAds1[value] = next  # 这个元素后面加入 当前所在树的这个节点的地址# print(next.nextCommonId)if rootNode.childs is not None:  # 根节点孩子不是null,则对它的每个孩子,依次递归进行线索构建keys = list(rootNode.childs.keys())for i in keys:buildIndex(rootNode.childs[i], d1)def createIndexTableHead(**indexTableHead):  # 创建一个表头,用来构建线索,表头的名字是相应节点的名字keys = list(indexTableHead.keys())# print(keys)for i in keys:indexTableHead[i] = 0return indexTableHeaddef getNewRecord(idK, **indexTableHead):  # 得到新的数据记录newData = []address = indexTableHead[idK]while address != 0:times = 0times = address.idCount  # 当前节点count数l = []  # 临时存放这个分支上的所有节点元素,单个单个存储 二维列表getOneNewR = []  # 和l一样,是l的倒叙,因为l本来是倒叙的,现在把它改成倒叙# print(list(address.parent.keys())[0])  #这样写才是 字符 c  而不是 'c'nextAdress = copy.copy(address)  # 一个指针,指向父亲节点,初始化为表头第一个的地址while list(nextAdress.parent.keys())[0] != 'root':  # 该节点发父亲节点不是根节点。则# print(address.parent)l.append(list(nextAdress.parent.keys()))  # 把它的父亲节点加入l中parentIdName = list(nextAdress.parent.keys())[0]  # 父亲节的名字nextAdress = nextAdress.parent[parentIdName]  # 指向该节点父亲节点if l != []:for j in l:getOneNewR.append(j[0])if getOneNewR != []:for k in range(times):  # 若最后的那个 idk 计数为多次,要把它多次添加到新产生的newData中newData.append(list(getOneNewR))# 把得到的记录加入新的数据集中address = address.nextCommonId[idK]  # 指向下一个表头元素的开始地址,进行循环return newData#    idK表示当前新产生的数据集是在去除这个字母后形成的,  fk是去除掉idk后,新的第一次频繁项集  dk是fk的支持度
def getAllConditionBase(newDatabase, idK, fk, minSup, **dk):  # 返回条件频繁项集 base, 和支持度if fk != []:  # 频繁项集非空newRootNode = createRootNode()  # 创建新的头节点buildTree(newDatabase, newRootNode, fk)# newIndexTableHead = {}  #创建新表头newIndexTableHead = createIndexTableHead(**dk)  # **dk 就是传了个值,给了它一个拷贝,修改函数里面的这个拷贝,不会影响到外面的这个变量的值buildIndex(newRootNode, newIndexTableHead)else:return [idK], {idK: 9999}  # 频繁项集是空的,则返回idk的名字,支持度设为最大值9999,这样会出现一些问题,最后已经解决了,在主函数代码中有表现出来if len(newRootNode.getAllChildsName()) < 2:  # 新的FpTree只有1条分支,(这里只认为根节点只有1个孩子,就说他只有一条分支)# 若是实际数据,就不能这样写了,应当在写一个函数,从根节点开始遍历,确保每个节点都只有1个孩子,才能认为只有1条分支base = [[]]  # 条件基node = newRootNodewhile node.getAllChildsName() != []:  # 当前节点有孩子节点childName = list(node.childs.keys())  # 一个列表,孩子节点的所有名字,其实就1个孩子,前面已经判断了是单节点base.append(list(childName[0]))  # 把孩子节点加入条件基# print(node.childs)# print(childName)node = node.childs[childName[0]]  # 指向下一个节点# print(base)itemSup = {node.idName: node.idCount}  # 这一条分支出现的次数,最后求频繁项集支持度需要用到# print(itemSup)return base, itemSup  # 返回条件基,还有这一条分支出现的次数,else:  # 分支不止1条,进行递归查找,重复最开始的操作base = [[]]for commonId in fk[-1::-1]:  # 倒叙进行newIdK = str(commonId[0])newDataK = getNewRecord(newIdK, **newIndexTableHead)  # 传入这个表头的一个拷贝fk2, dk2 = getFreq(newDataK, minSup)conditionBase, itemSup = getAllConditionBase(newDataK, newIdK, fk2, minSup, **dk2)  # 得到该条件基下的条件基,及各个分支出现次数# 递归进行base.append(conditionBase)return base, itemSup# FpGrowth算法本身(Frequent Pattern Growth—-频繁模式增长)
def FpGrowth(database, minSup=3):f1, d1 = getFreq(database, minSup)  # 求第一次频繁项集,并返回一个字典存放支持度,且按大到小排序,返回频繁项和存放频繁项支持度的字典rootNode = createRootNode()  # 创建根节点# print(f1,d1)        #[['a'], ['b'], ['c'], ['d']]      {'a': 4, 'b': 4, 'c': 4, 'd': 3}# 第一步建造树buildTree(database, rootNode, f1)# indexTableHead = {}     #创建线索的表头,一个链表indexTableHead = createIndexTableHead(**d1)  # **d1 就是传了个值,给了它一个拷贝,修改函数里面的这个拷贝,不会影响到外面的这个变量的值buildIndex(rootNode, indexTableHead)  # 创建线索,用这个表头# print('构建线索后,前序遍历如下:')# FpNode.checkFirstTree(rootNode)# print('构建线索后,后序遍历如下:')# FpNode.checkBehindTree(rootNode)freAll = []  # 所有频繁项集freAllDic = {}  # 所有频繁项集的支持度# 第二步    进行频繁项集的挖掘,从表头header的最后一项开始。for commonId in f1[-1::-1]:  # 倒叙 从支持度小的到支持度大的,进行挖掘idK = str(commonId[0])newDataK = getNewRecord(idK, **indexTableHead)  # 传入这个表头的一个拷贝, 函数返回挖掘出来的新记录fk, dk = getFreq(newDataK, minSup)  # 对新数据集求频繁项集# print(fk,dk)base, itemSup = getAllConditionBase(newDataK, idK, fk, minSup, **dk)  # 得到当前节点的条件频繁模式集,返回# 有可能会发生这样一种情况,条件基是 a ,然后fk,dk为空,结果这个函数又返回了 a,那么最后的结果中,就会出现 a,a  这种情况,处理方法请往下看# print(base,idK)for i in base:# print(i)t = list(i)t.append(idK)t = set(t)  # 为了防止出现 重复 的情况,因为我的getAllConditionBase(newDataK, idK, fk, minSup, **dk)方法的编写,可能会形成重复,如   a,at = list(t)freAll.append(t)itemSupValue = list(itemSup.values())[0]x = tuple(t)  # 列表不能做字典的关键字,因为他可变,,而元组可以# <class 'list'>: ['c', 'd']# print(t[0])     # t是列表,字典的关键字不能是可变的列表, 所以用 t[0] 来取出里面的值freAllDic[x] = min(itemSupValue, d1[idK])# print(freAll)# print(freAllDic)return freAll, freAllDicif __name__ == '__main__':n = int(input("共有几组数据?"))testList = [[] for i in range(n)]for i in range(n):testList[i] = list(map(str, input().split(' ')))  # 装入数据 二维列表print("原始数据:", testList)freAll, freAllDic = FpGrowth(testList, minSup=3)  # 设置最小支持度为3print("频繁项集", freAll)print("各个频繁项集的支持度依次为:")for i in freAllDic.keys():print(i, ":", freAllDic[i])

在这里插入图片描述

3、两种算法比较

在以下情况下,FP-growth算法Apriori算法好:
数据量大。因为Apriori算法需要生成大量的候选项集,并对每个候选项集进行频繁项集的计数,这会消耗大量的时间和内存,而FP-growth算法通过构建FP树,将数据集压缩成了一个紧凑的数据结构,大大减少了存储空间和计算时间的开销。
数据分布不均匀。因为Apriori算法在生成候选项集时,需要遍历数据集多次,对于数据分布不均匀的情况,频繁项集的计算会受到影响。而FP-growth算法通过构建FP树,将频繁项集的计算转化为对FP树的遍历,不受数据分布的影响。
最小支持度阈值较低。因为Apriori算法在生成候选项集时,需要对每个候选项集进行频繁项集的计数,当最小支持度阈值较低时,需要计算大量的候选项集,导致计算时间较长。而FP-growth算法通过构建FP树,只需要遍历一次数据集,计算频繁项集,所以FP-growth算法更快速。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/535325.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCASCADE开发指南<四>:OCC 数据类型和句柄

一个软件首先要规定能处理的数据类型&#xff0c; 其次要实现三项最基本的功能——引用管理、内存管理和异常管理。在 OCC 中&#xff0c;这三项功能分别对应基础类中的句柄、内存管理器和异常类。 1 数据类型 在基本概念篇里&#xff0c;已经介绍了 OCC 数据类型的分类&…

Linux:好用的Linux指令

进程的Linux指令 1.查看进程信息 ​​​​ps ajx | head -1 && ps ajx | grep 进程名创建一个进程后输入上述代码&#xff0c;会打印进程信息&#xff0c;当我们在code.exe中写入打印pid&#xff0c;ppid&#xff0c;这里也和进程信息一致。 while :; do ps ajx | he…

Python语言在编程业界的地位——《跟老吕学Python编程》附录资料

Python语言在编程业界的地位——《跟老吕学Python编程》附录资料 ⭐️Python语言在编程业界的地位2024年3月编程语言排行榜&#xff08;TIOBE前十&#xff09; ⭐️Python开发语言开发环境介绍1.**IDLE**2.⭐️PyCharm3.**Anaconda**4.**Jupyter Notebook**5.**Sublime Text** …

机器学习——过拟合问题、正则化解决法

过拟合的基本概念 欠拟合&#xff1a;假设函数没有很好的拟合训练集数据&#xff0c;也称这个假设函数有高偏差&#xff1b; 过拟合&#xff1a;过拟合也称为高方差。在假设函数中添加高阶多项式&#xff0c;让假设函数几乎能完美的拟合每个样本数据点&#xff0c;这看起来很…

使用ubuntu搭建hadoop伪分布全过程图解

目录 1. 安装jdk 2. 添加java环境变量 3. 设置免密登录 4. 安装hadoop 5. 添加hadoop环境变量 6. 修改hdoop-env.sh文件 7. 修改core-site.xml文件 8. 修改yarn-site.xml文件 9. 修改mapred-site.xml文件 10. 修改hdfs-site.xml文件 11. 进行授权 12. 创建namenode…

【论文精读】Transformer:Attention Is All You Need

《动手学深度学习》关于Transformer和注意力机制的笔记 李沐《动手学深度学习》注意力机制 文章目录 《动手学深度学习》关于Transformer和注意力机制的笔记一、文章概览&#xff08;一&#xff09;摘要&#xff08;二&#xff09;结论部分&#xff08;三&#xff09;引言&am…

java基础2-常用API

常用API Math类 帮助我们进行数学计算的工具类。 里面的方法都是静态的。 3.常见方法如下&#xff1a; abs:获取绝对值 absExact:获取绝对值 ceil:向上取整 floor:向下取整 round:四舍五入 max:获取最大值 …

第十四届蓝桥杯蜗牛

蜗牛 线性dp 目录 蜗牛 线性dp 先求到达竹竿底部的状态转移方程 求蜗牛到达第i根竹竿的传送门入口的最短时间​编辑 题目链接&#xff1a;蓝桥杯2023年第十四届省赛真题-蜗牛 - C语言网 关键在于建立数组将竹竿上的每个状态量表示出来&#xff0c;并分析出状态转移方程 in…

游泳防水耳机排名,高性价比国际游泳耳机品牌排行榜前十名

随着科技的飞速发展&#xff0c;运动游泳耳机已经成为当代运动时尚和游泳爱好者的必备装备之一。在游泳训练或是户外运动时&#xff0c;一款高性价比的游泳耳机不仅能带给我们高品质的音乐体验&#xff0c;还能有效防水防汗&#xff0c;陪伴我们享受运动带来的乐趣。为了帮助大…

视频素材网站哪个好?推荐几个高清无水印的短视频素材网

小伙伴们&#xff0c;如果你也是短视频的狂热爱好者&#xff0c;想要制作出优质满分的短视频作品&#xff0c;但苦于不知道从哪儿搞来那些高清无水印的素材&#xff0c;那今天你就来对地方啦&#xff01;我这里有几个绝佳的素材网站推荐给你&#xff0c;让你的创作源源不断。 …

Kamailio的SIP服务的性能

官方的性能报告&#xff1a; Kamailio (OpenSER) 1.2.0 - Transaction Module and User Location Performance Tests 如下的提取的性能参数也是基于官方的性能报告&#xff0c;信令走的UDP&#xff0c;作为做系统方案的参照&#xff0c;Kamailio的性能还是非常&#xff0c;非常…

代码学习记录18

随想录日记part18 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.13 主要内容&#xff1a;今天的主要内容是二叉树的第七部分&#xff0c;主要涉及二叉搜索树的最近公共祖先 &#xff1b;二叉搜索树的最近公共祖先&#xff1b;删除二叉搜索树中的节点 。 23…