关联规则进行数据挖掘
1. 关联分析(Association analysis)
理解:
- 从大规模的数据中, 发现事物(物品)间的隐含关系的过程就是关联分析(association analysis)或者关联规则学习(association rule learning).
- 是一种在大规模数据中发现有趣关系的任务.
这些有趣关系, 主要分为两种: 频繁项集或者关联规则
频繁项集(frequent item sets): 经常在一起出现的物品的集合
关联规则(association rule): 暗示两个物品的集合之间可存在某种很强的依赖的关系;后面会给出明确定义。
以下,将说明未然频繁项集的产生与关联规则的产生进行说明
因为频繁项集的产生需要对大规模数据进行遍历,所以产生了Apriori算法来挖掘频繁项集。
2. 相关概念
- 支持度(support): 某个项集的支持度被定义为: 包含该项集的事务的比例( 包含该项集的事务 / 所有事务)
- 置信度(confidence): 针对两个项集 如 {尿布} -->{啤酒} 的关联规则, 其置信度为 : support( {啤酒, 尿布} ) / support( {尿布} ) ,即可以理解为: 在尿布存在的情况下,啤酒存在的概率 或 由尿布可以推出啤酒这件事的确定性。
- 项集(sets): 事务中的每一个物品的集合, 如商店每个购物车里的每件商品自由组合; 毒蘑菇每种蘑菇的特征的组合。所有标称数值的值的集合。
- 事务: 现实世界中每一件完整的事项, 如商店每个人的购物车,美国国会的每一次投票, 毒蘑菇的每一种类。
- 候选项集(Candidate sets): 一般用C来表示,候选项集里面的部分或全部项集组成相应的频繁项集。
- 最小支持度(min-support): 根据实际业务情况来定义的一个最小支持度,大于该支持度的项集可以看做为有趣的项集。
- 频繁项集(frequent sets): 一般用L表示,候选项集中 支持度大于最小支持度的项集
- 最小置信度(min confidence):根据实际业务情况确定的一个最小置信度,大于该置信度的规则可以看做是有趣的。
- n项集: 表示该项集中一共有n项物品,Cn表示候选n项集,Ln表示频繁n项集
3. Apriori算法查找频繁项集
3.1 Apriori算法的原理:
Apriori 算法的一般过程
- 收集数据:使用任意方法
- 准备数据:任何数据类型都可以, 因为我们需要的只是集合
- 分析数据:使用任一方法
- 训练算法:使用Apriori算法来找到频繁项集
- 测试算法:不需要测试
- 使用算法:用于发现频繁项集以及物品之间的关联规则
基本原理
- 如果一个项集是频繁的,那么它的所有子集也是频繁的,
如: {1, 0} 是一个频繁项集,那么 {1}, {0}也是频繁项集- 反之,如果一个项集是非频繁的,那么它的所有超集也是非频繁的
如:{1,2} 是一个非频繁项集,那么 {1,2,3},{0,1,2,}也是非频繁项集- 在下面可以看到,有了这个先验知识,就可以对频繁项集的寻找过程进行剪枝
3.2 Apriori算法步骤
主要步骤
- 根据原始数据生成候选1项集 C1
- 根据C1生成频繁1项集 L1
- 迭代
根据Lk项集生成Ck+1项集 //连接
对Ck+1项集剪枝 //剪枝
如果Ck+1剪枝后为空,算法结束
根据Ck+1生成Lk+1项集
连接 由Ck生成Ck+1
- 笛卡尔乘积
- 例如 对于频繁项集L2: [ {1, 2}, {1, 3}, {2, 3}, {2, 4} ], 连接结果为C3: [ {1, 2, 3}, {2, 3, 4} ]
- 具体过程: 对于频繁项集Lk中的每一内部有序的项Lk[i]与其他所有内部有序的项Lk[j], 判断Lk[i][:k-1] == Lk[j][:k-1], 如果相等,则 C3.append(Lk[i] | Lk[j]), 这里的 | 是集合的并操作
剪枝 由Ck+1生成Lk+1
- 根据基本原理,对于生成的候选项集Ck,利用Apriori的原理:其子集若为非频繁项集,则该项集也为非频繁项集。
- 具体来说,就是对于Ck中的每个元素Ck[i], 若Ck[i]的所有k-1项的子集在Lk-1中不存在,则说明Ck[i]肯定不是频繁的,剪枝
- 根据最小支持度得出Lk
遍历原始事务数据集,计算 support = 含有Ck[i]的事务的数量 / 总事务数量, support >= min-support 添加到 Lk中
Python 实现
- 输入:事务的列表: [ [事务1的项], [事务2的项], … ]
- 输出:
- 频繁项集列表freqSets: [ [频繁1项集], [频繁2项集], … ];
- 支持度字典support:{ {项集}:支持度, {项集}:支持度,… }
- 数据结构:
项集:冻结集合, frozenset , 可以用来作为字典的key
频繁项集,候选项集: 列表
项集与其支持度:字典
我的代码
这里我因为之前忘记了 frozenset这个数据结构, 所以用的元组来代替, 所以很多冗余代码, 懒得改了, 就这样吧, 实现的算法看主要步骤, 数据结构看上面, 很清楚了
def read_data(path):
"""
读取初始数据集, 给数据集格式化为字典
:param path: 文件路径,
:return: {key: [value1, value2, ...], key2: [...], ...}
"""
data = dict()
with open(path, 'r') as f:
lines = f.readlines()
for line in lines:
cart = line.split(" ")
data[cart[0]] = [c.strip() for c in cart[1:]]
data[cart[0]].sort()
print(data)
return data
def get_frequent_itemsets(c, min_support, num_itmes):
l = list()
support = {}
for i in filter(lambda key: c.get(key) if c.get(key)/float(num_itmes) >= min_support else None, c):
support[i] = c.get(i)/float(num_itmes)
l.append(i)
l.sort()
return l, support
def find_frequent_1_itemsets(data, min_support):
c_1 = dict()
for v in data.values():
for j in v:
r = c_1.get((j,))
if r is not None:
c_1[(j,)] = r + 1
else:
c_1[(j, )] = 1
num_itmes = len(data)
return get_frequent_itemsets(c_1, min_support, num_itmes)
def find_frequent_n_itemsets(data, c, min_support):
c_n = dict()
for i in c: # 每个元组 项集
for key in data:
for j in i: # 元组里面的元素 项
try:
data.get(key).index(j)
except ValueError as e:
break
else:
r = c_n.get(i)
if r is not None:
c_n[i] = r + 1
else:
c_n[i] = 1
num_items = len(data)
return get_frequent_itemsets(c_n, min_support, num_items)
def decartes(c1, c2):
c = list()
for i_1 in c1:
for i_2 in c2:
if i_1[0:-1] == i_2[0:-1] and i_1[-1] < i_2[-1]:
li = list()
li.extend(i_1)
li.append(i_2[-1])
tu = tuple(li)
c.append(tu)
return c
def cut(c, pre_c):
"""
找出c_k中所有k-1项的子集, 并判断在前一个(c_k-1)项集中是否存在该子集
若不存在,则将该项去除
"""
num = len(c[0])
for i in c:
for lost in range(num):
tmp = list(i)
tmp.remove(i[lost])
try:
pre_c.index(tuple(tmp))
except ValueError: # 不存在该项集 移除
c.remove(i)
break
return c
def generate_freqSet_support_list(data, min_support):
l, support = find_frequent_1_itemsets(data, min_support)
freq_support = {}
while len(l) > 0:
print("正在计算{0}项集的support...".format(len(l[0])))
freq_support.update(support)
c = decartes(l, l)
if len(c) == 0:
break
cut_c = cut(c, l)
l, support = find_frequent_n_itemsets(data, cut_c, min_support)
return freq_support
3.3 Apriori 算法优劣
- 优势:
1. 实现简单
2. 处理大型数据的时候,可以存储到外存上- 劣势
1. 效率低,每次计算Li项集的时候,就需要遍历一遍原始数据来计算支持度
3.4 另一种频繁项集计算:FP_growth (frequent pattern)
4. 根据频繁项集挖掘关联规则
原理
在上一步, 我们已经得到了 频繁项集 与 支持度计数(support)
根据关联规则公式: 对于关联规则 p --> r 有:conf = support( p | r ) / support( p )
因此 我们只需要不断从上一步的support结果中选择 support 就能得到confidence了
算法主要步骤(python实现)
算法简单,直接看代码
from Apriori.apriori import decartes # 计算笛卡尔乘积
from Apriori import apriori
def tuple2frozenset(freq_support):
"""
将数据结构{元组:支持度, 元组:支持度}, 转换为: {frozenset: 支持度, frozenset:支持度}
:param freq_support:{元组:支持度, 元组:支持度}
:return:{frozenset: 支持度, frozenset:支持度}
"""
# freq_support的key转换成frozenset
support = dict()
for i in freq_support:
support[frozenset(i)] = freq_support.get(i)
return support
def generateRules(res_path, support, min_conf=0.7):
"""
生成关联规则
:param res_path: 关联规则的文件写入路径
:param support: apriori算法得到的支持度字典 {frozenset: 支持度, frozenset:支持度}
:param min_conf: 最小置信度
:return: 强关联规则的结果集 对于 p->q conf 有 [(p, q, conf), ()]
"""
with open(res_path, 'w+') as f:
ruleList = []
for freqSet in support: # freqSet: 频繁项集(frozenset)
h1 = [frozenset([item]) for item in freqSet] # 对频繁项集的每一项生成一个项集
if len(freqSet) == 2: # 如果是频繁二项集, h1连接之后也是2项集, 所以只需要计算一次 h1
caculConf(freqSet, support, h1, min_conf, ruleList, f)
else:
rulesFromConseq(freqSet, support, h1, min_conf, ruleList, f)
return ruleList
def caculConf(freqSet, support, h1, min_conf, ruleList, *f):
for conSeq in h1:
try:
conf = support[freqSet]/support[freqSet-conSeq] # freqSet-conSeq --> conSeq
except ValueError:
print("集合不存在该频繁项集!")
continue
if conf > min_conf:
str = "{0}-->{1}: support:{2:.2}, conf:{3:.2}".format(set(freqSet-conSeq), set(conSeq), support[freqSet], conf)
f[0].write(str+'\n') # 该强关联规则写入文件
ruleList.append((freqSet-conSeq, conSeq, conf)) # 该强关联规则加入最后的结果集
def rulesFromConseq(freqSet, support, h1, min_conf, ruleList, *f):
"""
循环
1. 计算 freqSet - h1[i] --> h1[i] 的置信度
2. h1进行笛卡尔乘积连接 成为新的h1
3. h1不为空 and len(h1[i]) < freqSet
返回 1.
否则
退出循环
:param freqSet:
:param support:
:param h1:
:param min_conf:
:param ruleList: 存储所有的 强关联规则
:param f: 写入文件路径
:return:
"""
while len(h1[0]) < len(freqSet):
caculConf(freqSet, support, h1, min_conf, ruleList, f[0])
# 以下对h1进行连接
c = list(map(tuple, h1))
h1 = [i for i in map(frozenset, decartes(c, c))]
if len(h1) == 0:
break