简介这份资源是Apriori关联规则挖掘算法的Python实现代码包面向数据挖掘初学者、算法学习者以及需要做购物篮分析或市场关联分析的数据从业者。它解决的是从零理解并落地频繁项集发现与关联规则挖掘的问题涵盖数据预处理、频繁项集生成、支持度与置信度计算以及剪枝优化等核心环节。压缩包共5个文件约3KB包含1个py核心实现脚本、1个csv示例数据集、1个md说明文档、1个license授权文件及1个gitignore配置结构轻量便于直接运行与二次修改。目前已有247人学习下载。通过阅读核心脚本与示例数据读者可以掌握Apriori算法的完整执行流程理解支持度、置信度与剪枝策略的代码表达并借助示例数据快速验证结果为关联规则挖掘实践提供可复用的参考实现。1. Apriori 算法 Python 实现从零手写到 mlxtend 一把梭电商订单表里躺着几十万条交易记录老板丢过来一句“看看哪些商品经常一起买”这就是关联规则挖掘最典型的落地场景。Apriori 算法就是干这个的从一堆交易记录里找出频繁项集再生成“买了 A 的人大概率也会买 B”这种规则。Python 实现这件事说难不难说简单也有坑——自己手写一遍能彻底搞懂原理但真上生产环境我一般直接用 mlxtend 这类成熟库。这篇笔记面向两类人刚学完 Python 基础语法、想找个真实项目练手的入门者以及需要快速在业务里跑出关联规则、不想在算法细节上耗时间的工程师。下面从算法逻辑讲到代码落地每一步都能直接抄。2. Apriori 的两个核心概念支持度与置信度怎么算2.1 支持度、置信度、提升度的数学定义Apriori 的所有判断都建立在三个指标上。支持度Support衡量一个项集在所有交易中出现的频率公式是Support(X) 包含X的交易数 / 总交易数。置信度Confidence衡量规则的可靠程度Confidence(X→Y) Support(X∪Y) / Support(X)意思是买了 X 的人里有多少也买了 Y。提升度Lift用来判断 X 和 Y 是不是真的有关联Lift(X→Y) Confidence(X→Y) / Support(Y)大于 1 说明正相关等于 1 说明独立小于 1 说明负相关。这三个指标的关系可以用一个具体例子说清楚。假设 1000 笔交易里买啤酒的有 200 笔买尿布的有 300 笔同时买两者的有 150 笔。那么 Support(啤酒→尿布) 150/1000 0.15Confidence(啤酒→尿布) 150/200 0.75Lift 0.75 / 0.3 2.5。提升度 2.5 意味着买啤酒的人买尿布的概率是普通人的 2.5 倍这个规则值得推。实际调参时最小支持度min_support设多少直接决定结果数量。我一般先跑一遍统计看项集支持度的分布再取一个能过滤掉长尾噪声但又不至于只剩两三条规则的值。零售场景常见起点是 0.01 到 0.05具体看 SKU 数量和交易量。2.2 Apriori 的逐层剪枝逻辑Apriori 的核心思想是“一个频繁项集的所有子集也必须是频繁的”。反过来用就是如果一个项集不频繁那它的所有超集也不可能频繁直接剪掉。这个性质让算法不用穷举所有组合。具体流程分两步。第一步找频繁项集先扫描一遍数据统计每个单品1-项集的支持度砍掉低于阈值的然后用剩下的频繁 1-项集两两组合生成候选 2-项集再扫描数据统计支持度继续砍重复这个过程直到无法生成新的候选项集。第二步生成规则对每个频繁项集枚举它所有非空子集作为规则前件计算置信度保留高于阈值的规则。这个过程的计算瓶颈在“扫描数据”这一步。每生成一层候选项集就要重新遍历一次交易库如果交易量大、项集层数深I/O 开销会非常可观。这也是为什么后面要讲用 mlxtend 而不是纯手写循环——库内部做了不少优化。3. 手写 Apriori60 行 Python 代码跑通频繁项集3.1 数据准备与候选集生成函数先定义一个最小的交易数据集用列表嵌套集合表示每个内层集合是一笔交易。# 交易数据集每笔交易是一个商品集合 transactions [ {牛奶, 面包, 尿布}, {可乐, 面包, 尿布, 啤酒}, {牛奶, 尿布, 啤酒, 鸡蛋}, {面包, 牛奶, 尿布, 啤酒}, {面包, 牛奶, 尿布, 可乐}, ] def create_c1(transactions): 生成所有单个商品的集合即候选1-项集 c1 set() for t in transactions: for item in t: c1.add(frozenset([item])) return c1 def scan_dataset(transactions, candidates, min_support): 扫描数据集统计候选项集的支持度返回频繁项集字典 ss_cnt {} for t in transactions: for can in candidates: if can.issubset(t): ss_cnt[can] ss_cnt.get(can, 0) 1 num_items len(transactions) ret_list {} for key in ss_cnt: support ss_cnt[key] / num_items if support min_support: ret_list[key] support return ret_listcreate_c1把每笔交易拆成单个商品用frozenset是因为集合要作为字典的键普通 set 不可哈希。scan_dataset遍历每笔交易判断候选项集是否是交易的子集是就计数最后除以总交易数得到支持度低于阈值的直接丢弃。这里min_support是外部传入的参数控制过滤强度。3.2 逐层组合与完整频繁项集挖掘有了频繁 1-项集接下来要不断组合生成更高阶的候选项集。def apriori_gen(freq_sets, k): 由频繁k-1项集生成候选k项集 ret_list [] list_keys list(freq_sets.keys()) for i in range(len(list_keys)): for j in range(i 1, len(list_keys)): l1 list(list_keys[i])[:k - 2] l2 list(list_keys[j])[:k - 2] l1.sort() l2.sort() if l1 l2: # 前k-2项相同才合并 ret_list.append(list_keys[i] | list_keys[j]) return ret_list def apriori(transactions, min_support0.5): 主函数返回所有频繁项集及其支持度 c1 create_c1(transactions) freq_set scan_dataset(transactions, c1, min_support) all_freq dict(freq_set) k 2 while len(freq_set) 0: candidates apriori_gen(freq_set, k) freq_set scan_dataset(transactions, candidates, min_support) if freq_set: all_freq.update(freq_set) k 1 return all_freq result apriori(transactions, min_support0.4) for itemset, sup in sorted(result.items(), keylambda x: -x[1]): print(f项集: {set(itemset)}, 支持度: {sup:.2f})apriori_gen的合并条件是前 k-2 项相同这是 Apriori 剪枝的关键——只有前缀一致的项集才可能产生频繁超集。主循环里每轮生成候选、扫描、过滤直到没有新的频繁项集为止。min_support0.4意味着一个项集至少要在 40% 的交易中出现才算频繁5 笔交易里至少出现 2 次。跑这段代码会输出类似项集: {尿布, 面包}, 支持度: 0.60的结果。手写版本适合理解原理但交易量上万之后性能会明显下降因为每轮都要全量扫描。4. 用 mlxtend 替代手写三行代码出关联规则4.1 安装与数据格式转换手写版跑通之后实际项目里我基本不自己维护 Apriori 实现。mlxtend是 Python 生态里关联规则挖掘最常用的库安装一条命令pip install mlxtend pandasmlxtend 要求输入是布尔型 DataFrame每行一笔交易每列一个商品值为 True/False。从原始交易列表转换import pandas as pd from mlxtend.preprocessing import TransactionEncoder transactions [ [牛奶, 面包, 尿布], [可乐, 面包, 尿布, 啤酒], [牛奶, 尿布, 啤酒, 鸡蛋], [面包, 牛奶, 尿布, 啤酒], [面包, 牛奶, 尿布, 可乐], ] te TransactionEncoder() te_ary te.fit(transactions).transform(transactions) df pd.DataFrame(te_ary, columnste.columns_) print(df)TransactionEncoder把所有出现过的商品收集成列名然后对每笔交易做 one-hot 编码。输出的 DataFrame 可以直接喂给频繁项集挖掘函数。这一步的坑在于如果交易数据里有重复商品同一笔交易里同一个 SKU 出现两次需要先去重否则编码会出问题。4.2 频繁项集与规则生成from mlxtend.frequent_patterns import apriori, association_rules # 挖掘频繁项集min_support 控制阈值 frequent_itemsets apriori(df, min_support0.4, use_colnamesTrue) print(frequent_itemsets) # 生成关联规则min_threshold 是置信度下限 rules association_rules(frequent_itemsets, metricconfidence, min_threshold0.7) print(rules[[antecedents, consequents, support, confidence, lift]])apriori函数的use_colnamesTrue让输出显示商品名而不是列索引。association_rules的metric参数可以换成lift或supportmin_threshold对应调整。返回的 rules 表里antecedents是规则前件consequents是后件lift列直接告诉你关联强度。我一般会按 lift 降序排一遍再人工扫一眼前 20 条看有没有业务上说得通的组合。纯靠置信度排序容易出“因为 Y 本身就很畅销所以 X→Y 置信度高”的假关联lift 能过滤掉这类噪声。5. 避坑与排查Apriori 落地时最容易翻车的 4 个点5.1 支持度设太高一条规则都跑不出来现象apriori返回空 DataFrame或者只有零星几个单项集。原因通常是min_support设得过高比如交易数据有 5000 个 SKU你设了 0.1意味着一个商品要在 10% 的交易里出现很多长尾商品直接被砍光。解决办法是先跑一次支持度分布统计用df.mean()看每个商品的出现频率取一个能保留 20 到 50 个商品的阈值作为起点再逐步调整。5.2 交易数据没去重支持度算出来偏高现象同一笔订单里同一个商品出现多次比如数量为 2 的行被展开成两行导致该商品的支持度被重复计算。原因在于数据清洗阶段没有按订单号加商品去重。解决方式是在构建交易列表时用set()包一层或者在 SQL 层就SELECT DISTINCT order_id, product_id。这个坑很隐蔽因为结果看起来“有数据”但数值是错的。5.3 规则数量爆炸lift 全在 1 附近现象调低min_support和min_threshold之后规则数量从几十条暴涨到几万条但大部分 lift 在 1.0 上下浮动。原因是阈值太低大量统计上不显著的组合被保留下来。解决办法是加一道 lift 过滤rules rules[rules[lift] 1.2]同时把min_support回调到合理区间。如果数据量确实大可以先对商品做聚类或按品类聚合降低维度再跑。5.4 中文商品名编码问题导致列名乱码现象TransactionEncoder输出的列名显示为乱码或者association_rules结果里 antecedents 打印出来是frozenset({\u725b\u5976})。原因是终端或 IDE 的编码设置不是 UTF-8。解决办法是在脚本开头加# -*- coding: utf-8 -*-Windows 环境下用chcp 65001切换终端编码Jupyter 里一般不会有这个问题。如果是在数据库里读数据确认连接字符串里指定了charsetutf8mb4。6. 从规则到业务提升度排序与规则后处理技巧跑出规则只是第一步真正难的是从几百条规则里挑出能用的。我自己的习惯是分三步走。第一步按 lift 降序排取前 50 条第二步人工过一遍把“啤酒→尿布”这种明显是数据噪声或者促销导致的伪关联剔掉第三步对剩下的规则做业务映射比如把商品 ID 换成品类名看跨品类关联有没有运营价值。一个具体的后处理技巧是用rules表的antecedents和consequents列做长度过滤。单前件单后件的规则最容易解释也最容易落地到推荐位。多前件规则虽然 lift 可能更高但业务上很难触发。代码上就是# 只保留单前件、单后件的规则 simple_rules rules[ (rules[antecedents].apply(len) 1) (rules[consequents].apply(len) 1) ] # 按 lift 降序取前 20 top_rules simple_rules.sort_values(lift, ascendingFalse).head(20) print(top_rules[[antecedents, consequents, support, confidence, lift]])另外如果数据量到了百万级交易mlxtend 的 apriori 也会变慢。这时候可以考虑 FP-Growth 算法mlxtend 同样提供了fpgrowth函数接口和 apriori 一致但不需要反复扫描数据速度通常快一个数量级。切换方式就是把apriori(df, ...)换成fpgrowth(df, ...)其他代码不用动。最后说一个我踩过的坑不要拿全量历史数据直接跑。先按时间窗口切一段比如最近 3 个月再按品类拆开跑。全量数据跑出来的规则往往被季节性和促销活动污染lift 高但没复用价值。分品类跑还有个好处是规则解释性强运营拿到“母婴品类内 A→B”比拿到跨品类的“A→B”更容易执行。希望帮到你。本文还有配套的精品资源点击获取