基于Python的A-Priori算法发现购物篮关联规则
前言
一个生动的例子介绍购物篮模型——啤酒与尿布的故事
有10000个消费者购买了商品,其中购买尿布1000个,购买啤酒2000个,购买面包500个,同时购买尿布和啤酒800个,同时购买尿布和面包100个,
我们发现同时购买啤酒与尿布的消费者比较多,一个可能的猜测是年轻的爸爸们在为孩子购买尿布时顺便买了啤酒。
基于上述发现,可以给我们的商品上架或者划分消费人群等提供参考。
本篇给定超市购物记录集sales_detail.csv,提取其中的交易标识符和商品名称构成购物篮数据集。用A-Priori算法发现其中的关联规则。
原理分析及流程
频繁项集定义
如果项集 I 的支持度不小于阈值 s ,则称 I 是频繁项集。
s: 支持度阈值 (support threshold);
项集 I 的支持度(support):包含 I 的购物篮 (记录) 的数目。
一个8购物篮的例子,假设有如下8个购物篮:
(1) { Cat, and, dog, bites }
(2) { Yahoo, new, claims, a , cat, mated, with, a, dog, and, produced, viable, offspring }
(3) { Cat, killer, likely, is, a, big, dog }
(4) { Professional, free, advice, on, dog, training, puppy, training }
(5) { Cat, and, kitten, training, and, behavior }
(6) { Dog, &, Cat, provides, dog, training, in, Eugene, Oregon }
(7) { “ Dog, and, cat “, is, a, slang, term, used, by, police, officers, for, a, male-female, relationship }
(8) { Shop, for, your, show, dog, grooming, and, pet, supplies }
我们来发现其频繁项:
“cat” :6 ( 除了(4)和(8)的全部购物篮中 )
“dog” :7 ( 除了(5)之外的全部购物篮中 )
“and” :5
“a”、“traning” :3
“for”、“is” :2
其它不多于1
假定给出的支持度阈值 s 为3,频繁项集为:{dog}、{cat}、{and}、{a}、{training}
单元素的频繁项在商品销售中可应用于发现人们经常购买的商品。
双元素频繁项集合
一个双元素频繁项集合中的两个元素本身都必须是频繁的,这样该集合才有可能是频繁的。
如上述例子中,可能的双元素频繁项集合只有10个,分别是:
{dog, cat}、{dog, and}、{dog, a}、{dog, training}、{cat, and}
{cat, a}、{cat, training}、{and, a}、{and, training}、{a, training}
更多元素的频繁项集同理。
对于多元素的频繁项集在商品销售中的一个应用就是发现那些顾客经常一起购买的商品,以为我们上架提供参考。
关联规则
不同项集和项之间有许多关联规则,关联规则表示形式如下:
I
→
j
(
I
为
项
集
,
j
为
项
)
I \rightarrow j ( I 为项集, j 为项 )
I→j(I为项集,j为项)
I
I
I为先决条件,
j
j
j为相应的关联结果,用于表示数据内隐含的关联性。
1.支持度 Support
支持度是指在所有项集中
{
I
,
j
}
\left \{ I, j \right \}
{I,j}出现的可能性,即项集中同时含有
I
I
I和
j
j
j的概率,该指标作为建立强关联规则的第一个门槛,衡量了所考察关联规则在"量"上的多少。
2.可信度 Confidence
I
∩
j
的
支
持
度
/
I
的
支
持
度
I \cap { j }的支持度 / I 的支持度
I∩j的支持度/I的支持度即所有包含项集
I
I
I 的数据记录中同时包含项
j
j
j 的比例。置信度表示在先决条件
I
I
I发生的条件下,关联结果
j
j
j发生的概率,这是生成强关联规则的第二个门槛,衡量了所考察的关联规则在“质”上的可靠性。
3. 提升度 Lift
表示“购买
I
I
I的用户中同时购买j的比例”与“购买j的用户比例”的比值,该指标与置信度同样衡量规则的可靠性,可以看作是置信度的一种互补指标。
以上三个参数用于衡量关联规则的可采用性。
关联规则参数设置问题
在进行关联规则的发现时,我们应该注意以下问题:对支持度和置信度等参数的设置应该合理。
我们设想以下情况:支持度如果设置过低将导致过多频繁项集的发现,从而对于所得结果不具有代表性,而如果支持度设置过高,则可能频繁项集发现的商品是人们每次购物都会大概率购买的(比如塑料袋),但这并不是我们想要得到的结果,我们想要发现的是那些平时看上去没有联系的商品(如啤酒与尿布)。
对于置信度的设置也是如此,如果置信度设置过低,则关联规则将变得不太可信,而如果设置过高,我们考虑以下情况:某调和油做活动附赠酱油,置信度设置过高将会导致我们发现的结果是这类“捆绑”销售的商品,而不是那些真正有关联的商品。
其他参数也应该合理设置(可多次实验看输出结果)。
A-Priori算法
该算法运用于寻找频繁项集及频繁项集推出高支持度和可信度的关联规则。基于如下理论:
如果某个项集是频繁的,那么它的所有子集都是频繁的;而如果它的超集不再频繁,则称该项集是最大频繁项集。
当项对的数目太多而无法在内存中对所有的项对计数时,A-Priori算法可以减少必须计数的项对数目,其代价是要对数据做两遍而不是一遍扫描。
- A-Priori算法的第一遍扫描:采用两张表分别存储项名和该项出现次数,通过对项计数看哪些是频繁的;
- A-Priori算法两遍扫描之间的处理:检查所有项的计数值,对频繁项重新编号,编号范围是1到m,得到的表格称为频繁项表格。
- A-Priori算法的第二遍扫描:由单调性原理可知,除非一个项对中的两个项都频繁,否则这个项对也不可能是频繁的。因此,只用扫描第2步中编号的频繁项,不用担心丢失任何频繁项对。
A-Priori算法对于更大频繁项集的发现做法如下:
先找到频繁 1 - 项集集合 L1,然后利用 L1找到频繁 2 -项集集合 L2,接着用L2找频繁3-项集集合L3……,直到最后找不到为止。
实现流程
所给数据集的每一行的数据是订单号、商品名称及其他参数,首先我们提取所要的订单号及商品名称(去除商品规格),然后按照订单号划分商品所属的购物篮,再利用Python的第三方模块apriori库的apriori包完成频繁项集及关联规则的发现。
具体实现
读入并查看数据集的前5行数据
In [5]: f = pd.read_csv('sales_detail.csv', encoding='utf-8', sep='\t', header=None)
In [6]: f.head()
Out[6]:
0 1 2 3 4 5 6 7 8 9 10 11
0 34121002436593 1 2012-08-01 07:45:38 5440483 2186463 苦瓜(一级) 公斤 0.262 4 1.048 3.6 0.94
1 34121002436593 2 2012-08-01 07:45:39 5440483 2186463 苦瓜(一级) 公斤 0.192 4 0.768 3.6 0.69
2 34121002436593 3 2012-08-01 07:45:45 5440466 2186359 南瓜(一级) 公斤 4.052 1.98 8.023 1.78 7.21
3 34121002436594 1 2012-08-01 07:45:26 5110324 6934665081392 蒙牛益生菌酸牛奶(原味)1.2kg 桶 1 19.59 19.59 19.59 19.59
4 34121002436595 1 2012-08-01 07:47:18 5110467 6901209206146 光明酸牛奶(红枣味)180g 盒 2 3.5 7 3.5 7
如上,我们所需要的数据是订单号和商品名,分别在第0列和第5列,且我们观察商品名,去掉其不同规格只需要对商品名做如下处理:截取部分是从左边第一个中文字符开始,到右边第一个非中文字符结束,所以我们定义去除商品规格的函数并进行测试:
In [8]: deprive_bracket_specification('L三全面包(草莓味)1.25kg')
Out[8]: '三全面包'
如上,我们定义了一个去除商品规格的函数并进行了测试,最终结果和我们想要的一致。
接下来就是提取出我们需要的第0列和第5列,并对第5列商品名去除规格。
In [17]: tip_shopname = f.loc[:,[0,5]]
In [18]: tip_shopname.columns = ['tip', 'shopname']
In [19]: tip_shopname.head()
Out[19]:
tip shopname
0 34121002436593 苦瓜(一级)
1 34121002436593 苦瓜(一级)
2 34121002436593 南瓜(一级)
3 34121002436594 蒙牛益生菌酸牛奶(原味)1.2kg
4 34121002436595 光明酸牛奶(红枣味)180g
In [20]: tip_shopname.shopname = tip_shopname.shopname.apply(func=deprive_bracket_specification)
In [21]: tip_shopname.head()
Out[21]:
tip shopname
0 34121002436593 苦瓜
1 34121002436593 苦瓜
2 34121002436593 南瓜
3 34121002436594 蒙牛益生菌酸牛奶
4 34121002436595 光明酸牛奶
如上,我们提取出了单号和商品名,并对商品名去除了规格。
接下来按照单号进行分篮子。
In [24]: grouping_by_grocery_list = []
In [25]: for meb in tip_shopname.groupby('tip'):
...: grouping_by_grocery_list.append(list(meb[1].shopname))
In [26]: grouping_by_grocery_list[0:5]
Out[26]:
[['鲜鸡蛋', '良厨杂粮馒头'],
['鲜鸡蛋', '鲜鸡蛋', '酒鬼花生', '牛后腿肉', '萝卜', '西兰花'],
['牛后腿肉', '云楼精什锦'],
['多力葵花籽油', '金龙鱼花生浓香调和油', '野生大白鲢', '野生大白鲢'],
['野生大白鲢', '年糕', '鲜鸡蛋']]
分篮结果如上所示,同一个订单中的商品被分到了一起。
接下来我们利用Python的第三方模块apriori库的apriori包完成频繁项集及关联规则的发现。
# 导入所需要的包
from apyori import apriori
# 使用apriori包进行频繁项集及关联规则的发现。
result = apriori(grouping_by_grocery_list, min_support=0.0002, min_confidence=0.7, min_lift=10, max_length=3)
# 将结果迭代器转保存为列表
result_list = []
for meb in result:
result_list.append(meb)
如上,我们经过多次实验,设置了较为合理的参数,并将结果保存在结果列表中,下面我们查看结果列表的一部分数据。
In [34]: # 查看发现的频繁项集
...: for meb in result_list[0:10]:
...: print(meb.items)
...:
frozenset({'云烟', '七匹狼'})
frozenset({'利群', '七匹狼'})
frozenset({'红双喜', '七匹狼'})
frozenset({'红塔山', '七匹狼'})
frozenset({'七匹狼', '雄狮'})
frozenset({'云烟', '万宝路'})
frozenset({'万宝路', '利群'})
frozenset({'万宝路', '南京'})
frozenset({'万宝路', '红双喜'})
frozenset({'万宝路', '红塔山'})
如上结果所示,前十个频繁项集中两个项均为香烟,这并没有太大的参考价值,因为我们习惯性是将同一类商品放在一起。
我们对所有频繁项集进行观察,发现了下面这条较为有趣的数据:
In [40]: result_list[64]
Out[40]: RelationRecord(items=frozenset({'康师傅冰红茶', '华乐二条装礼盒毛巾'}), support=2.8567383004145996e-05, ordered_statistics=[OrderedStatistic(items_base=frozenset({'华乐二条装礼盒毛巾'}), items_add=frozenset({'康师傅冰红茶'}), confidence=0.92, lift=202.3231029773286)])
如上,本来看上去毫无关联的冰红茶和毛巾,却被多次一同购买。这可以为我们的商品上架提供参考。
另外,我们还可以通过调整参数,发现其他频繁项集和关联规则。
总结
“啤酒与尿布”的故事给了我们许多启示,看似毫无联系的两件商品,被同时购买的次数却不少,通过对频繁项集及关联规则的挖掘,不仅可以寻找到事物之间平时不容易被人们发现的内在联系,跟深层次的挖掘是我们可以去分析形成此种现象背后的原因。
总而言之,它能够为我们提供许多指导。
附录
数据集:sales_detail.csv 及完整代码已放置于:Github