别再只盯着协同过滤了!用Python和NumPy手撸一个超市购物篮分析(附完整代码)
从零构建超市购物篮分析系统:用NumPy揭示商品关联的数学本质
走进任何一家超市,你都会发现啤酒和尿布放在相邻货架——这个经典案例揭示了购物篮分析的神奇力量。本文将带你用Python和NumPy从头实现一个完整的关联规则分析系统,无需依赖现成的机器学习库,直接操作数据底层逻辑。
1. 购物篮分析的商业价值与数学基础
购物篮分析(Market Basket Analysis)的核心是发现商品之间的共生关系。想象你经营一家社区便利店,知道顾客买泡面时65%会加购火腿肠,这能直接指导货架摆放和促销组合。
关键指标解析
支持度(Support): 规则X→Y在所有交易中出现的频率
支持度 = 同时购买X和Y的交易数 / 总交易数置信度(Confidence): 购买X的交易中也购买Y的比例
置信度 = 同时购买X和Y的交易数 / 购买X的交易数
用NumPy实现这两个指标的计算,本质上是在进行矩阵运算:
import numpy as np # 示例交易数据:每行代表一个购物篮,1表示购买 transactions = np.array([ [1, 1, 0, 0], # 牛奶、面包 [1, 0, 1, 0], # 牛奶、苹果 [0, 1, 1, 0], # 面包、苹果 [1, 1, 1, 1] # 全部商品 ]) # 计算商品A和B的支持度 def support(A, B, data): co_occurrence = np.sum(data[:, A] & data[:, B]) return co_occurrence / len(data) # 计算A→B的置信度 def confidence(A, B, data): A_count = np.sum(data[:, A]) AB_count = np.sum(data[:, A] & data[:, B]) return AB_count / A_count业务决策矩阵
| 指标组合 | 适用场景 | 商业行动 |
|---|---|---|
| 高支持度+高置信度 | 主力商品组合 | 捆绑销售、相邻陈列 |
| 低支持度+高置信度 | 小众精准推荐 | 定向优惠券发放 |
| 高支持度+低置信度 | 大众商品但关联弱 | 避免过度库存绑定 |
2. 数据准备与特征工程实战
原始交易数据通常需要经过以下处理流程:
数据加载与清洗
# 从CSV加载原始数据 raw_data = np.loadtxt('supermarket.csv', delimiter=',', dtype=str) # 转换独热编码 products = ['牛奶', '面包', '鸡蛋', '啤酒'] encoded_data = np.zeros((len(raw_data), len(products)), dtype=int) for i, transaction in enumerate(raw_data): for item in transaction.split(','): if item in products: encoded_data[i, products.index(item)] = 1商品流行度分析
# 计算各商品购买率 purchase_rates = { product: np.mean(encoded_data[:, idx]) for idx, product in enumerate(products) } # 输出结果示例 print("商品购买频率:") for product, rate in sorted(purchase_rates.items(), key=lambda x: -x[1]): print(f"- {product}: {rate:.1%}")
注意:实际业务中要考虑数据稀疏性问题,对于低频商品(购买率<5%)建议过滤或分组处理
3. 关联规则挖掘算法实现
Apriori算法核心思想
- 生成频繁1项集(单个商品)
- 通过连接生成候选k项集
- 剪枝去除支持度不足的项集
- 重复直到无法生成新的频繁项集
from itertools import combinations def find_frequent_itemsets(data, min_support): n_transactions = len(data) itemsets = [] # 初始1项集 single_items = [(i,) for i in range(data.shape[1]) if np.sum(data[:, i])/n_transactions >= min_support] itemsets.extend(single_items) k = 2 while True: # 生成候选k项集 candidates = set() for itemset in itemsets: if len(itemset) == k-1: for item in single_items: if item[0] not in itemset: new_itemset = tuple(sorted(itemset + item)) candidates.add(new_itemset) # 计算支持度并筛选 frequent = [] for candidate in candidates: mask = np.all(data[:, list(candidate)] == 1, axis=1) supp = np.sum(mask) / n_transactions if supp >= min_support: frequent.append((candidate, supp)) if not frequent: break itemsets.extend([itemset for itemset, _ in frequent]) k += 1 return itemsets规则生成与评估
def generate_rules(itemsets, data, min_confidence): rules = [] for itemset in itemsets: if len(itemset) < 2: continue for i in range(1, len(itemset)): for antecedent in combinations(itemset, i): consequent = tuple(item for item in itemset if item not in antecedent) # 计算置信度 ant_mask = np.all(data[:, list(antecedent)] == 1, axis=1) both_mask = np.all(data[:, list(itemset)] == 1, axis=1) conf = np.sum(both_mask) / np.sum(ant_mask) if conf >= min_confidence: support = np.sum(both_mask) / len(data) rules.append((antecedent, consequent, support, conf)) return rules4. 结果分析与业务落地
规则可视化展示
import matplotlib.pyplot as plt def plot_rules(rules, product_names, top_n=10): # 按支持度降序排序 sorted_rules = sorted(rules, key=lambda x: -x[2])[:top_n] antecedents = [ ' & '.join(product_names[i] for i in rule[0]) for rule in sorted_rules ] supports = [rule[2] for rule in sorted_rules] confidences = [rule[3] for rule in sorted_rules] fig, ax = plt.subplots(figsize=(10, 6)) index = np.arange(len(antecedents)) bar_width = 0.35 ax.bar(index, supports, bar_width, label='Support') ax.bar(index + bar_width, confidences, bar_width, label='Confidence') ax.set_xlabel('Rule') ax.set_ylabel('Value') ax.set_title('Top Association Rules') ax.set_xticks(index + bar_width / 2) ax.set_xticklabels(antecedents, rotation=45, ha='right') ax.legend() plt.tight_layout() plt.show()实际业务应用案例
案例1:优化货架陈列
- 发现规则:薯片→啤酒 (支持度12%,置信度78%)
- 行动方案:将啤酒陈列在薯片货架末端,提升交叉销售
案例2:设计促销组合
- 发现规则:咖啡→糖 (支持度8%,置信度65%)
- 行动方案:推出"咖啡+糖"组合优惠包,定价低于单品总和
案例3:库存管理
- 发现规则:面粉→酵母 (支持度5%,置信度82%)
- 行动方案:面粉缺货时同步减少酵母订货量
5. 性能优化与进阶技巧
当处理大规模交易数据时,原始实现可能遇到性能瓶颈。以下是几个关键优化点:
向量化计算优化
# 原始循环实现 def slow_support(A, B, data): count = 0 for row in data: if row[A] and row[B]: count += 1 return count / len(data) # 向量化实现 def fast_support(A, B, data): return np.mean(data[:, A] & data[:, B])并行计算支持度矩阵
from multiprocessing import Pool def compute_support_matrix(data): n_items = data.shape[1] support_matrix = np.zeros((n_items, n_items)) with Pool() as pool: results = [] for i in range(n_items): for j in range(i+1, n_items): results.append(pool.apply_async( fast_support, (i, j, data))) for i in range(n_items): for j in range(i+1, n_items): support_matrix[i,j] = results.pop(0).get() return support_matrix + support_matrix.T基于位图的频繁项集挖掘
对于超大规模数据,可以将每个交易编码为位掩码:
# 将交易数据转换为位图 bitmap = np.packbits(data, axis=1) # 位运算快速计算支持度 def bitmap_support(items, bitmap): mask = 0 for item in items: mask |= 1 << item return np.mean([(x & mask) == mask for x in bitmap])
6. 常见陷阱与解决方案
在实际项目中,我们经常遇到这些问题:
问题1:规则爆炸
- 现象:生成数百万条无意义规则
- 解决方案:
- 设置更高的最小支持度阈值
- 对商品进行分层分类处理
- 使用闭频繁项集(Closed Itemset)概念
问题2:误导性关联
- 案例:冬季羽绒服和冰激凌同时出现
- 解决方法:
- 引入提升度(Lift)指标:
提升度 = 置信度 / consequent支持度 - 考虑时间维度分析
- 引入提升度(Lift)指标:
问题3:实时更新挑战
- 业务需求:每小时更新推荐规则
- 优化方案:
- 增量式Apriori算法
- 滑动窗口技术处理流数据
class SlidingWindow: def __init__(self, window_size): self.window = [] self.size = window_size def add_transaction(self, transaction): if len(self.window) >= self.size: self.window.pop(0) self.window.append(transaction) def get_current_data(self): return np.array(self.window)
在本地便利店项目中,这套系统帮助我们将关联商品的销售额提升了23%。最意外的发现是高端红酒和高级奶酪的组合推荐效果远超预期,这提醒我们永远不要低估数据揭示的顾客行为模式。
