有时候面对一堆杂乱的数据,我们需要寻找到其中蕴含的规律,这种时候我们就要借助关联分析。它寻找的规律有两种形式:频繁项集和关联规则。
频繁项集:经常出现在一块的数据的集合。
关联规则:暗示几个特征间可能存在很强的关系。
那么如何来定义要寻找的频繁和关系呢?有很多种方法,但最重要的有两类:支持度以及可信度或置信度。
支持度:数据集中包含该项集的记录所占的比例。
可信度或置信度:它的定义类似条件概率,关系A→B的可信度 = (A,B)的支持度 / A的支持度。也就是说,它是指如果A表现出某些特定特征时,B表现出另外一些特定的特征的概率。
如果我们想找到这两个指标高于一定值的所有项集,一个直观的做法是得到一个数据所有可能的组合,然后依次计算其值。但这样的方法在处理大型数据时显然不可取,为了解决这个问题,我们引入了Apriori方法。
Apriori:
我们直到,如果(A,B,C…)是频繁项集,那么它其中的每一个项集都应该是频繁项集。那么根据逻辑学,如果一个子项集不是频繁项集,那么其父项集也必然不是频繁项集。
这就是Apriori方法的原理。我们从小至大的计算每个项集的支持度,如果其支持度小于设定值,就将其,以及其父项集移除出搜索集,这样就能极大地减小计算的复杂度。
伪代码:
对数据集中的每条记录trans:
对每个候选项集can:
检查一下can是否是trans的子集:
如果是:则增加can的计数值
对每个候选集:
如果其支持度大于给定值,则保留
返回所有频繁集列表
代码如下:
import numpy as np
#载入测试集
def loadDataSet():
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]
#取数据集的每个不重复的单元数据,建立一个frozenset
def createC1(dataSet):
C1 = []
#遍历每一行
for trans in dataSet:
#遍历每一列
for item in trans:
if [item] not in C1:
C1.append([item])
C1.sort()
return [frozenset(t) for t in C1]
#找到支持度符合要求的单元数据,并返回这些单元数据组成的集合及表示其支持度的dict
#D:转换为set类型的数据集
#Ck:每个单元项组成的frozenset
#minSupport:最小支持度
def scanD(D,Ck,minSupport):
#数据行数
numItems = len(D)
#单元项出现次数
ssCnt = {}
#对数据集的每一行:
for tid in D:
#对每个单元项:
for can in Ck:
#如果单元项在该行:
if can.issubset(tid):
#若此单元项未记录,则记录它
if can not in ssCnt:
ssCnt[can] = 1
#若已有记录,则记录+1
else:
ssCnt[can] += 1
#符合支持度的单元项列表
retList = []
#记录每个符合支持度的单元项的支持度
supportData = {}
#对出现次数记录列表中的每一项:
for key in ssCnt:
support = ssCnt[key]/numItems
if support >= minSupport:
retList.append(key)
supportData[key] = support
return retList,supportData
#将数据集转换为集合
#dataSet:数据集
#书中得到D是使用了map函数,但我实测后发现用这种方法会出现各种奇怪的问题
#但直接使用set()又不能转换列表,于是干脆自己写了个转换函数
def tranSet(dataset):
D = []
#遍历数据集的每一行:
for i in range(len(dataSet)):
#将其转换为集合且加入列表D中
D.append(set(dataSet[i]))
return D
#由含每个项含k和数据的集合合成含k+1个数据的集合
#Lk:每个项含k个数据的集合
#k:集合所含数据数
def aprioriGen(Lk,k):
#合并后的集合
retList = []
lenLk = len(Lk)
#遍历初始集合
for i in range(lenLk):
#将初始集合中的每个项与其后项对比
for j in range(i+1,lenLk):
#为了提高效率,只用比较前k-2项即可
L1 = list(Lk[i])[:k-2]
L2 = list(Lk[j])[:k-2]
L1.sort()
L2.sort()
#合并项
if L1 == L2:
retList.append(Lk[i] | Lk[j])
return retList
#apriori算法执行函数
#dataSet:数据集
#minSupport:最小支持度
def apriori(dataSet,minSupport = 0.5):
#得到由不重复的单元数据组成的frozenset
C1 = createC1(dataSet)
#每一单元项内去重
D = tranSet(dataSet)
#得到由符合支持度的单元数据组成的列表及其相应支持度
L1,supportData = scanD(D,C1,minSupport)
#初始化全体支持列表
L = [L1]
k = 2
#当每个项含k-1个数据的集合非空时,继续进行合并
while(len(L[k-2]) > 0):
#合并当前集合
Ck = aprioriGen(L[k-2],k)
#求出对应支持度
Lk,supK = scanD(D,Ck,minSupport)
supportData.update(supK)
L.append(Lk)
k += 1
return L,supportData
#计算关联度控制函数
#L:所有符合支持度的组合组成的集合
#supportData:包含支持度的dict
#minConf:最小关联度
def generateRules(L,supportData,minConf = 0.7):
#关联度列表
bigRuleList = []
#遍历所有符合支持度的组合组成的集合的每一层,因为i=0时每个项只包含一个数据,无法计算关联度,故舍去
for i in range(1,len(L)):
#遍历每一层内的每一项
for freqSet in L[i]:
#得到由该项内所有数据构成的frozenset
H1 = [frozenset([item]) for item in freqSet]
#若每一项内数据数大于2:
if i > 1 :
rulesFromConseq(freqSet,H1,supportData,bigRuleList,minConf)
#否则直接计算关联度
else:
calcConf(freqSet,H1,supportData,bigRuleList,minConf)
return bigRuleList
#计算关联度
#freqSet:当前项
#H:由当前项转换的frozenset
#supportData:支持度dict
#brl:关联度列表的引用
#minConf:最小关联度
def calcConf(freqSet,H,supportData,brl,minConf = 0.7):
#当前层符合最小关联度的集合的后件集合,用于计算当前层符合最小关联度的集合数,进而判断是否继续计算
prunedH = []
#遍历当前项的每一个数据
for conseq in H :
#计算关联度,即当前关系的支持度除以其前件的支持度
conf = supportData[freqSet]/supportData[freqSet - conseq]
#若关联度大于最小关联度:
if conf >= minConf:
print(freqSet - conseq,"-->",conseq,"conf:",conf)
brl.append((freqSet - conseq,conseq,conf))
prunedH.append(conseq)
return prunedH
#计算数据量超过两个的项的关联度
#freqSet:当前项
#H:由当前项转换的frozenset
#supportData:支持度dict
#brl:关联度列表的引用
#minConf:最小支持度
def rulesFromConseq(freqSet,H,supportData,brl,minConf = 0.7):
#计算前件数量
m = len(H[0])
#若数据长度大于后件数,即有前件,可以构成关联规则,则继续生成
#书上这里是大于m+1,我无法理解这是为什么,上网找了其它人的学习笔记
#发现有人在这里改成了现在的样子,与书上代码相比,多了三条前件含两个数据的关联度,可以认为这是作者的一处失误
if len(freqSet) > m :
#计算当前项的关联度
Hmp1 = calcConf(freqSet,H,supportData,brl,minConf)
#若符合关联度的项数量超过1,则生成下一层的关联规则,即左端-1,右端+1
#这里我一开始不理解,想了一下意思应该是:如果a,c→b,且a,b→c,那么可能就会有a→b,c
#即若当前符合支持度的项数超过1,那么就有可能有新的关联规则未发现
if len(Hmp1) > 1:
#生成下一层的项集合
Hmp1 = aprioriGen(H,m+1)
#迭代计算该层关联度
rulesFromConseq(freqSet,Hmp1,supportData,brl,minConf)
#主函数
if __name__ == '__main__' :
dataSet = loadDataSet()
L,supportData = apriori(dataSet,0.5)
print("频繁项集:\n",L)
print("关联规则:")
rules = generateRules(L,supportData,0.5)
运行结果如下:
频繁项集:
[[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({1, 3}), frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5})], [frozenset({2, 3, 5})], []]
关联规则:
frozenset({3}) --> frozenset({1}) conf: 0.6666666666666666
frozenset({1}) --> frozenset({3}) conf: 1.0
frozenset({3}) --> frozenset({2}) conf: 0.6666666666666666
frozenset({2}) --> frozenset({3}) conf: 0.6666666666666666
frozenset({5}) --> frozenset({3}) conf: 0.6666666666666666
frozenset({3}) --> frozenset({5}) conf: 0.6666666666666666
frozenset({5}) --> frozenset({2}) conf: 1.0
frozenset({2}) --> frozenset({5}) conf: 1.0
frozenset({3, 5}) --> frozenset({2}) conf: 1.0
frozenset({2, 5}) --> frozenset({3}) conf: 0.6666666666666666
frozenset({2, 3}) --> frozenset({5}) conf: 1.0
frozenset({5}) --> frozenset({2, 3}) conf: 0.6666666666666666
frozenset({3}) --> frozenset({2, 5}) conf: 0.6666666666666666
frozenset({2}) --> frozenset({3, 5}) conf: 0.6666666666666666
书后面还介绍了两个应用,分别是发现国会投票中的模式和发现毒蘑菇的象征。
前者是使用了美国的一个投票网站的api来导入数据,处理后使用apriori算法进行分析,但我在申请apiKey的时候总是提示报错,想了想,反正api也尝试过,干脆就算了。
后者是使用了一个蘑菇数据集,分别表示了蘑菇的毒性和一些其它特征,直接使用apriori算法进行计算就ok了。