行业资讯
📅 2026/9/2 3:14:38
Apriori算法从零实现:Python关联规则挖掘与频繁项集详解
简介这是一份面向数据分析初学者与数据挖掘从业者的Apriori算法Python实现资源包内含可直接运行的关联规则挖掘代码与配套交易数据集。资源重点解决频繁项集生成、候选集剪枝及支持度、置信度计算等核心问题适合用于学习经典关联规则算法、课程实验或小规模商业数据探索。压缩包共2个文件包含1个Python脚本和1个txt数据文件脚本基于apyori库实现算法流程txt文件提供示例交易数据可直接加载测试包体仅3KB轻量易用。目前已有9647人学习下载受众认可度较高。通过本资源读者可以获得完整可复现的Apriori算法实现代码、数据格式说明及调参思路能够快速理解算法原理并迁移到自己的数据集上为后续掌握FP-Growth等进阶算法打下基础。 Apriori算法是关联规则挖掘领域最经典的算法之一搞数据分析、做推荐系统、研究用户购物行为的人基本都绕不开它。用Python实现Apriori并不是一件难事但如果你只是调一下mlxtend这样的现成库很难真正理解频繁项集是怎么一步步“长”出来的更别提遇到格式乱糟糟的真实数据集时怎么排查问题。这篇文章我从零手写一遍Apriori附带一份可以直接运行的数据集把每一段代码背后的逻辑都拆开讲清楚包括支持度、置信度怎么算频繁项集如何逐层生成关联规则怎么提取以及我在实际调试中碰到的坑。适合刚接触数据挖掘的初学者也适合想自己动手实现一遍算法的开发者。1. 算法核心原理Apriori到底在算什么1.1 关联规则挖掘解决什么问题关联规则挖掘最早源自一个很经典的场景——超市购物篮分析。顾客买了啤酒之后是不是更容易同时买尿布如果能在海量交易记录里找出这种“组合出现的规律”超市就能优化货架摆放、做捆绑促销电商平台也能做交叉推荐。用技术的话说我们面对的数据是若干条交易记录每条记录里包含若干个商品。算法要做的就是从这些记录中找到“如果买了A那么很可能也会买B”这样的关联规则。这里A和B不是单个商品也可以它们可以是商品集合比如“买了面包和牛奶的人很可能也会买黄油”。1.2 三个度量指标支持度、置信度、提升度要判断一条规则是否有意义不能只看“出现过几次”得用三个指标来衡量。支持度Support表示项集在全部交易中出现的概率。对于规则A→B支持度就是“同时包含A和B的交易数”除以“总交易数”。支持度反映的是规则的普遍性太低说明这条规则只覆盖极少数情况参考价值不大。置信度Confidence是在买了A的前提下买B的概率也就是“同时包含A和B的交易数”除以“包含A的交易数”。置信度反映的是规则的可靠性越高说明A出现时B越容易出现。提升度Lift是置信度除以“B单独出现的概率”。提升度大于1说明A对B有正向促进作用等于1说明A和B相互独立小于1说明A反而抑制B的出现。这个指标很关键它能帮你区分“真实的关联”和“本来就卖得好的商品带来的伪关联”。1.3 Apriori性质与剪枝思想Apriori算法最核心的定理是一条反单调性质如果一个项集是频繁的那么它的所有非空子集也一定是频繁的。反过来如果一个项集不是频繁的那么所有包含它的超集也一定不是频繁的。这个性质的价值在于剪枝。假设“啤酒”出现频率很低低于最小支持度阈值那么“啤酒尿布”这个组合的出现频率只会更低绝不可能成为频繁项集。算法就可以直接跳过所有包含“啤酒”的超集大幅减少计算量。Apriori的整个流程可以概括为先扫描数据集找出所有频繁1项集然后基于频繁1项集生成候选2项集再次扫描数据集过滤出频繁2项集接着生成候选3项集重复这个过程直到无法生成新的频繁项集为止。每一轮都利用上一轮的频繁项集来缩小候选集范围这就是Apriori“逐层搜索、迭代连接”的核心思想。2. 数据集设计与环境准备2.1 数据集格式说明标题里说了“含数据集”我在这里直接给出一份模拟超市交易数据。数据格式很简单每一行代表一笔交易交易中的商品用逗号分隔。这份数据一共20条记录足够完整跑通算法又不会让等待时间太长。1,2,5 2,4 2,3 1,2,4 1,3 2,3 1,3 1,2,3,5 1,2,3 2,5 1,2,4 2,3 2,4 2,3,4 1,2,3 1,2,4 1,5 1,2,3 3,4 1,2,3,5这里每一列是一个商品ID。1代表牛奶2代表面包3代表黄油4代表啤酒5代表鸡蛋。注意同一行内商品ID不会重复因为同一笔交易里不会重复购买同一个商品。如果你要换用自己的数据只要把数据整理成“列表的列表”就行外层列表是交易集合内层列表是单笔交易的商品集合。比如[[1,2,5], [2,4], [2,3]]就是三笔交易。2.2 运行环境与依赖库实现Apriori只需要Python 3.6以上的环境不需要安装任何第三方库纯标准库就能搞定。这一点对新手特别友好你不需要为跑一个demo先折腾一堆依赖。如果后续想分析结果、画图可以装matplotlib做可视化但那不是必须的。为了避免初学者在环境配置上卡壳我先把代码部分做到零依赖等跑通了再考虑可视化。提示从文件读取数据时注意把每行末尾的换行符去掉同时把字符串转换成整数。这一步很容易被忽视后面我会专门讲这个坑。3. 完整代码实现与逐步拆解3.1 候选1项集生成与支持度统计第一步是把数据集中所有出现过的商品找出来生成候选1项集。所谓候选就是“可能成为频繁项集”的集合需要后续扫描数据验证。def create_C1(dataset): C1 set() for transaction in dataset: for item in transaction: C1.add(frozenset([item])) return C1这里我用frozenset而不是普通的set是因为后面要把项集作为字典的键来计数而普通集合是可变的不能作为字典的键。frozenset是不可变集合正好满足要求。接着写一个支持度统计函数输入数据集和候选集输出每个候选项集的支持度以及满足最小支持度的频繁项集。这一步需要完整扫描一遍数据集统计每个候选在多少笔交易中出现过。def calculate_support(dataset, candidates, min_support): support_count {} for transaction in dataset: transaction_set set(transaction) for candidate in candidates: if candidate.issubset(transaction_set): support_count[candidate] support_count.get(candidate, 0) 1 total len(dataset) frequent {} support_data {} for candidate, count in support_count.items(): support count / total if support min_support: frequent[candidate] support support_data[candidate] support return frequent, support_datacalculate_support做了两件事一是计算所有候选的支持度二是筛出超过最小支持度阈值的频繁项集。筛掉不频繁的候选下一轮就不会再基于它们生成更大项集这就是剪枝的第一层。3.2 频繁项集迭代生成频繁1项集拿到之后核心循环就开始了。每一轮要做两件事连接步和剪枝步。连接步是根据当前频繁k-1项集生成候选k项集。规则是两个k-1项集如果它们的前k-2个元素完全相同只有最后一个元素不同就把它们合并成一个k项集。def generate_Ck(frequent_prev, k): candidates set() frequent_list list(frequent_prev) for i in range(len(frequent_list)): for j in range(i 1, len(frequent_list)): set1 list(frequent_list[i]) set2 list(frequent_list[j]) set1.sort() set2.sort() if set1[:k-2] set2[:k-2]: new_candidate frozenset(set1 [set2[-1]]) candidates.add(new_candidate) return candidates这里set1[:k-2] set2[:k-2]就是判断前k-2个元素是否一致。比如两个频繁2项集{1,2}和{1,3}前1个元素都是1所以连接成{1,2,3}。剪枝步的作用是提前淘汰那些“包含非频繁子集”的候选。虽然连接是基于频繁项集生成的但连接后产生的新项集它的某些子集可能不是频繁的。比如{1,2,3}连接出来之后如果{1,3}不是频繁的那么{1,2,3}肯定不频繁可以直接删掉不用再扫描数据集验证。def prune_Ck(candidates, frequent_prev): frequent_list [frozenset(item) for item in frequent_prev] pruned set() for candidate in candidates: is_valid True for item in candidate: subset candidate - frozenset([item]) if subset not in frequent_list: is_valid False break if is_valid: pruned.add(candidate) return prunedprune_Ck检查候选k项集的每一个k-1项子集是否都在频繁项集列表里只要有一个不在就直接淘汰。最后把连接、剪枝、支持度过滤组合成主函数就是完整的Apriori流程。def apriori(dataset, min_support0.3): C1 create_C1(dataset) frequent1, support_data calculate_support(dataset, C1, min_support) all_frequent [frequent1] k 2 while len(all_frequent[-1]) 0: candidates generate_Ck(all_frequent[-1], k) candidates prune_Ck(candidates, all_frequent[-1]) frequent_k, support_k calculate_support(dataset, candidates, min_support) all_frequent.append(frequent_k) support_data.update(support_k) k 1 return all_frequent, support_dataall_frequent这个列表里第0个元素是频繁1项集字典第1个元素是频繁2项集字典以此类推。循环会在某一轮频繁项集为空时停止说明已经找到最大频繁项集。3.3 关联规则提取与置信度计算频繁项集找出来之后最后一步是提取关联规则。对每一个频繁项集生成它的所有非空真子集然后计算子集→补集的置信度过滤掉低于最小置信度的规则。def generate_rules(all_frequent, support_data, min_confidence0.7): rules [] for frequent_set in all_frequent[1:]: for itemset, support in frequent_set.items(): items list(itemset) if len(items) 2: continue for i in range(1, len(items)): from itertools import combinations for antecedent_tuple in combinations(items, i): antecedent frozenset(antecedent_tuple) consequent itemset - antecedent if len(consequent) 0: continue support_antecedent support_data[antecedent] confidence support / support_antecedent if confidence min_confidence: lift confidence / support_data[consequent] rules.append({ antecedent: antecedent, consequent: consequent, support: round(support, 4), confidence: round(confidence, 4), lift: round(lift, 4) }) return rules这里有个细节值得注意支持度和置信度是从support_data里直接查的不需要再次扫描数据集。因为支持度在之前已经全部算过并存下来了。关联规则的生成只涉及集合运算和字典查询计算量远小于频繁项集挖掘。4. 结果分析与参数调优4.1 运行结果解读把之前的数据集喂进算法设置最小支持度为0.3、最小置信度为0.7跑完看看输出。dataset [ [1,2,5], [2,4], [2,3], [1,2,4], [1,3], [2,3], [1,3], [1,2,3,5], [1,2,3], [2,5], [1,2,4], [2,3], [2,4], [2,3,4], [1,2,3], [1,2,4], [1,5], [1,2,3], [3,4], [1,2,3,5] ] frequent, support_data apriori(dataset, min_support0.3) rules generate_rules(frequent, support_data, min_confidence0.7) for rule in rules: print(f买{set(rule[antecedent])} - 买{set(rule[consequent])} f支持度{rule[support]}置信度{rule[confidence]}提升度{rule[lift]})输出里可能会看到类似这样的规则买{1}→买{2}支持度0.6置信度0.857提升度1.138。这条规则可以解读为在所有交易中有60%的交易同时包含商品1和商品2在所有买了商品1的交易中有85.7%的顾客也买了商品2提升度大于1说明这种关联不是随机出现的商品1和商品2确实存在正向关联。实际分析时不要只看置信度。置信度高的规则有时候只是因为后件本身就卖得好。比如商品2出现概率是0.75任何以商品2为后件的规则置信度上限都不会太低。这时候提升度才是判断价值的关键。4.2 最小支持度与置信度怎么设置这两个阈值是Apriori唯一的两个参数但设置起来很有讲究。最小支持度设得太低频繁项集的数量会爆炸式增长。比如阈值从0.3降到0.1频繁项集可能从几十个变成几千个每一轮连接和剪枝的计算量都急剧上升。设得太高比如0.8大概率什么规则都找不到因为现实中很少有一个商品组合能出现在80%的交易里。我的建议是先粗后细第一次跑用0.5试水如果频繁项集为空就往下降如果频繁项集太多就往上升。在这个区间找到“既能出结果又不会刷屏”的平衡点。最小置信度一般是0.7起步。如果业务场景对准确率要求高比如医疗诊断这种“绝不能乱推荐”的领域可以设到0.9以上。如果是做电商推荐0.5到0.7就够了因为推荐错了成本很低关键是覆盖得够广。4.3 支持度-置信度联动分析实际项目中我喜欢把支持度、置信度和提升度放到一起看。一条规则如果支持度低、置信度也很低那基本可以忽略。如果支持度低但置信度很高说明这条规则属于“小圈子铁律”虽然覆盖的人不多但只要出现前件后件基本跑不掉。这种规则在精准营销里其实很有价值。反过来支持度高但置信度普通的规则反映的是大众普遍行为并没有太多信息量。真正值得关注的是提升度明显大于1的规则不管支持度高低都说明这条规则背后有真实的关联逻辑。5. 常见问题与避坑指南5.1 数据格式与编码陷阱我在实现过程中踩过的第一个坑是数据格式不统一。从文件读数据时商品ID如果是字符串有的带空格有的不带比如1和 1会被当成两个不同的商品。建议读取后统一做strip和类型转换把所有商品转成整数或统一格式的字符串。中文场景下还有编码问题。读取CSV文件时用utf-8可能报错因为Windows下有些文件是gbk编码。读进来之后全是乱码匹配直接失败。解决方法是读取时指定encodingutf-8-sig或者干脆保存成纯数字ID的文本格式省去编码烦恼。另外一个容易忽视的坑是同一笔交易里不要有重复商品。如果[1,1,2,3]这种数据混进来算法不会报错但支持度统计会混乱。建议在数据预处理阶段就去重把每一行转成set再参与运算。5.2 性能瓶颈数据量一大就跑不动Apriori算法的性能瓶颈在于每一轮都要完整扫描数据集做支持度计数而且当商品种类多、支持度阈值低时候选集数量会指数级增长。这种场景下Apriori确实力不从心。有几个实用的优化手段。第一把商品映射成连续的整数ID减少集合比较的开销。第二扫描数据前先做一次全局统计把所有支持度本身就低于阈值的商品直接删掉因为它们不可能出现在任何频繁项集中。第三如果数据实在太大就别死磕Apriori了改用FP-Growth算法。FP-Growth只需要扫描两次数据而且不需要生成候选集在稀疏数据集上性能提升非常明显。5.3 结果解读的三个误区第一个误区是把关联关系当成因果关系。算法只能告诉你“买了A的人大概率也会买B”但不能告诉你“因为买了A所以买了B”。这种理解偏差在业务决策中很危险。第二个误区是只看置信度不看提升度。如前文所说后件本身出现概率高时置信度天然就高。判断规则价值必须结合提升度。第三个误区是忽略支持度只看提升度。有些规则提升度高达10但支持度只有0.01说明只有极少数交易满足条件统计意义不强大概率是偶然现象。尤其是数据量不大的时候这种“高提升度”规则要格外小心。我在实际项目里的经验是筛选规则时先定支持度下限过滤掉尾巴数据再按置信度排序看业务逻辑是否成立最后用提升度确认关联方向。三步走下来留下来的规则才真正有价值。Apriori虽然年代久远但搞清楚它的原理对理解后续的FP-Growth、PrefixSpan等算法都有帮助。建议你拿这份代码和数据跑一遍然后试着改改最小支持度看看频繁项集的变化规律比死记公式有用得多。本文还有配套的精品资源点击获取