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 的本质,就是帮我们从海量交易记录中,一层一层找出高频商品组合。
而下一步,自然就是:
已经找到高频组合了,怎么把它们变成真正有业务价值的规则?
这正是下一课要解决的问题。
写在最后
如果这篇文章对你有帮助,欢迎点赞、收藏、评论支持一下。
你在看频繁项集结果时,第一反应更关注:
- 哪个商品最常出现?
- 哪两个商品最适合搭配促销?
- 还是支持度阈值一改,结果变化为什么会这么大?
欢迎在评论区交流。