关联分析的目标包括两项:发现频繁项集和发现关联规则。首先需要找到频繁项集,然后才能获得关联规则。
APriori算法时发现频繁项集的一种方法。APriori算法的两个输入参数是最小支持度和数据集,算法首先会生成所有单个物品的项集列表;接着扫描交易记录来查看哪些项集满足最小支持度要求,那些不满足最小支持度的集合将会被去掉;然后,对剩下来的集合进行组合以生成包含两个元素的项集;接下来,再重新扫描交易记录,去掉不满足最小支持度的项集。该过程重复到所有项集都被去掉。
生成候选项集
在使用Python来对整个程序编码之前,需要创建一些辅助函数。下面创建一个用于构建初始集合的函数,也会常见一个通过扫描数据集以寻找交易记录子集的函数。数据集扫描的伪代码大致如下:
对数据集中的每条交易记录tran:
对每个候选项集can:
检查一下can是否是tran的自己:
如果是,则增加can的计数值
对每个候选项集:
如果其支持度不低于最小值,则保留该项集
返回所有频繁项列表
实际代码实现:
python
def loadData():
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]
#构建集合C1。C1是大小为1的所有候选项集的集合。
def createC1(dataSet):
C1=[]
for transaction in dataSet:
for item in transaction:
if not [item] in C1:
C1.append([item])
C1.sort()
#对C1中每个项构建一个不变集合
return list(map(frozenset,C1))
def scanD(D,Ck,minSupport):
ssCnt={}
for tid in D:
for can in Ck:
if can.issubset(tid):
if not can in ssCnt:
ssCnt[can]=1
else:
ssCnt[can]=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
上述代码包含了3个函数,其中第一个函数loadDataSet()创建了一个用于测试的简单数据集。
createC1()函数将构建集合C1。C1是大小为1的所有候选集的集合。APriori算法首先构建集合C1,然后扫描数据集来判断这些只有一个元素的项集是否满足最小支持度的要求。那些满足最低要求的项集构成项集L1,而L1中的元素相互组合构成C2,C2再进一步过滤变为L2,以此类推。
因此算法需要一个createC1()函数开构建第一个候选项集的列表C1。由于算法一开始是从输入数据中提取候选项集列表,所以这里需要一个特殊的函数来处理,而后续的项集列表则是按一定的格式存放的。
首先创建一个空列表C1,它用来存储所有不重复的项值。接下来遍历数据集中的所有交易记录。对每一条记录,遍历记录中的每一个项。如果某个物品项没有在C1中出现,则将其添加到C1中。这里并不是简单地添加每个物品项,而是添加只包含该物品项的一个列表。这样做的目的是为每个物品项构建一个集合。
因为在APriori算法的后续处理中,需要做集合操作。Python不能创建只有一个整数的集合,因此这里必须使用列表。
这就是我们使用一个由单物品列表组成的大列表的原因。最后,对大列表进行排序并将其中的每个单元素列表映射到frozenset(),最后返回frozenset的列表。
下一个函数是scanD(),它有三个参数:数据集、候选项集列表Ck以及感兴趣项集的最小支持度minSupport。该函数用于从C1生成L1。另外,该函数会返回一个包含支持度值的字典以备后用。scanD()函数首先创建一个空字典ssCnt,然后遍历数据集中的所有交易记录已经C1中的所有候选集。如果C1中的集合是记录的一部分,那么增加字典中对应的计数值。这里字典的键就是集合。当扫描完数据集中的所有项以及所有的候选集时,就需要计算支持度。不满足最小支持度要求的集合不会输出。函数也会先构建一个空列表,该列表包含满足最小支持度要求的集合。下一个循环遍历字典中的每个元素并且计算支持度。如果支持度满足最小支持度要求,则将字典元素添加到retList中。可以使用语句 retList.insert(0,key) 在列表的首部插入任意新的集合。函数最后返回最频繁项集的支持度supportData。
实际运行效果:
python
dataSet=loadData()
C1=createC1(dataSet)
print('C1:',C1)
D=list(map(set,dataSet))
print(D)
L1,suppData0=scanD(D,C1,0.5)
print(L1)
上图最后一行中的4个项集构成了L1列表,该列表中的每个单物品项集至少出现在50%以上的记录中。由于物品4没有达到最小支持度要求,所以没有包含在L1中。通过去掉这件物品,减少了查找两物品项的工作量。
组织完整的APriori算法
整个APriori算法的伪代码如下:
当集合中项的个数大于0时
构建一个k个项组成的候选项集的列表
检查数据以确认每个项集都是频繁的
保留频繁项集并构建k+1项组成的候选项集的列表
既然可以过滤集合,那么就能够构建完整的APriori算法了:
python
def aprioriGen(Lk,k):
retList=[]
lenLk=len(Lk)
for i in range(lenLk):
for j in range(i+1,lenLk):
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
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=k+1
return L,supportData
上述代码中有两个函数,其中主函数是apriori(),它会调用aprioriGen()来创建候选集项Ck。
函数aprioriGen()的输入参数为频繁项集列表Lk与项集元素个数k,输出为Ck。举例来说,该函数以{0}、{1}、{2}作为输入,会生成{0,1}、{0,2}、{1,2}。要完成这一点,首先创建一个空列表,然后计算Lk中的元素数目。接下来,比较Lk中的每一个元素与其他元素,这可以通过两个for循环来实现。接着,取列表中的两个集合进行比较,如果这两个列表的前面k-2个元素都相同,那么就将这两个集合合成一个大小为k的集合。这里使用集合的并操作来完成。
上面的操作被封装在apriori()函数中。给该函数传递一个数据集以及一个支持度,函数会生成候选项集的列表,这通过首先创建C1然后读入数据集将其转换为D(集合列表)来完成。程序中使用map列表将set()映射到dataSet列表中的每一项。接下来利用scanD()函数来创建L1,并将L1放入列表L中。L会包含:L1、L2、L3......。现在有了L1,后面会继续找L2、L3......,这可以通过while循环完成,它创建包含更大项集的更大列表,直到下一个大的项集为空。
之后首先使用aprioriGen()来创建Ck,然后使用scanD()基于C看来创建Lk。Ck是一个候选项集列表,然后scanD()会变量Ck,丢掉不满足最小支持度要求的项集。Lk列表被添加到L,同时增加k的值,重复上述过程。最后,当Lk为空时,程序返回L并退出。
代码运行效果:
python
dataSet=loadData()
L,suppData=apriori(dataSet)
print(L)
再尝试一下70%的支持度要求:
python
dataSet=loadData()
L,suppData=apriori(dataSet,minSupport=0.7)
print(L)
变量suppData是一个字典,它包含我们项集的支持度值。