用Python从零实现Apriori算法:超市购物篮分析的实战指南
超市收银台前,你是否好奇过为什么啤酒和尿布总被放在一起?这背后是关联规则挖掘的经典案例。今天我们不调用任何现成库,用纯Python实现Apriori算法的完整流程,让你真正理解购物篮分析的底层逻辑。
1. 算法原理与准备工作
关联规则挖掘的核心目标是发现商品之间的"如果...那么..."关系。Apriori算法通过两个关键指标筛选有价值的规则:
- 支持度(Support):某商品组合出现的频率,过滤掉冷门组合
- 置信度(Confidence):当A商品出现时,B商品也出现的概率
我们先准备测试数据集,模拟一个小型超市的购物记录:
python复制dataset = [
['牛奶', '面包', '尿布'],
['可乐', '面包', '尿布', '啤酒'],
['牛奶', '尿布', '啤酒', '鸡蛋'],
['面包', '牛奶', '尿布', '啤酒'],
['面包', '牛奶', '尿布', '可乐']
]
提示:实际项目中,数据通常来自数据库或CSV文件。保持事务记录为嵌套列表结构,每个子列表代表一次购物交易。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 构建候选项集与频繁项集
2.1 创建初始候选项集
首先生成所有单个商品的候选项集(C1):
python复制def createC1(dataset):
"""生成初始候选项集"""
C1 = set()
for transaction in dataset:
for item in transaction:
item_set = frozenset([item]) # 使用不可变集合
C1.add(item_set)
return C1
# 测试输出
print(createC1(dataset))
# {frozenset({'啤酒'}), frozenset({'牛奶'}), ...}
2.2 筛选频繁项集
扫描数据集,计算每个候选项集的支持度:
python复制def scanD(D, Ck, min_support):
"""筛选满足最小支持度的项集"""
ss_cnt = {}
for tid in D:
for candidate in Ck:
if candidate.issubset(tid):
ss_cnt[candidate] = ss_cnt.get(candidate, 0) + 1
num_items = len(D)
freq_items = []
support_data = {}
for candidate, count in ss_cnt.items():
support = count / num_items
if support >= min_support:
freq_items.insert(0, candidate)
support_data[candidate] = support
return freq_items, support_data
# 使用示例
D = list(map(set, dataset)) # 转换为集合列表
C1 = createC1(dataset)
L1, support_data = scanD(D, C1, min_support=0.5)
3. 实现Apriori核心迭代
3.1 生成候选项集
通过频繁(k-1)-项集生成k-候选项集:
python复制def apriori_gen(Lk_1, k):
"""生成k-候选项集"""
candidates = []
len_Lk = len(Lk_1)
for i in range(len_Lk):
for j in range(i+1, len_Lk):
# 前k-2项相同时合并
L1 = list(Lk_1[i])[:k-2]
L2 = list(Lk_1[j])[:k-2]
if L1 == L2:
candidates.append(Lk_1[i] | Lk_1[j])
return candidates
3.2 完整Apriori算法实现
python复制def apriori(dataset, min_support=0.5):
"""完整Apriori算法实现"""
# 初始化
C1 = createC1(dataset)
D = list(map(set, dataset))
L1, support_data = scanD(D, C1, min_support)
L = [L1] # 存储所有频繁项集
k = 2
# 迭代生成更大的频繁项集
while len(L[k-2]) > 0:
Ck = apriori_gen(L[k-2], k)
Lk, supK = scanD(D, Ck, min_support)
support_data.update(supK)
L.append(Lk)
k += 1
return L, support_data
# 运行算法
freq_items, support_data = apriori(dataset, min_support=0.6)
4. 挖掘关联规则
4.1 计算规则置信度
python复制def calc_conf(freq_set, H, support_data, rules, min_conf=0.7):
"""计算规则置信度"""
pruned_H = []
for conseq in H:
conf = support_data[freq_set] / support_data[freq_set - conseq]
if conf >= min_conf:
rules.append((freq_set - conseq, conseq, conf))
pruned_H.append(conseq)
return pruned_H
4.2 递归生成关联规则
python复制def rules_from_conseq(freq_set, H, support_data, rules, min_conf=0.7):
"""递归生成关联规则"""
m = len(H[0])
if len(freq_set) > m + 1:
Hmp1 = apriori_gen(H, m+1)
Hmp1 = calc_conf(freq_set, Hmp1, support_data, rules, min_conf)
if len(Hmp1) > 1:
rules_from_conseq(freq_set, Hmp1, support_data, rules, min_conf)
4.3 完整规则生成函数
python复制def generate_rules(L, support_data, min_conf=0.7):
"""生成所有关联规则"""
rules = []
for i in range(1, len(L)):
for freq_set in L[i]:
H1 = [frozenset([item]) for item in freq_set]
if i > 1:
rules_from_conseq(freq_set, H1, support_data, rules, min_conf)
else:
calc_conf(freq_set, H1, support_data, rules, min_conf)
return rules
# 生成并打印规则
rules = generate_rules(freq_items, support_data, min_conf=0.7)
for ante, conseq, conf in rules:
print(f"规则: {list(ante)} => {list(conseq)}, 置信度: {conf:.2f}")
5. 实战:超市购物篮分析
让我们用更大的数据集测试算法。假设我们有超市购物记录的CSV文件:
python复制import pandas as pd
def load_dataset(filepath):
"""加载超市购物数据集"""
df = pd.read_csv(filepath)
basket = []
for customer_id in df['customer_id'].unique():
items = df[df['customer_id'] == customer_id]['item'].tolist()
basket.append(items)
return basket
# 完整分析流程
dataset = load_dataset('supermarket.csv')
freq_items, support_data = apriori(dataset, min_support=0.1)
rules = generate_rules(freq_items, support_data, min_conf=0.5)
# 按置信度排序输出
sorted_rules = sorted(rules, key=lambda x: x[2], reverse=True)
for rule in sorted_rules[:10]:
ante, conseq, conf = rule
print(f"{list(ante)} => {list(conseq)} (置信度: {conf:.2f})")
6. 性能优化与实用技巧
6.1 算法优化策略
- 事务压缩:减少不包含任何频繁项集的事务
- 哈希树:高效存储和检索候选项集
- 分区:将数据分成可放入内存的块
- 采样:对数据子集运行算法
6.2 参数调优建议
| 参数 | 典型范围 | 影响 | 调整策略 |
|---|---|---|---|
| min_support | 0.01-0.3 | 值越小,结果越多 | 从高开始逐步降低 |
| min_conf | 0.5-0.9 | 值越小,规则越宽松 | 根据业务需求调整 |
| max_length | 3-5 | 限制规则长度 | 避免过长无意义规则 |
6.3 常见问题排查
- 内存不足:尝试增加min_support或使用采样数据
- 运行时间过长:限制项集最大长度(max_length)
- 规则质量差:检查数据质量,调整参数组合
python复制# 带最大长度限制的Apriori改进
def apriori_with_max_length(dataset, min_support, max_length=3):
C1 = createC1(dataset)
D = list(map(set, dataset))
L1, support_data = scanD(D, C1, min_support)
L = [L1]
k = 2
while len(L[k-2]) > 0 and k <= max_length:
Ck = apriori_gen(L[k-2], k)
Lk, supK = scanD(D, Ck, min_support)
support_data.update(supK)
L.append(Lk)
k += 1
return L, support_data
7. 实际应用场景扩展
7.1 跨行业应用案例
- 电商平台:商品推荐组合
- 医疗健康:症状与疾病关联
- 网络安全:异常事件关联
7.2 结果可视化方法
使用NetworkX绘制关联规则网络图:
python复制import networkx as nx
import matplotlib.pyplot as plt
def visualize_rules(rules, top_n=15):
G = nx.DiGraph()
sorted_rules = sorted(rules, key=lambda x: x[2], reverse=True)[:top_n]
for ante, conseq, conf in sorted_rules:
G.add_edge(', '.join(ante), ', '.join(conseq), weight=conf)
pos = nx.spring_layout(G)
plt.figure(figsize=(12, 8))
nx.draw(G, pos, with_labels=True,
width=[d['weight']*5 for (u,v,d) in G.edges(data=True)],
edge_color='gray', node_color='skyblue')
plt.show()
visualize_rules(rules)
7.3 与其他算法的对比
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Apriori | 原理简单,易于实现 | 多次扫描数据库,效率低 | 中小规模数据集 |
| FP-Growth | 只需扫描两次数据库 | 实现复杂,内存消耗大 | 大规模数据集 |
| Eclat | 垂直数据格式,速度快 | 不适合增量更新 | 密集数据集 |
