关联规则之Apriori
一、名词解释:
支持度和置信度:
支持度、置信度越大,商品出现一起购买的次数就越多,可信度就越大
作用:找到商品购买记录中反复一起出现的商品,帮助营销人员做更好的策略,帮助顾客方便购买
策略:同时购买的商品放在一起、同时购买的商品放在两端
support(A->B) = P(AB)
confidence(A->B) = P(B|A)=P(AB)/P(A)
项集:项的集合,也就是商品的组合
K项集:K种商品的组合
频繁项集:如果项集的相对支持度满足给定的最小值支持度,则该项集是频繁项集
强关联规则:满足给定支持度与置信度阈值的关联规则
二、明确问题
明确问题:
1.找到总是一起出现的物品
2.提出衡量标准:支持度、置信度阈值
3.支持度、置信度计算
4.频繁项集
5.由频繁项集找到强管关联规则
找关联规则 --------> 频繁项集
1.找出所有的频繁项集:这个项集出现的次数至少与要求的最小计数一样
2.由频繁项集产生强关联规则:这些关联规则满足最小支持度与最小置信度
挑战:
1.多次数据库扫描
2.巨大数量的候补项集
3.繁琐的支持度计算
改善:
1.减少数据库扫描次数
2.减少候选项集的数量,提高支持度
3.简化候选项集的支持度计算
三、过程,原理:
产生频繁集:
Apriori 的原理:如果某个项集是频繁项集,那么它所有的子集也是频繁的。
即如果 {0,1} 是频繁的,那么 {0}, {1} 也一定是频繁的。
生关联规则:
从一个频繁项集可以产生多少条关联规则呢?可以基于该频繁项集生成一个可能的规则列表,然后测试每条规则的可信度,如果可信度不满足最小值要求,则去掉该规则。类似于前面讨论的频繁项集生成,一个频繁项集可以产生许多可能的关联规则,如果能在计算规则可信度之前就减少规则的数目,就会很好的提高计算效率。
这里有一条规律就是:如果某条规则并不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求,
算法流程:
下面我们对Aprior算法流程做一个总结。
输入:数据集合D,支持度阈值α
输出:最大的频繁k项集
1)扫描整个数据集,得到所有出现过的数据,作为候选频繁1项集。k=1,频繁0项集为空集。
2)挖掘频繁k项集
a) 扫描数据计算候选频繁k项集的支持度
b) 去除候选频繁k项集中支持度低于阈值的数据集,得到频繁k项集。如果得到的频繁k项集为空,则直接返回频繁k-1项集的集合作为算法结果,算法结束。如果得到的频繁k项集只有一项,则直接返回频繁k项集的集合作为算法结果,算法结束。
c) 基于频繁k项集,连接生成候选频繁k+1项集。
3) 令k=k+1,转入步骤2。
从算法的步骤可以看出,Aprior算法每轮迭代都要扫描数据集,因此在数据集很大,数据种类很多的时候,算法效率很低。
四、代码解析:
(1)主函数入口
if __name__ == '__main__':
myDat = LoadDataSet() # 导入数据集
L, suppData = apriori(myDat, 0.5) # 选择频繁项集
print(u"频繁项集L:", L)
print(u"所有候选项集的支持度信息:", suppData)
rules = generateRules(L, suppData, minConf=0.7)
print('rules:\n', rules)
dataSet数据人为设定
# 生成原始数据集用于测试
def LoadDataSet():
return [[1, 3, 4, 5], [2, 3, 5], [1, 2, 3, 4, 5], [2, 3, 4, 5]]
(2)分析第一部分,对频繁集的求解。首先,调用CreateC1(),求解dataSet中有多少不同的值;再用scanD()用阈值minSupport去过滤频繁项集;
# 输入:数据集、最小支持度
def apriori(dataSet, minSupport=0.5):
C1 = CreateC1(dataSet) # 构建初始候选项集C1
D = [set(var) for var in dataSet] #对数据及进行映射至D, 去掉重复的数据记录
L1, suppData = scanD(D, C1, minSupport) # 构建初始的频繁项集,即所有项集只有一个元素
L = [L1] # 最初的L1中的每个项集含有一个元素,新生成的
k = 2 # 项集应该含有2个元素,所以 k=2
# 根据L1寻找L2、L3,通过while来完成
# 他创建包含更大项集的更大列表,直到下一个更大的项集为空
# 候选项集物品组合长度不超过元数据集原数据集最大的物品记录长度
# 如果原始数据集物品记录最大长度为4,候选集最多为4项集
while (len(L[k - 2]) > 0): #如果前面的前面的频繁项集合为空,则停止迭代
Ck = aprioriGen(L[k - 2], k) # 生成候选集合 如果输入为{0}, {1}, {2}, 会生成 {0,1}, {0,2}, {1,2}
Lk, supK = scanD(D, Ck, minSupport)
suppData.update(supK) # 将新的项集的支持度数据加入原来的总支持度字典中
L.append(Lk) # 将符合最小支持度要求的项集加入L
k += 1 # 新生成的项集中的元素个数应不断增加
return L, suppData # 返回所有满足条件的频繁项集的列表,和所有候选项集的支持度信息
CreateC1(): 求解dataSet中有多少不同的值
# 遍历数据集每项物品,建立1-项集
def CreateC1(dataSet):
'''
构建初始候选项集的列表,即所有候选项集只包含一个元素,
C1是大小为1的所有候选项集的集合
'''
C1 = []
for transaction in dataSet:
for item in transaction:
if [item] not in C1:
C1.append([item])
C1.sort() #print("C1 : " + str(C1)) #输出[[1],[2],[3],[4],[5]]
return [frozenset(var) for var in C1] # frozenset数据类型,不能被修改 只有var是数组的时候才能够frezenset(var)
#print([var for var in map(frozenset,C1)]) #得到[frozenset({1}), frozenset({2}), frozenset({3}), frozenset({4}), frozenset({5})]
# print([var for var in map(frozenset,C1)])
scanD()求解支持度,用阈值minSupport去过滤频繁项集。
# 输入:数据集D, 候选集CK, 最小支持度
# 候选集Ck由上一层的频繁项集Lk-1组合得到
# 用最小支持度minSupport对候选集Ck过滤
# 输出:第k层的频繁项集Lk,每项的支持度
def scanD(D, Ck, minSupport):
'''
计算Ck中的项集在数据集合D(记录或者transactions)中的支持度,
返回满足最小支持度的项集的集合,和所有项集支持度信息的字典。
'''
# 建立字典,key为候选集Ck中的每项, value为该物品在所有物品记录中出现的次数
ssCnt = {}
# 对比候选集中的每项与原物品记录,统计出现的次数
# 遍历每条物品记录
for tid in D: # 对于每一条transaction
for can in Ck: # 对于每一个候选项集can,检查是否是transaction的一部分 # 即该候选can是否得到transaction的支持
if can.issubset(tid):
ssCnt[can] = ssCnt.get(can, 0) + 1 #get() 函数返回指定键的值,如果值不在字典中返回默认值。
numItems = float(len(D)) # 数据集中总的记录数,物品购买记录总数,用于计算支持度
retList = [] # 记录经最小支持度过滤后的频繁项集
# key ---------> 候选集中满足条件的项
# value -------> 该项支持度
supportData = {}
for key in ssCnt:
support = ssCnt[key] / numItems # 每个项集的支持度
if support >= minSupport: # 将满足最小支持度的项集,加入retList
retList.insert(0, key)
supportData[key] = support # 记录该项的支持度
return retList, supportData
aprioriGen() :生成候选集合 如果输入为{0}, {1}, {2}, 会生成 {0,1}, {0,2}, {1,2}
选取候选集的时候有个方法:比较频繁项集中的每项与其他项,若两项的前k-1个元素相同,那么就将两项合并。这是由于apriori的算法原理的问题。
# 由上层频繁k-1项集生成候选k项集
# 如果输入为{0}, {1}, {2}, 会生成 {0,1}, {0,2}, {1,2}
# 输入:频繁k-1项集,新的候选集元素个数k
# 输出:候选集
def aprioriGen(Lk, k): # Aprior算法
'''
由初始候选项集的集合Lk生成新的生成候选项集,
k表示生成的新项集中所含有的元素个数
'''
retList = [] # 保存新的候选集
lenLk = len(Lk)
# 比较频繁项集中的每项与其他项,若两项的前k-1个元素相同,那么就将两项合并
for i in range(lenLk):
for j in range(i + 1, lenLk):
L1 = list(Lk[i])[: k - 2]
# 候选集其余项的k-1个元素,每次只有其余项中的一项
L2 = list(Lk[j])[: k - 2]
L1.sort()
L2.sort()
if L1 == L2:
# 相同,两项合并
retList.append(Lk[i] | Lk[j])
return retList #例如: Lk = [{A,C},{B,C},{B,E},{C,E}] 找有三个数的频繁集: Lk[1][:1] == 'B' ==Lk[2][:1] 所以retList = [{B,C,E}]
(3)生成关联规则:
主函数:generateRules():
这里有一条规律就是:如果某条规则并不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求,
def generateRules(L, supportData, minConf=0.7):
'''
根据频繁项集和最小可信度生成规则。
L(list):存储频繁项集
supportData(dict):存储着所有项集(不仅仅是频繁项集)的支持度
minConf(float):最小可信度
'''
bigRuleList = []
for i in range(1, len(L)):
for freqSet in L[i]: # 对于每一个频繁项集的集合freqSet,(从k=2开始)
H1 = [frozenset([item]) for item in freqSet]
if i > 1: # 如果频繁项集中的元素个数大于2,需要进一步合并,这样做的结果就是会有“[1|多]->多”(右边只会是“多”,
# 因为合并的本质是频繁子项集变大,而calcConf函数的关联结果的右侧就是频繁子项集),的关联结果
rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
else:
calcConf(freqSet, H1, supportData, bigRuleList, minConf)
return bigRuleList
计算置信度:calcConf()
def calcConf(freqSet, H, supportData, brl, minConf=0.7): # 规则生成与评价
'''
计算规则的可信度,返回满足最小可信度的规则。
freqSet(frozenset):频繁项集
H(frozenset):频繁项集中所有的元素
supportData(dic):频繁项集中所有元素的支持度
brl(tuple):满足可信度条件的关联规则
minConf(float):最小可信度
'''
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
rulesFromConseq():# 如果频繁项集中的元素个数大于2,需要进一步合并,这样做的结果就是会有“[1|多]->多”(右边只会是“多”,# 因为合并的本质是频繁子项集变大,而calcConf函数的关联结果的右侧就是频繁子项集),的关联结果
def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
'''
对频繁项集中元素超过2的项集进行合并。
freqSet(frozenset):频繁项集
H(frozenset):频繁项集中的所有元素,即可以出现在规则右部的元素
supportData(dict):所有项集的支持度信息
brl(tuple):生成的规则
'''
m = len(H[0])
if len(freqSet) > m + 1: # 查看频繁项集是否足够大,以到于移除大小为 m的子集,否则继续生成m+1大小的频繁项集
Hmp1 = aprioriGen(H, m + 1)
Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf) # 对于新生成的m+1大小的频繁项集,计算新生成的关联规则的右则的集合
if len(Hmp1) > 1: # 如果不止一条规则满足要求(新生成的关联规则的右则的集合的大小大于1),进一步递归合并,
# 这样做的结果就是会有“[1|多]->多”(右边只会是“多”,因为合并的本质是频繁子项集变大,
# 而calcConf函数的关联结果的右侧就是频繁子项集)的关联结果
rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)