1. Apriori算法
关联规则挖掘是数据挖掘中最活跃的研究方法之一 。最早是由 Agrawal 等人提出的1993最初提出的动机是针对购物篮分析问题提出的,其目的是为了发现交易数据库中不同商品之间的联系规则。这些规则刻画了顾客购买行为模式,可以用来指导商家科学地安排进货,库存以及货架设计等。之后诸多的研究人员对关联规则的挖掘问题进行了大量的研究。他们的工作涉及到关联规则的挖掘理论的探索,原有的算法的改进和新算法的设计,并行关联规则挖掘Parallel Association Rule Mining,以及数量关联规则挖掘Quantitive Association Rule Mining 等问题。在提高挖掘规则算法的效率、适应性、可用性以及应用推广等方面,许多学者进行了不懈的努力。
该算法的基本思想是:首先找出所有的频集,这些项集出现的频繁性至少和预定义的最小支持度一样。然后由频集产生强关联规则,这些规则必须满足最小支持度和最小可信度。然后使用第1步找到的频集产生期望的规则,产生只包含集合的项的所有规则,其中每一条规则的右部只有一项,这里采用的是中规则的定义。一旦这些规则被生成,那么只有那些大于用户给定的最小可信度的规则才被留下来。为了生成所有频集,使用了递归的方法。
Apriori算法的伪代码如下:
2. 实验数据
常用到Groceries数据集,该数据集是某个杂货店一个月真实的交易记录,共有9835条消费记录,169个商品。
数据为传统的结构化数据,是一张二维表。每一行代表一个购物记录;每一列代表购物篮数据的一个特定项,其中第一列是购物篮中包含的项数,每条购物记录最多有32个项。数据可视化如下:
3. 实验思路
首先,我们先规定数据集dataset
的格式为二维列表,第一维代表数据集中的样本(即购物记录),第二维代表样本的具体取值(即购物记录中的项)。
按照伪代码所示,我们编写代码,我们先定义几个函数:
3.1 数据预处理
我们从数据可视化记录中可以看出,原始的数据二维表中,存在许多的nan
值,我们去除他们,并整个数据库转化为二维列表:
def LoadData():
rawData = pd.read_csv('groceries - groceries.csv')
dataset = []
for data in rawData.values:
data = set(data[1:])
if np.nan in data:
data.remove(np.nan)
dataset.append(data)
return dataset
3.2 产生频繁1项集
给定数据集和支持度,我们通过对购物记录中每一个特定的项计数,大于支持度的项集我们直接加入频繁项集中:
注意:频繁项集的格被设置为字典,其中键为频繁项集(格式为元组),值为对应的支持度计数。这样就可以很方便的记录所有频繁项集的支持度信息。
def GenerateFrequentItemset1(dataset, support):
candidate = {}
for data in dataset:
for x in data:
key = (x, )
if key in candidate:
candidate[key] += 1
else:
candidate[key] = 1
frequentItemset1 = {key: value for key, value in candidate.items() if value >= support}
return frequentItemset1
3.3 产生候选项集
给定频繁k
项集集合,我们通过合并两两频繁k
项集产生候选频繁k+1
项集。
注意:这里我们假定项集的排列是有序的,我们只合并两个前
k-1
项相同的K
项集。
def GenerateCandidateItemset(candidate):
candidateNext = {}
candidateKey = sorted(candidate)
length = len(candidateKey)
for i in range(length):
front = candidateKey[i][:-1]
for j in range(i+1, length):
rear = candidateKey[j][:-1]
if front == rear:
candidateNext[candidateKey[i] + (candidateKey[j][-1], )] = 0
else:
break
return candidateNext
3.4 候选项集剪枝
给定候选项集,我们通过扫描数据库,对其进行计数,之后根据先验原理对不满足小于支持度计数的候选项集进行剪枝,代码实现如下:
def PruneItemset(candidate, support, dataset):
for data in dataset:
for (i, key) in enumerate(candidate.keys()):
if set(key).issubset(data):
candidate[key] += 1
prunedCandidate = {key: value for (key, value) in candidate.items() if value >= support}
return prunedCandidate
3.5 Apriori频繁项集产生算法
给定数据集,支持度阈值,产生k
项集。为了方便,我们将产生k
项集过程中产生的所有频繁项集的记录保存到supportRecord
中。具体代码如下:
def aprioriFrequentItemset(dataset, k, support):
supportRecord = []
frequentItemset1 = GenerateFrequentItemset1(dataset, support)
frequentItemset = frequentItemset1
supportRecord.append(frequentItemset)
for i in range(k-1):
candidateNext = GenerateCandidateItemset(frequentItemset)
frequentItemset = PruneItemset(candidateNext, support, dataset)
if len(frequentItemset.keys()) == 0:
print('Not exist frequent itemset: {} with support = {}'.format(i+2, support))
break
supportRecord.append(frequentItemset)
return frequentItemset, supportRecord
3.6 产生1后件规则
给定k
频繁项集,我们可以从中提取出后件是1的规则,代码如下:
def GenerateRule1(frequentItemset):
candidateRule1 = {}
for key in frequentItemset.keys():
front = (key[:-1], )
rear = ((key[-1], ), )
candidateRule1[(front + ('->', ) + rear)] = 0
return candidateRule1
3.7 规则产生
给定m
后件规则,我们从中提取出m+1
后件的规则:
def GenerateRule(candidate):
candidateNext = {}
for key in list(candidate.keys()):
front = (key[0][:-1], )
rear = ((key[0][-1], ) + key[-1], )
candidateNext[(front + ('->',) + rear)] = 0
return candidateNext
3.8 规则剪枝
给定m
后件规则,我们通过对其置信度计数进行剪枝,代码如下:
def PruneRule(candidate, confidence, record):
prunedCandidate = {}
for key in candidate.keys():
front = key[0]
rear = key[2]
item = front + rear
candidate[key] = record[len(item)-1][item] / record[len(front)-1][front]
prunedCandidate = {key: value for (key, value) in candidate.items() if value >= confidence}
return prunedCandidate
3.9 Apriori规则产生算法
与Apriori频繁项集产生算法类似的,给定频繁k
项集,置信度,支持度计数,我们从中提取m
后件规则并将再次过程中产生的规则保存在confidenceRecord
中。
def aprioriRule(frequentItemset, m, confidence, supportRecord):
confidenceRecord = []
rule1 = GenerateRule1(frequentItemset)
rule = PruneRule(rule1, confidence, record)
confidenceRecord.append(rule)
for i in range(m-1):
candidateNext = GenerateRule(rule)
rule = PruneRule(candidateNext, confidence, record)
if len(rule.keys()) == 0:
print('Not exist rule: {} with confidence = {}'.format(i+2, confidence))
break
confidenceRecord.append(rule)
return rule, confidenceRecord
4. 实验结果
我们加载数据集,设置支持度阈值为200,产生频繁2项集。运行如下代码:
support = 200
k = 2
dataset = LoadData()
frequentItemset, supportRecord = aprioriFrequentItemset(dataset, k, support)
可以看到支持度大于200的频繁项集结果如下:
{('beef', 'whole milk'): 209,
('bottled beer', 'whole milk'): 201,
('bottled water', 'other vegetables'): 244,
('bottled water', 'rolls/buns'): 238,
('bottled water', 'soda'): 285,
('bottled water', 'whole milk'): 338,
('bottled water', 'yogurt'): 226,
('brown bread', 'whole milk'): 248,
('butter', 'whole milk'): 271,
('citrus fruit', 'other vegetables'): 284,
('citrus fruit', 'whole milk'): 300,
('citrus fruit', 'yogurt'): 213,
('curd', 'whole milk'): 257,
('domestic eggs', 'other vegetables'): 219,
('domestic eggs', 'whole milk'): 295,
('frankfurter', 'whole milk'): 202,
('frozen vegetables', 'whole milk'): 201,
('fruit/vegetable juice', 'other vegetables'): 207,
('fruit/vegetable juice', 'whole milk'): 262,
('margarine', 'whole milk'): 238,
('newspapers', 'whole milk'): 269,
('other vegetables', 'pastry'): 222,
('other vegetables', 'pip fruit'): 257,
('other vegetables', 'pork'): 213,
('other vegetables', 'rolls/buns'): 419,
('other vegetables', 'root vegetables'): 466,
('other vegetables', 'sausage'): 265,
('other vegetables', 'shopping bags'): 228,
('other vegetables', 'soda'): 322,
('other vegetables', 'tropical fruit'): 353,
('other vegetables', 'whipped/sour cream'): 284,
('other vegetables', 'whole milk'): 736,
('other vegetables', 'yogurt'): 427,
('pastry', 'rolls/buns'): 206,
('pastry', 'soda'): 207,
('pastry', 'whole milk'): 327,
('pip fruit', 'tropical fruit'): 201,
('pip fruit', 'whole milk'): 296,
('pork', 'whole milk'): 218,
('rolls/buns', 'root vegetables'): 239,
('rolls/buns', 'sausage'): 301,
('rolls/buns', 'soda'): 377,
('rolls/buns', 'tropical fruit'): 242,
('rolls/buns', 'whole milk'): 557,
('rolls/buns', 'yogurt'): 338,
('root vegetables', 'tropical fruit'): 207,
('root vegetables', 'whole milk'): 481,
('root vegetables', 'yogurt'): 254,
('sausage', 'soda'): 239,
('sausage', 'whole milk'): 294,
('shopping bags', 'soda'): 242,
('shopping bags', 'whole milk'): 241,
('soda', 'tropical fruit'): 205,
('soda', 'whole milk'): 394,
('soda', 'yogurt'): 269,
('tropical fruit', 'whole milk'): 416,
('tropical fruit', 'yogurt'): 288,
('whipped/sour cream', 'whole milk'): 317,
('whipped/sour cream', 'yogurt'): 204,
('whole milk', 'yogurt'): 551}
紧接着,我们设置置信度阈值为0.4,从中提取1后件规则:
confidence = 0.4
m = 1
rule, confidenceRecord = aprioriRule(frequentItemset, m, confidence, supportRecord)
我们得到如下1后件规则:
{(('beef',), '->', ('whole milk',)): 0.4050387596899225,
(('butter',), '->', ('whole milk',)): 0.4972477064220184,
(('curd',), '->', ('whole milk',)): 0.4904580152671756,
(('domestic eggs',), '->', ('whole milk',)): 0.47275641025641024,
(('frozen vegetables',), '->', ('whole milk',)): 0.4249471458773784,
(('margarine',), '->', ('whole milk',)): 0.4131944444444444,
(('root vegetables',), '->', ('whole milk',)): 0.44869402985074625,
(('tropical fruit',), '->', ('whole milk',)): 0.40310077519379844,
(('whipped/sour cream',), '->', ('whole milk',)): 0.44964539007092197}
我们观察可以发现一些有趣的结论:例如,消费者买了牛肉、黄油、豆腐、鸡蛋、蔬菜,有较大概率会买全脂牛奶。
同样的,我们设置支持度阈值100,置信度阈值为0.2,从中提取2后件规则:
support = 100
confidence = 0.2
k = 3
m = 2
dataset = LoadData()
frequentItemset, supportRecord = aprioriFrequentItemset(dataset, k, support)
rule, confidenceRecord = aprioriRule(frequentItemset, m, confidence, supportRecord)
我们得到的规则如下:
{(('butter',), '->', ('other vegetables', 'whole milk')): 0.20733944954128442}
消费者买了黄油,会有一定的概率买其他蔬菜和全脂牛奶。