文章目录
- 前言
- 原理
- 引入
- 目标
- 定理
- 计算流程:
- 代码实现
- 结束语
前言
apriori是关联规则分析的一个经典算法。它并不是最好的关联规则分析方法,它存在一定的问题。但其贵在“经典”。本文仅作简单的介绍,如果对原理需要严谨的说明的朋友,本文并不适合你。
原理
引入
对于关联规则分析,顾名思义。就是要选择几种或者多种东西之间的关联,其规则是什么。
举个栗子,尿布和啤酒的故事基本上已经广为流传。讲的是在超市购物中,人们再购买了尿布的同时,是否会购买啤酒?这两个看起来毫不相关的商品,却被证明是存在关联性的。得证之后那么超市就可以将啤酒放置再尿布售卖处的附近。以便更多的顾客发现啤酒而取购买。
那么?如何证明其真的存在关联性呢?假如现在我们有一批数据
顾客\购买的商品 | ||||
1 | 面包 | 牛奶 | ||
2 | 面包 | 尿布 | 啤酒 | 鸡蛋 |
3 | 牛奶 | 尿布 | 啤酒 | 可乐 |
4 | 面包 | 牛奶 | 尿布 | 啤酒 |
5 | 面包 | 牛奶 | 尿布 | 可乐 |
在此之前,先来了解一些概念(以下概念用上面商品的关联规则来解释,并不严谨,但大体意思差不多)
**①支持度:**指顾客购买某种商品的可能性。
计算方法:。其中num(A)的意思是A在数据中出现的次数。all为数据的总数。直接用商品在所有事件中出现的次数除以数据总数。
例如数据中,我们要求面包的支持度。因为面包在1,2,4,5中都出现了,总共4次。又因为数据总数是5。所以支持度为0.8。
为什么要支持度?因为对于出现次数少的商品,其意义不大。
**②置信度:**指购买了A商品的人去购买B商品的可能性。
计算方法:。其中意为数据中购买了A商品下又购买了B的次数。num(A)是购买A商品的数量。
例如在数据中,我们想要求购买了面包对牛奶的置信度;可以看到面包一种出现了四次。其中在2的时候没有买牛奶,其余都买了。我们就说此时的置信度为。
**③提升度:**指商品A和商品B之间存在的关系
计算方法:。
如果A提升度大于1。我们就说商品A购买量增加能够提高B商品的购买量。小于1则说明商品A的购买量增加会导致商品B的购买量减少。
目标
商品的关联规则分析就是要找出支持度和置信度大于某个阈值的商品。其中支持度和置信度的阈值是我们人工给定的。我们将这些大于阈值的称为频繁项集。要做的,就是找出所有的频繁项集。
频繁项集又分为频繁1项集、二项集、三项集等等。
对于上面的数据,我们计算里面的商品的种类。得到{可乐, 啤酒, 尿布, 牛奶, 面包, 鸡蛋}称作一项集。。再将一项集两两组合,得到二项集。以此类推。最后再计算出各个项的频繁项,得到频繁项集。
定理
如果要计算所有的组合。一旦数据多起来,计算量将是非常的大的。每一次都要计算两两组合。这无疑非常耗费算力。所以就要改进。如何改进?先看两个定理:
①如果一个项集是频繁项集,那么它的所有子集也是频繁的。
②如果一个项集是非频繁的,则他的所有超集一定是非频繁的。
比如下面这张图(来自慕课网)
这样,我们在计算一项集的时候顺便计算处频繁一项集,那么那些不频繁就没必要组合成二项集了。因为它们的超集也是非频繁的。
计算流程:
看图(来自慕课网)
其中{A,B,C,D,E}代表商品。通过层层计算。最终得到频繁1项集,2项集,3项集。
代码实现
注意两点,
①代码计算频繁项集的时候仅用支持度计算,加上置信度其实也是同支持度一个道理的。读者自行加入即可,本文主要是为了代码的简洁。另外,代码将采用原始的python来实现,不使用numpy来提升速度,因为笔者认为使用numpy实现的话数据储存量将增大,有些得不偿失。
②当计算k项集时,需要两个k-1项集来组合而成。怎么组合?
假如我们的集合里面的数据都是按照一定的顺序排列的,那么我们就可以说如果两个集合的前k-1个元素一样,如果这些元素一一相等,我们就说他们是可连接组合的。
比如我们要组合{A,B}、{A,C}。我们取出每个集合的前k-1个元素。所以对于第一个集合是{A},第二个集合{A},因为他们相等,所以我们就可以组合成{A,B,C}。
def calculate_support(classify,data_len,support_per):
'''
:param classify: 数据类别数
:param data_len: 总数据的长度
:param support_per: 最小支持度
:return:
'''
supports_dict = {} #生成一个用于储存类别的字典
for i in classify: #循环每一个类别
num=0 #计算类别出现的次数
for j in data: #循环总数据的每一行
if set(i).issubset(set(j)): #如果某个类别在总数据的一行中,则出现次数+1
num+=1
support=num/data_len #计算支持度
if support>=support_per: #如果支持度大于设定的支持度,则将该类别加入到字典中
supports_dict[" ".join(i)]=support #采用键为字符串的形式储存
return supports_dict #返回字典
def main(data,support_per):
num_n= len(data) #数据的长度n
one_dimension=sum(data,[]) #原数据是二维数据,此方法可将数据展平
classify=[ [i] for i in sorted(set(one_dimension))] #求出数据类别数并转化为二维形式,原因是计算支持度的形式是针对二维形式的数据
supports_dict=calculate_support(classify,num_n,support_per)#计算频繁1项集并将结果保存到字典中
a=sorted(supports_dict) #对1项集进行排序
a=[ [i] for i in a] #转化为二维形式
k=0 #程序计数从0开始,所以k=0意为着此时是1项集。
while True:
k+=1 #项集+1
two_len=len(a) #计算项集的长度
classify=[] #生成一个用于储存 k项集的列表
#循环计算出k-1项集的各个组合
for i in range(two_len - 1):
for j in range(i + 1, two_len):
if a[i][0:k-1]==a[j][0:k-1]:#如果前k-1项相同,那么我们就认为可以连接,此处和a[i][:-1]==a[j][:-1]一样
classify.append(sorted(set(a[i]+a[j]))) #将两个数组相加后再去重然后排序加入到classify中
a=calculate_support(classify,num_n,support_per)#计算k项集的支持度,返回字典
supports_dict=dict(**supports_dict,**a) #将频繁的第k项集加入到我们用于储存结果的字典中
if len(a)<=1: #如果频繁项集a的项小于等于1,说明不再可以两两组合,退出循环
print(supports_dict) #打印频繁项集结果
break
a=[ i.split() for i in a] #将频繁项集转化为数组,并开始下一个循环
if __name__ == '__main__':
data=[["面包","牛奶",],
["面包","尿布","啤酒","鸡蛋"],
["牛奶","尿布","啤酒","可乐"],
["面包","牛奶","尿布","啤酒"],
["面包","牛奶","尿布","可乐"]]
main(data,support_per=0.5)
结束语
以上介绍并不严谨,如有错误,还请指出。阿里嘎多。