使用Apriori算法进行关联分析
关联分析力图在大规模数据集中寻找关系,这些关系有两种形式:频繁项集、关联规则。
频繁项集:经常出现在一块的物品的集合
关联规则:两种物品之间可能存在的关系
支持度:数据集上包含该项集的记录所占的比例
可信度/置信度:针对物品A-->物品B的关联规则来定义。物品A-->物品B的置信度 = 支持度(A | B)/支持度(A)
Apriori算法原理:如果某个项集是频繁的,则它的所有子集也是频繁的。==》如果某个项集是非频繁的,则它的所有超集也是非频繁的。
寻找频繁项集:
辅助函数:
def loadDataSet(): return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]] def createC1(dataSet): C1 = [] for transaction in dataSet: for item in transaction: if not [item] in C1: #这里并不是简单地添加每个物品项,而是添加只包含该物品项的一个列表。 C1.append([item]) C1.sort() return list(map(frozenset, C1)) #frozenset创建不可变集合 def scanD(D, Ck, minSupport): #数据集,候选项集列表,最小支持度,过滤不符合最小支持度的项集 ssCnt = {} for tid in D: for can in Ck: if can.issubset(tid): if can not in ssCnt: ssCnt[can]=1 else: ssCnt[can] += 1 numItems = float(len(D)) retList = [] supportData = {} for key in ssCnt: support = ssCnt[key]/numItems if support >= minSupport: retList.insert(0,key) supportData[key] = support return retList, supportData
完整的Apriori算法:
def aprioriGen(Lk, k): #参数为当前频繁项集列表,需要生成的新的频繁项集的元素个数。因为每次生成的新的项元素个数比当前多1,所以[:k-2]表示当前项除了最后一个元素,即为[:,-1] retList = [] print(Lk) print('k:',k) lenLk = len(Lk) for i in range(lenLk): for j in range(i+1, lenLk): print('i:',Lk[i]) print('j:',Lk[j]) 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]) #集合操作符,求并集 print(retList) return retList def apriori(dataSet, minSupport = 0.5): C1 = createC1(dataSet) D = list(map(set, dataSet)) L1, supportData = scanD(D, C1, minSupport) L = [L1] k = 2 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
测试:
if __name__=='__main__': dataSet=loadDataSet() L,suppData=apriori(dataSet,0.5) print(L) print(suppData)
输出:
[[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({3, 5}), frozenset({1, 3}), frozenset({2, 5}), frozenset({2, 3})], [frozenset({2, 3, 5})], []] {frozenset({5}): 0.75, frozenset({3}): 0.75, frozenset({2, 3, 5}): 0.5, frozenset({1, 2}): 0.25, frozenset({1, 5}): 0.25, frozenset({3, 5}): 0.5, frozenset({4}): 0.25, frozenset({2, 3}): 0.5, frozenset({2, 5}): 0.75, frozenset({1}): 0.5, frozenset({1, 3}): 0.5, frozenset({2}): 0.75}
从频繁项集中挖掘关联规则:
如果某条规则不满足最小可信度要求,则该规则的所有子集也不满足最小可信度要求。
分级法:首先从一个频繁项集开始,接着创建一个规则列表,其中规则右部只包含一个元素,然后对这些元素进行测试。接下来合并所有剩余规则来创建一个新的规则列表,其中规则右部包含两个元素。
关联规则生成函数:
def generateRules(L, supportData, minConf=0.7): #频繁项集列表,支持度列表,最小可信度 bigRuleList = [] #包含可信度的规则列表 for i in range(1, len(L)): #因为无法对只有单个元素的频繁项集构建规则,智能从L[1]开始,L[1]是包含两个元素的频繁项集列表 for freqSet in L[i]: #遍历当前L[i]的所有频繁项集 H1 = [frozenset([item]) for item in freqSet] #从频繁项集中得到元素列表 if (i > 1): #i>1时,频繁项集至少有三个元素 rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf) #对H1进一步合并成含有两个元素的频繁项集列表 else: #频繁项集有两个元素,则计算可信度 calcConf(freqSet, H1, supportData, bigRuleList, minConf) return bigRuleList def calcConf(freqSet, H, supportData, brl, minConf=0.7): #计算规则的可信度并且找到满足最小可信度要求的规则;函数内部没有重置brl,所以brl可以保存满足条件的规则。 prunedH = [] #保存满足最小可信度的规则列表 for conseq in H: # conf = supportData[freqSet]/supportData[freqSet-conseq] #计算可信度 print(freqSet - conseq, '====>', conseq, '可信度:', conf) if conf >= minConf: # print(freqSet-conseq,'====>',conseq,'可信度:',conf) brl.append((freqSet-conseq, conseq, conf)) #对列表brl补充,brl即为前面通过检查的bigRuleList prunedH.append(conseq) print('prunedH:',prunedH) return prunedH def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7): #频繁项集,可以出现在规则右面的元素列表H m=len(H[0]) #第一次调用时m=1 if len(freqSet) > m + 1: #如果频繁项集的元素个数大于H的元素长度加1,例如freqSet=[2,3,5],H=[2,3,5]时,3>1+1,此时把H的元素融合成[[2,3],[2,5],[3,5]] Hmp1 = aprioriGen(H, m+1) #Hmpl=[[2,3],[2,5],[3,5]] print('freqSet:',freqSet) print('Hmp1:',Hmp1) Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf) if len(Hmp1) > 1: #如果不止一条规则满足要求,则使用Hmp1迭代调用rulesFromConseq来判断是否可以进一步组合这些规则 rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)
测试:
if __name__=='__main__': dataSet=loadDataSet() L,suppData=apriori(dataSet,0.5) rules=generateRules(L,suppData,0.7) print(rules)
输出:
frozenset({5}) ====> frozenset({3}) 可信度: 0.6666666666666666 frozenset({3}) ====> frozenset({5}) 可信度: 0.6666666666666666 prunedH: [] frozenset({3}) ====> frozenset({1}) 可信度: 0.6666666666666666 frozenset({1}) ====> frozenset({3}) 可信度: 1.0 prunedH: [frozenset({3})] frozenset({5}) ====> frozenset({2}) 可信度: 1.0 frozenset({2}) ====> frozenset({5}) 可信度: 1.0 prunedH: [frozenset({2}), frozenset({5})] frozenset({3}) ====> frozenset({2}) 可信度: 0.6666666666666666 frozenset({2}) ====> frozenset({3}) 可信度: 0.6666666666666666 prunedH: [] freqSet: frozenset({2, 3, 5}) Hmp1: [frozenset({2, 3}), frozenset({2, 5}), frozenset({3, 5})] frozenset({5}) ====> frozenset({2, 3}) 可信度: 0.6666666666666666 frozenset({3}) ====> frozenset({2, 5}) 可信度: 0.6666666666666666 frozenset({2}) ====> frozenset({3, 5}) 可信度: 0.6666666666666666 prunedH: [] [(frozenset({1}), frozenset({3}), 1.0), (frozenset({5}), frozenset({2}), 1.0), (frozenset({2}), frozenset({5}), 1.0)]
上述代码有逻辑错误:
在函数rulesFromConseq中,如果H有三个元素,例如原文中的H=[2,3,5],freqSet=[2,3,5],程序会将H融合成[[2,3],[2,5],[3,5]],不会校验[2,3]==>[5]、[2,5]==>[3]、[3,5]==>[2]的可信度,所以结果少了这些规则。
修改:
def generateRules(L, supportData, minConf=0.7): #频繁项集列表,支持度列表,最小可信度 bigRuleList = [] #包含可信度的规则列表 for i in range(1, len(L)): #因为无法对只有单个元素的频繁项集构建规则,智能从L[1]开始,L[1]是包含两个元素的频繁项集列表 for freqSet in L[i]: #遍历当前L[i]的所有频繁项集 H1 = [frozenset([item]) for item in freqSet] #从频繁项集中得到元素列表 if (i > 1): #i>1时,频繁项集至少有三个元素 H1 = calcConf(freqSet, H1, supportData, bigRuleList, minConf) #计算可信度,过滤可信度低的候选规则 rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf) #对H1进一步合并成含有两个元素的频繁项集列表 else: #频繁项集有两个元素,则计算可信度 calcConf(freqSet, H1, supportData, bigRuleList, minConf) return bigRuleList
测试输出:
[(frozenset({1}), frozenset({3}), 1.0), (frozenset({5}), frozenset({2}), 1.0), (frozenset({2}), frozenset({5}), 1.0), (frozenset({3, 5}), frozenset({2}), 1.0), (frozenset({2, 3}), frozenset({5}), 1.0)]
if…else…多余,修改为下:
def generateRules(L, supportData, minConf=0.7): #频繁项集列表,支持度列表,最小可信度 bigRuleList = [] #包含可信度的规则列表 for i in range(1, len(L)): #因为无法对只有单个元素的频繁项集构建规则,只能从L[1]开始,L[1]是包含两个元素的频繁项集列表 for freqSet in L[i]: #遍历当前L[i]的所有频繁项集 H1 = [frozenset([item]) for item in freqSet] #从频繁项集中得到元素列表 rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf) #对H1进一步合并成含有两个元素的频繁项集列表 return bigRuleList def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7): #频繁项集,可以出现在规则右面的元素列表H m=len(H[0]) #第一次调用时m=1 if len(freqSet) > m: #如果频繁项集的元素个数大于H的元素长度,例如freqSet=[2,3,5],H=[2,3,5]时,3>1,此时把H的元素融合成[[2,3],[2,5],[3,5]] # print('freqSet:',freqSet) # print('Hmp1:',Hmp1) Hmp1 = calcConf(freqSet, H, supportData, brl, minConf) if len(Hmp1) > 1: #如果不止一条规则满足要求,则使用Hmp1迭代调用rulesFromConseq来判断是否可以进一步组合这些规则 Hmp1=aprioriGen(Hmp1,m+1) rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf) #递归
进一步修改rulesFromConseq里的递归函数如下:
def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7): m = len(H[0]) while (len(freqSet) > m): # 判断长度 > m,这时即可求H的可信度 H = calcConf(freqSet, H, supportData, brl, minConf) if (len(H) > 1): # 判断求完可信度后是否还有可信度大于阈值的项用来生成下一层H H = aprioriGen(H, m + 1) m += 1 else: # 不能继续生成下一层候选关联规则,提前退出循环 break