关联分析---Apriori算法和FPGrowth算法挖掘规则计算频繁项间的置信度


博主在进行了Apriori算法和FPgrowth算法的学习与完成置信度计算之后写下此篇文章,没有过多的理论介绍,理论学习可以 点击这里进行查看,此篇文章主要写出实现功能的代码,有些中间结果并未输出。但最终的置信度是进行了输出的。

数据准备

此处演示所用的数据集为书中作业题目的数据集:
dataset = [
[‘M’, ‘O’, ‘N’, ‘K’, ‘E’, ‘Y’],
[‘D’, ‘O’, ‘N’, ‘K’, ‘E’, ‘Y’],
[‘M’, ‘A’, ‘K’, ‘E’],
[‘M’, ‘U’, ‘C’, ‘K’, ‘Y’],
[‘C’, ‘O’, ‘O’, ‘K’, ‘I’, ‘E’]
]

另外,你使用的数据也可以来至文本文件,最终的结果也可以保存至文件中,如有需要可以联系我!微:18385457714

Apriori算法:

apriori算法流程

输入:数据集合D,支持度阈值α
输出:最大的频繁k项集
    1)扫描整个数据集,得到所有出现过的数据,作为候选频繁1项集。k=1,频繁0项集为空集。
    2)挖掘频繁k项集
      a) 扫描数据计算候选频繁k项集的支持度
      b) 去除候选频繁k项集中支持度低于阈值的数据集,得到频繁k项集。如果得到的频繁k项集为空,则直接返回频繁k-1项集的集合作为算法结果,算法结束。如果得到的频繁k项集只有一项,则直接返回频繁k项集的集合作为算法结果,算法结束。
      c) 基于频繁k项集,连接生成候选频繁k+1项集。
    3) 令k=k+1,转入步骤2。

实现代码

import itertools

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("----------------候选集--------------------")
    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]
    print("=============频繁项集==============")
    print(Fk)
    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  # 返回所有频繁项集, 以及存放频繁项集支持度的字典


def generate_association_rules(patterns, confidence_threshold):
    """
    Given a set of frequent itemsets, return a dict
    of association rules in the form
    {(left): ((right), confidence)}
    """
    rules = []
    for itemset in patterns.keys():
        upper_support = patterns[itemset]

        for i in range(1, len(itemset)):
            for antecedent in itertools.combinations(itemset, i):
                antecedent = tuple(sorted(antecedent))
                consequent = tuple(sorted(set(itemset) - set(antecedent)))

                if antecedent in patterns:
                    lower_support = patterns[antecedent]
                    confidence = float(upper_support) / lower_support

                    if confidence >= confidence_threshold:
                        rules.append([antecedent, consequent, confidence])
    return rules


def printPatterns(patterns):
    keys1 = list(patterns.keys())
    values1 = list(patterns.values())
    for i in range(len(keys1)):
        keys1[i] = list(keys1[i])

    for i in range(len(keys1)):
        for j in range(len(keys1[i])):
            print(keys1[i][j], end=" ")
        for i2 in range(10 - 2 * len(keys1[i])):
            print(" ", end="")
        print(" : ", end="")
        print(values1[i], end="\n")


def printRules2(rlues):
    keys1 = []
    values1 = []
    for i in range(len(rules)):
        keys1.append(list(rules[i][0]))
        values1.append(list(rules[i][1]))

    for i in range(len(keys1)):
        for j in range(len(keys1[i])):
            print(keys1[i][j], end=" ")
        for i2 in range(10 - 2 * len(keys1[i])):
            print(" ", end="")
        print(" --> ", end="")
        for i1 in range(len(values1[i])):
            print(values1[i][i1], end=" ")
        for i3 in range(10 - 2 * len(values1[i])):
            print(" ", end="")
        print(":  " + str(rules[i][2]), end="\n")


if __name__ == '__main__':
    dataset = [
        ['M', 'O', 'N', 'K', 'E', 'Y'],
        ['D', 'O', 'N', 'K', 'E', 'Y'],
        ['M', 'A', 'K', 'E'],
        ['M', 'U', 'C', 'K', 'Y'],
        ['C', 'O', 'O', 'K', 'I', 'E']
    ]  # 装入数据 二维列表
    F, sup_data = Apriori(dataset, min_support=3)  # 最小支持度设置为3
    rules = generate_association_rules(sup_data, 0.8)  # 置信度(条件概率)删选

    print("各频繁集及其出现次数如下", end="\n")
    printPatterns(sup_data)
    print('---------------------------------')
    print("各强关联规则及其置信度如下", end="\n")
    printRules2(rules)


执行结果示例:
在这里插入图片描述
在这里插入图片描述

FP-growth算法

FP-growth算法优点

FP-growth算法比Apriori算法效率更高,在整个算法执行过程中,只需遍历数据集2次,就能够完成频繁模式发现,其发现频繁项集的基本过程如下:
第一次:构建FP树
第二次:从FP树中挖掘频繁项集

FP-growth算法流程

FP-growth的一般流程如下:
1:先扫描一遍数据集,得到频繁项为1的项目集,定义最小支持度(项目出现最少次数),删除那些小于最小支持度的项目,然后将原始数据集中的条目按项目集中降序进行排列。
2:第二次扫描,创建项头表(从上往下降序),以及FP树。
3:对于每个项目(可以按照从下往上的顺序)找到其条件模式基(CPB,conditional patten base),递归调用树结构,删除小于最小支持度的项。如果最终呈现单一路径的树结构,则直接列举所有组合;非单一路径的则继续调用树结构,直到形成单一路径即可。

实现代码

# -*- coding: utf-8 -*-

from tqdm import tqdm

def load_data():  # 根据路径加载数据集
    ans = []  # 将数据保存到该数组
    reader = [
        ['M', 'O', 'N', 'K', 'E', 'Y'],
        ['D', 'O', 'N', 'K', 'E', 'Y'],
        ['M', 'A', 'K', 'E'],
        ['M', 'U', 'C', 'K', 'Y'],
        ['C', 'O', 'O', 'K', 'I', 'E']
    ]
    for row in reader:
        row = list(set(row))  # 去重,排序
        row.sort()
        ans.append(row)  # 将添加好的数据添加到数组
    return ans  # 返回处理好的数据集,为二维数组

def show_confidence(rule):
        index = 1
        for item in rule:
            s = " {:<4d}  {:.3f}        {}=>{}".format(index, item[2], str(list(item[0])), str(list(item[1])))
            index += 1
            print(s)

class Node:
    def __init__(self, node_name, count, parentNode):
        self.name = node_name
        self.count = count
        self.nodeLink = None  # 根据nideLink可以找到整棵树中所有nodename一样的节点
        self.parent = parentNode  # 父亲节点
        self.children = {}  # 子节点{节点名字:节点地址}


class Fp_growth_plus():

    def data_compress(self, data_set):
        data_dic = {}
        for i in data_set:
            if frozenset(i) not in data_dic:
                data_dic[frozenset(i)] = 1
            else:
                data_dic[frozenset(i)] += 1
        return data_dic

    def update_header(self, node, targetNode):  # 更新headertable中的node节点形成的链表
        while node.nodeLink != None:
            node = node.nodeLink
        node.nodeLink = targetNode

    def update_fptree(self, items, count, node, headerTable):  # 用于更新fptree
        if items[0] in node.children:
            # 判断items的第一个结点是否已作为子结点
            node.children[items[0]].count += count
        else:
            # 创建新的分支
            node.children[items[0]] = Node(items[0], count, node)
            # 更新相应频繁项集的链表,往后添加
            if headerTable[items[0]][1] == None:
                headerTable[items[0]][1] = node.children[items[0]]
            else:
                self.update_header(headerTable[items[0]][1], node.children[items[0]])
        # 递归
        if len(items) > 1:
            self.update_fptree(items[1:], count, node.children[items[0]], headerTable)

    def create_fptree(self, data_dic, min_support, flag=False):  # 建树主函数
        '''
        根据data_dic创建fp树
        header_table结构为
        {"nodename":[num,node],..} 根据node.nodelink可以找到整个树中的所有nodename
        '''
        item_count = {}  # 统计各项出现次数
        for t in data_dic:  # 第一次遍历,得到频繁一项集
            for item in t:
                if item not in item_count:
                    item_count[item] = data_dic[t]
                else:
                    item_count[item] += data_dic[t]
        headerTable = {}
        for k in item_count:  # 剔除不满足最小支持度的项
            if item_count[k] >= min_support:
                headerTable[k] = item_count[k]

        freqItemSet = set(headerTable.keys())  # 满足最小支持度的频繁项集
        if len(freqItemSet) == 0:
            return None, None
        for k in headerTable:
            headerTable[k] = [headerTable[k], None]  # element: [count, node]
        tree_header = Node('head node', 1, None)
        if flag:
            ite = tqdm(data_dic)
        else:
            ite = data_dic
        for t in ite:  # 第二次遍历,建树
            localD = {}
            for item in t:
                if item in freqItemSet:  # 过滤,只取该样本中满足最小支持度的频繁项
                    localD[item] = headerTable[item][0]  # element : count
            if len(localD) > 0:
                # 根据全局频数从大到小对单样本排序
                order_item = [v[0] for v in sorted(localD.items(), key=lambda x: x[1], reverse=True)]
                # 用过滤且排序后的样本更新树
                self.update_fptree(order_item, data_dic[t], tree_header, headerTable)
        return tree_header, headerTable

    def find_path(self, node, nodepath):
        '''
        递归将node的父节点添加到路径
        '''
        if node.parent != None:
            nodepath.append(node.parent.name)
            self.find_path(node.parent, nodepath)

    def find_cond_pattern_base(self, node_name, headerTable):
        '''
        根据节点名字,找出所有条件模式基
        '''
        treeNode = headerTable[node_name][1]
        cond_pat_base = {}  # 保存所有条件模式基
        while treeNode != None:
            nodepath = []
            self.find_path(treeNode, nodepath)
            if len(nodepath) > 1:
                cond_pat_base[frozenset(nodepath[:-1])] = treeNode.count
            treeNode = treeNode.nodeLink
        return cond_pat_base

    def create_cond_fptree(self, headerTable, min_support, temp, freq_items, support_data):
        # 最开始的频繁项集是headerTable中的各元素
        freqs = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1][0])]  # 根据频繁项的总频次排序
        for freq in freqs:  # 对每个频繁项
            freq_set = temp.copy()
            freq_set.add(freq)
            freq_items.add(frozenset(freq_set))
            if frozenset(freq_set) not in support_data:  # 检查该频繁项是否在support_data中
                support_data[frozenset(freq_set)] = headerTable[freq][0]
            else:
                support_data[frozenset(freq_set)] += headerTable[freq][0]

            cond_pat_base = self.find_cond_pattern_base(freq, headerTable)  # 寻找到所有条件模式基
            # 创建条件模式树
            cond_tree, cur_headtable = self.create_fptree(cond_pat_base, min_support)
            if cur_headtable != None:
                self.create_cond_fptree(cur_headtable, min_support, freq_set, freq_items, support_data)  # 递归挖掘条件FP树

    def generate_L(self, data_set, min_support):
        data_dic = self.data_compress(data_set)
        freqItemSet = set()
        support_data = {}
        tree_header, headerTable = self.create_fptree(data_dic, min_support, flag=True)  # 创建数据集的fptree
        # 创建各频繁一项的fptree,并挖掘频繁项并保存支持度计数
        self.create_cond_fptree(headerTable, min_support, set(), freqItemSet, support_data)

        max_l = 0
        for i in freqItemSet:  # 将频繁项根据大小保存到指定的容器L中
            if len(i) > max_l: max_l = len(i)
        L = [set() for _ in range(max_l)]
        for i in freqItemSet:
            L[len(i) - 1].add(i)
        for i in range(len(L)):
            print("frequent item {}:{}".format(i + 1, len(L[i])))
        return L, support_data

    def generate_R(self, data_set, min_support, min_conf):
        L, support_data = self.generate_L(data_set, min_support)
        rule_list = []
        sub_set_list = []
        for i in range(0, len(L)):
            for freq_set in L[i]:
                for sub_set in sub_set_list:
                    if sub_set.issubset(
                            freq_set) and freq_set - sub_set in support_data:  # and freq_set-sub_set in support_data
                        conf = support_data[freq_set] / support_data[freq_set - sub_set]
                        big_rule = (freq_set - sub_set, sub_set, conf)
                        if conf >= min_conf and big_rule not in rule_list:
                            # print freq_set-sub_set, " => ", sub_set, "conf: ", conf
                            rule_list.append(big_rule)
                sub_set_list.append(freq_set)
        rule_list = sorted(rule_list, key=lambda x: (x[2]), reverse=True)
        return rule_list

if __name__ == "__main__":

    min_support = 3  # 最小支持度
    min_conf = 0.8  # 最小置信度

    data_set = load_data()
    print(data_set)
    fp = Fp_growth_plus()
    rule_list = fp.generate_R(data_set, min_support, min_conf)
    print("confidence:")
    show_confidence(rule_list)

执行结果示例
在这里插入图片描述

如果你刚好需要使用,那么修改为你需要的数据集、最小支持度,最小置信度即可,
数据集、最小支持度和置信度的位置
在这里插入图片描述

希望能对你有用,欢迎评论和指出错误,欢迎一键三连,就不设置收费才能看了。希望大家多多支持!!!此篇文章也有参考借鉴其他大佬的文章进行修改创造,如有侵权,请联系删除。

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

似懂非dong

你的肯定使我加倍努力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值