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

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 c1


def 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中,就把对应元素后面加 1
                cut_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_dataK


def 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 ck


def 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)  # 把新产生的候选集假如F
        sup_data.update(sup_k)  # 字典更新,加入新得出的数据
        K += 1
    return 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)  # 最小支持度设置为2

    print("具有关联的商品是{}".format(F))  # 带变量的字符串输出,必须为字典符号表示
    print('------------------')
    print("对应的支持度为{}".format(sup_data))
    

在这里插入图片描述

2、F-Growth算法

import copy

class 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.idName

    def getAllChildsName(self):  # 获取该节点所有孩子节点的名字
        ch = self.childs
        keys = list(ch.keys())
        names = []
        for i in keys:
            names.append(list(i))
        return names

    def printAllInfo(self):  # 打印该节点所有信息
        print(self.idName, self.idCount, list(self.childs.keys()), list(self.parent.keys()), self.nextCommonId.items())

    @classmethod
    def 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])

    @classmethod
    def 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_c1


def createRootNode():  # 创建一个根节点
    rootNode = FpNode('root', {}, {}, {}, -1)  # name, childs, parent, nextCommonId, idCount
    return rootNode


def buildTree(database, rootNode, f1):  # 构建频繁模式树 FpTree

    for 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 + 1
                    next.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 = temp2
                        present.idCount = present.idCount + 1  # count+1即可
                    else:
                        next.idName = str(j[0])  # 对新插入的节点赋值
                        next.idCount = next.idCount + 1
                        next.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 = next
                        next = 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] = 0

    return indexTableHead


def getNewRecord(idK, **indexTableHead):  # 得到新的数据记录
    newData = []
    address = indexTableHead[idK]

    while address != 0:
        times = 0
        times = 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 = newRootNode
        while 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,a
            t = 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, freAllDic


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)

    freAll, freAllDic = FpGrowth(testList, minSup=3)  # 设置最小支持度为3

    print("频繁项集", 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算法更快速。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Apriori算法FP-Growth算法都是用于频繁项集挖掘的经典算法,它们都可以用来发现数据集中的频繁项集。 Apriori算法的基本思想是利用集合的逐层包含关系,从而发现频繁项集。该算法首先扫描数据集,计算出所有项的支持,然后利用支持和最小支持阈值剪枝,得到一组频繁1项集。然后,利用频繁1项集生成所有频繁2项集,再用频繁2项集生成频繁3项集,依次类推,直到不能再生成更多的频繁项集为止。 FP-Growth算法则是一种基于树形结构的频繁项集挖掘算法。该算法首先构建一个称为FP树的数据结构,并将所有事务按照频繁项的顺序插入到FP树中。然后,利用FP树的结构和头指针表,快速地发现所有的频繁项集。与Apriori算法不同的是,FP-Growth算法不需要生成候选项集,因此可以大大减少算法的时间和空间复杂。 相比之下,FP-Growth算法具有以下优点: 1. FP-Growth算法不需要生成候选项集,因此可以大大减少算法的时间和空间复杂。 2. FP-Growth算法使用FP树来存储数据,可以更方便地处理数据集中的频繁项集。 3. FP-Growth算法可以处理更大规模的数据集。 但是,由于FP-Growth算法需要构建FP树,因此在处理稀疏数据集时,其效率会下降。而Apriori算法则可以更好地处理稀疏数据集。因此,在实际应用中,我们需要根据具体的问题和数据集的特点来选择合适的算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值