18 Python 关联分析:Apriori 算法——如何从购物小票里找到高频组合

Python 数据分析入门:Apriori 算法------如何从购物小票里找到高频组合

适合人群:Python 初学者 / 数据分析入门 / 数据挖掘入门 / 教学案例分享

学完"关联分析基础"之后,很多人都会接着遇到一个问题:

知道了什么是"经常一起买",那到底怎么把这些高频商品组合系统地找出来?

比如校园超市有大量购物小票,人工去翻肯定不现实。

这时候更关心的是:

  • 面包和牛奶是不是高频组合?
  • 尿布和啤酒是不是经常一起出现?
  • 三种、四种商品的组合还能不能继续往下找?

这就进入了关联分析里非常关键的一步:

频繁项集挖掘

而在频繁项集挖掘里,最经典、最适合入门理解的方法之一,就是:

Apriori 算法

它的核心任务可以概括成一句话:

从大量交易记录中,一层一层找出经常一起出现的商品组合。

本文继续沿用"校园超市购物小票分析"这个案例,带你理解:

  • 什么是频繁项集
  • 为什么关联规则挖掘要先找频繁项集
  • Apriori 算法为什么能逐层搜索
  • 什么叫"先验性质"
  • 什么是连接与剪枝
  • 如何用 Python 代码找出频繁项集

如果你已经理解了支持度、置信度和购物篮分析,这一篇就是最自然的下一步。


摘要

频繁项集挖掘是关联规则挖掘的基础,而 Apriori 算法是最经典的频繁项集挖掘方法之一。Apriori 采用逐层搜索的思路,从频繁1项集开始,逐步生成频繁2项集、频繁3项集,直到不能继续扩展为止。它利用"频繁项集的所有非空子集也一定频繁"这一特点压缩搜索空间,并通过连接与剪枝减少无效候选项。本文以校园超市购物小票为案例,结合 Python 代码演示如何完成 One-Hot 编码,并使用 apriori() 找出高频商品组合,帮助初学者理解 Apriori 的实际应用。


一、导语:超市真正关心的不是"哪个卖得多",而是"哪些一起卖得多"

如果你是超市管理员,只知道下面这些信息,其实还不够:

  • 面包卖得多
  • 牛奶卖得多
  • 尿布卖得多

因为这些信息只能说明单个商品的热度,却不能回答一个更有用的问题:

哪些商品适合搭配在一起卖?

举几个更实际的问题:

  • 面包和牛奶是不是高频组合?
  • 泡面和火腿肠是不是常常一起出现?
  • 饮料和饼干适不适合做"第二件半价"?

这些问题背后,真正要解决的是:

从大量交易记录中系统地找出经常一起出现的商品组合。

而这种"经常一起出现的组合",就叫做:

频繁项集


二、先从一个真实案例开始:校园超市的小票太多了,人工根本看不过来

假设学校超市已经积累了很多购物小票。

超市经理现在提出了这样一个需求:

  • 不只是想知道单个商品卖得多不多
  • 更想知道哪些商品组合最值得做促销

比如:

  • 面包和牛奶是不是可以放在一起推荐?
  • 尿布和啤酒这种组合到底是不是常见?
  • 三件商品一起出现的情况,有没有值得重点关注的?

如果数据只有十几条,人工观察勉强还能做。

但如果是几百条、几千条甚至几万条交易记录,人工几乎不可能准确完成。

所以这里的问题就变成了:

怎么自动找出"经常一起买"的商品组合?

这就是 Apriori 要解决的事情。


三、什么是频繁项集?其实就是"高频商品组合"

先别被"频繁项集"这个名字吓到,它其实很好理解。

你可以把"项集"简单理解为:

一个商品组合

例如:

  • {面包}
  • {牛奶}
  • {面包, 牛奶}
  • {面包, 牛奶, 尿布}

这些都叫项集。

如果某个商品组合在所有交易记录中出现得足够频繁,达到了我们设定的最小支持度要求,那它就叫:

频繁项集

也就是说:

  • 项集:商品组合
  • 频繁项集:出现次数足够多的商品组合

而关联规则挖掘的第一步,就是先从交易数据中找出所有的频繁项集。


四、为什么要先找频繁项集?

很多初学者会问:

为什么不直接从数据里生成规则,非要先找频繁项集?

原因其实很简单:

1. 规则数量太多,直接生成会很乱

如果不先筛掉低频组合,规则会多得离谱,而且很多根本没有意义。

2. 很多组合本身就很少出现

如果一个组合只出现过一次、两次,就算硬生成规则,也很难说明它有稳定规律。

3. 频繁项集是后续规则生成的基础

一般来说,关联规则的发现分两步:

  • 先找频繁项集
  • 再由频繁项集生成规则

所以,先把高频组合找出来,是更合理也更高效的路径。


五、Apriori 算法到底在做什么?

Apriori 可以理解为一种"逐层搜索高频组合"的方法。

它的思路其实很像这样:

第一步:先找高频单个商品

也就是频繁1项集,比如:

  • 面包
  • 牛奶
  • 尿布

第二步:再找高频两件商品组合

也就是频繁2项集,比如:

  • {面包, 牛奶}
  • {牛奶, 尿布}

第三步:再找高频三件商品组合

也就是频繁3项集,比如:

  • {面包, 牛奶, 尿布}

然后继续往下找,直到再也找不到满足条件的更大组合为止。

所以 Apriori 最核心的特点就是:

它不是一下子把所有组合都算出来,而是从小到大,一层一层往上找。


六、为什么 Apriori 可以一层一层往上找?

这就涉及 Apriori 最核心的一条思想,也叫:

先验性质

它说的是:

如果一个商品组合是频繁的,那么它的所有非空子集也一定是频繁的。

这句话第一次看有点绕,但其实很好理解。

举个例子:

如果 {面包, 牛奶, 尿布} 这个三项组合是频繁项集,

那它的子集:

  • {面包, 牛奶}
  • {面包, 尿布}
  • {牛奶, 尿布}
  • {面包}
  • {牛奶}
  • {尿布}

也一定都是频繁的。

反过来说:

如果 {牛奶, 尿布} 都不是频繁项集,

{面包, 牛奶, 尿布} 就不可能是频繁项集。

这就是 Apriori 能提前淘汰很多无效组合的原因。


七、什么叫连接与剪枝?这是 Apriori 的关键动作

Apriori 在生成更高阶频繁项集时,主要有两个动作:

  • 连接
  • 剪枝

1. 连接:把小组合拼成大组合

比如已经知道下面两个 2 项集是频繁的:

  • {面包, 牛奶}
  • {面包, 尿布}

那就可以尝试把它们连接起来,生成候选 3 项集:

  • {面包, 牛奶, 尿布}

这一步就是连接。


2. 剪枝:先把明显不可能的组合删掉

并不是所有连接出来的候选项集都值得保留。

如果一个候选项集的某个子集都不是频繁项集,那它自己也不可能频繁,可以直接删掉。

例如:

如果 {牛奶, 尿布} 根本不是频繁项集,

{面包, 牛奶, 尿布} 也不用继续算了。

这一步就叫:

剪枝

它的作用就是减少计算量,提高效率。


八、继续用校园超市案例:从购物小票中找高频组合

下面继续使用这个小型样例数据,模拟校园超市的购物记录。

python 复制代码
transactions = [
    ['面包', '牛奶'],
    ['面包', '尿布', '啤酒', '鸡蛋'],
    ['牛奶', '尿布', '啤酒', '可乐'],
    ['面包', '牛奶', '尿布', '啤酒'],
    ['面包', '牛奶', '尿布', '可乐']
]

这几张小票里,我们已经知道:

  • 面包出现很多次
  • 牛奶出现很多次
  • 尿布出现很多次

现在更关心的是:

哪些商品组合达到了"高频组合"的标准?

这时候,就可以用 Apriori 来找频繁项集。


九、在写代码之前,先做一步数据准备:One-Hot 编码

Apriori 在 Python 里最常见的做法,是先把交易记录转换成 One-Hot 编码 的形式。

可以把它简单理解成一张"商品勾选表":

  • 每一行是一张购物小票
  • 每一列是一个商品
  • 买了记 True
  • 没买记 False

比如:

面包 牛奶 啤酒
True True False
True False True

这样一来,程序就更容易判断某个商品或组合在所有交易里出现了多少次。


十、Python 实操:用 Apriori 找频繁项集

下面直接上代码。

python 复制代码
import pandas as pd
from mlxtend.preprocessing import TransactionEncoder
from mlxtend.frequent_patterns import apriori

transactions = [
    ['面包', '牛奶'],
    ['面包', '尿布', '啤酒', '鸡蛋'],
    ['牛奶', '尿布', '啤酒', '可乐'],
    ['面包', '牛奶', '尿布', '啤酒'],
    ['面包', '牛奶', '尿布', '可乐']
]

# 第一步:数据编码
te = TransactionEncoder()
te_array = te.fit(transactions).transform(transactions)
df = pd.DataFrame(te_array, columns=te.columns_)

print("One-Hot编码后的数据:")
print(df)

# 第二步:Apriori挖掘频繁项集
freq_items = apriori(df, min_support=0.6, use_colnames=True)

print("\n频繁项集:")
print(freq_items)

输出:

log 复制代码
One-Hot编码后的数据:
      可乐     啤酒     尿布     牛奶     面包     鸡蛋
0  False  False  False   True   True  False
1  False   True   True  False   True   True
2   True   True   True   True  False  False
3  False   True   True   True   True  False
4   True  False   True   True   True  False

频繁项集:
   support  itemsets
0      0.6      (啤酒)
1      0.8      (尿布)
2      0.8      (牛奶)
3      0.8      (面包)
4      0.6  (啤酒, 尿布)
5      0.6  (牛奶, 尿布)
6      0.6  (尿布, 面包)
7      0.6  (牛奶, 面包)

十一、这段代码在做什么?

这段代码的关键步骤有两个。

1)把原始购物记录变成机器能读懂的表格

python 复制代码
te = TransactionEncoder()
te_array = te.fit(transactions).transform(transactions)
df = pd.DataFrame(te_array, columns=te.columns_)

这一部分是在做 One-Hot 编码。

原来每张小票是这样的:

python 复制代码
['面包', '牛奶']

转换之后,变成类似这样:

text 复制代码
面包=True, 牛奶=True, 啤酒=False ...

也就是:

  • 买了什么,标记成 True
  • 没买什么,标记成 False

2)调用 Apriori 找出频繁项集

python 复制代码
freq_items = apriori(df, min_support=0.6, use_colnames=True)

这一步是真正的频繁项集挖掘。

参数里最重要的是:

  • min_support=0.6

它表示:

只保留支持度不低于 0.6 的项集

因为我们一共有 5 条交易记录,支持度 0.6 就意味着:

  • 至少要出现在 3 条交易里,才算频繁

十二、运行结果应该怎么看?

运行后,通常会看到类似这样的结果:

text 复制代码
   support      itemsets
0      0.8         (面包)
1      0.8         (牛奶)
2      0.8         (尿布)
3      0.6         (啤酒)
4      0.6    (面包, 牛奶)
5      0.6    (面包, 尿布)
6      0.6    (牛奶, 尿布)
7      0.6    (啤酒, 尿布)
8      0.6  (面包, 牛奶, 尿布)

可以这样理解:

1. 单个商品也可以是频繁项集

比如:

  • (面包) 支持度 0.8
  • (牛奶) 支持度 0.8
  • (尿布) 支持度 0.8

说明这些商品本身就是高频商品。

2. 两个商品组合也可能是频繁项集

比如:

  • (面包, 牛奶) 支持度 0.6
  • (啤酒, 尿布) 支持度 0.6

说明这些组合经常一起出现。

3. 三个商品组合也可能继续保留

比如:

  • (面包, 牛奶, 尿布) 支持度 0.6

说明三件商品一起出现的次数也达到了阈值要求。

这就是 Apriori "逐层往上找"的结果。


十三、为什么支持度阈值一改,结果会差很多?

这是 Apriori 特别适合教学观察的一点。

如果你把代码改成:

python 复制代码
freq_items = apriori(df, min_support=0.4, use_colnames=True)

你会发现:

  • 结果变多了
  • 一些原本不够频繁的组合也被保留下来了

如果改成:

python 复制代码
freq_items = apriori(df, min_support=0.8, use_colnames=True)

你又会发现:

  • 结果明显变少
  • 只有特别常见的项集才会留下来

所以可以直接记住:

  • 支持度阈值越低,找到的频繁项集越多
  • 支持度阈值越高,筛选越严格

这也是后面做实际项目时很重要的一步:

参数不同,挖出来的结果可能差异很大。


十四、这一课最核心要记住什么?

学完这一课,不需要立刻把算法伪代码背下来,但至少要先抓住下面几个核心点:

1)频繁项集是什么?

就是经常一起出现的商品组合。

2)为什么要先找频繁项集?

因为它是后续生成关联规则的基础。

3)Apriori 怎么找频繁项集?

它采用逐层搜索的方法:

  • 先找频繁1项集
  • 再找频繁2项集
  • 再找频繁3项集

4)Apriori 为什么能剪枝?

因为它利用了先验性质:

频繁项集的所有非空子集也一定是频繁的。

5)代码里最关键的参数是什么?

min_support,也就是最小支持度阈值。


十五、结尾总结

这一课主要解决的是一个非常关键的问题:

如何从购物小票中系统地找出"经常一起买"的商品组合?

通过校园超市购物记录这个案例,可以先建立下面这几个核心认识:

  • 频繁项集是经常一起出现的商品组合
  • 关联规则挖掘的基础是先找频繁项集
  • Apriori 是最经典的频繁项集挖掘算法之一
  • 它采用逐层搜索的思路,从 1 项集逐步扩展到更高阶项集
  • 它利用先验性质进行剪枝,从而减少无效计算

对于初学者来说,这一课最重要的不是背算法定义,而是先理解:

Apriori 的本质,就是帮我们从海量交易记录中,一层一层找出高频商品组合。

而下一步,自然就是:

已经找到高频组合了,怎么把它们变成真正有业务价值的规则?

这正是下一课要解决的问题。


写在最后

如果这篇文章对你有帮助,欢迎点赞、收藏、评论支持一下。

你在看频繁项集结果时,第一反应更关注:

  • 哪个商品最常出现?
  • 哪两个商品最适合搭配促销?
  • 还是支持度阈值一改,结果变化为什么会这么大?

欢迎在评论区交流。

相关推荐
小心我捶你啊2 小时前
提升爬虫稳定性的关键,Python爬虫代理IP解析与轮换策略
爬虫·python·tcp/ip
大傻^2 小时前
LangChain4j RAG 核心:Document、Embedding 与向量存储抽象
开发语言·人工智能·python·embedding·langchain4j
快乐柠檬不快乐2 小时前
使用Python操作文件和目录(os, pathlib, shutil)
jvm·数据库·python
进击的小头2 小时前
第11篇:频率响应绘制方法——伯德图(Bode Plot)
python·算法
用户8356290780512 小时前
Python 设置 Excel 条件格式教程
后端·python·excel
2401_874732533 小时前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
l1t3 小时前
与系统库同名python脚本文件引起的奇怪错误及其解决
开发语言·数据库·python
Jackey_Song_Odd3 小时前
Part 1:Python语言核心 - 内建数据类型
开发语言·python
带娃的IT创业者3 小时前
WeClaw WebSocket 连接中断诊断:从频繁掉线到稳定长连的优化之路
python·websocket·网络协议·php·fastapi·实时通信