1. 什么是关联规则挖掘?
关联规则挖掘:用于发现数据库中属性之间的有趣联系。
如:用户在购买牛奶的时候,是否会同时购买面包? 购买面包的时候,是否会同时购买牛奶?
如图,可以看出4位顾客有3位同时购买了牛奶和面包,购买牛奶的顾客都购买了面包,购买面包的顾客都购买了牛奶,关联规则就是寻求此类关系。
2. 相关概念
事务:一次交易记录;
项:事务中的一个元素(商品);
项集:多件商品的组合;
k-项集:项集中包含个项(k件商品构成的项集);
频繁项集:对于一个项集,它出现在若干事务中;
支持度:表示同时包含项X和项Y的事务占所有事务的比例;
对{X,Y},support{X,Y} = P(X&Y) = 同时含X,Y的事务数 / 总事务数;
最小支持度:人为设定的一个阈值,用来评判一个项集是否是频繁项集。若一个项集大于最小支持度即为频繁项集;
置信度:设X,Y位两个项集,则:
confidence(XY) = P(Y|X) = support(X,Y)/support(X)= 同时含X,Y的事务数 / 含 X的事务数;
eg:beer Diaper的置信度=3/3=100%;
最小置信度:人为设定的一个阈值;
强关联规则: support{X,Y} > 最小支持度
confidence(XY) > 最小置信度
3. Apriori算法实现过程
直接图解,文字表达可以看这:https://www.cnblogs.com/llhthinker/p/6719779.html
4. 实例
蔬菜价格相关性分析
蔬菜价格会受季节、天气等多方面因素的影响,但许多会出现同涨或同跌等现象,根据给出的蔬菜价格数据,采用关联规则发现算法,发现那些蔬菜之间具有同涨、同跌或者涨跌不同步的现象。
4.1 数据预处理
数据初始情况如下图,我们选择剔除空值,将数据重新组织,
import numpy as np
import pandas as pd
def DataArrage():
data = pd.read_excel('蔬菜价格_原版.xls',sheet_name = '蔬菜价格3');
data = data.drop_duplicates(['日期','蔬菜名'],'last'); #去除可能出现的一种蔬菜一天重复统计的可能
data = data.drop_duplicates(['日期','肉食禽蛋'],'last'); # 去除可能出现的一种肉食禽蛋一天重复统计的可能
data = data.dropna(); #去除含有缺失值的行
#提取蔬菜类
k1 = data['蔬菜名'] != '蔬菜类';
vegetable = data[k1];
#提取肉蛋类
k2 = data['肉食禽蛋'] != '肉食禽蛋类';
meat = data[k2];
# 新数据
dataveg = vegetable.pivot(index='日期', columns='蔬菜名', values='价格');
datam = meat.pivot(index='日期', columns='肉食禽蛋', values='批发价格');
data = pd.merge(dataveg, datam, on="日期");
#用Nan代替空格以便下面的dropna操作
for i in data.columns:
data[i] = data[i].apply(lambda x: np.NaN if str(x).isspace() else x)
df_null = data[data[i].isnull()]
df_not_null = data[data[i].notnull()]
data = data.dropna(axis = 1) # 去除没有价格的蔬菜/肉蛋(有空值的列)
# 不选择填充是因为如果填充的数据不合适会影响涨跌关联分析
# 存入excel表
writer = pd.ExcelWriter('新蔬菜价格.xlsx') #需要手动将excel日期列的数字分类设置为日期才能正常显示日期
data.to_excel(writer)
writer.save()
if __name__ == '__main__':
DataArrage();
重新组织后的数据如下:
4.2 生成蔬菜价格涨跌情况
"""
pandas读出数据类型为DataFrame,为了方便操作数据,
使用xlwd和xlrd模块,读出的数据保存为元组,易于操作
1表示价格同比上一日增长
0 表示不变
-1表示跌
"""
import xlrd;
import xlwt;
def rise_fall():
'''
1表示价格同比上一日增长
0 表示不变
-1表示跌
'''
workbook = xlrd.open_workbook(r'新蔬菜价格.xlsx')
workspace = xlwt.Workbook(encoding='ascii')
booksheet = workbook.sheet_by_index(0)
createsheet = workspace.add_sheet('蔬菜价格', cell_overwrite_ok=True)
r_num = booksheet.nrows
c_num = booksheet.ncols
#生成横纵表头
for r in range(0,r_num):
createsheet.write(r, 0, booksheet.cell_value(r, 0))
r = r + 1
for c in range(0,c_num):
createsheet.write(0, c, booksheet.cell_value(0, c))
c = c + 1
for c in range(1,c_num): #日期最早的一天,没有对比
createsheet.write(1,c,0)
for r in range(2,r_num):
for c in range(1,c_num):
dis = booksheet.cell_value(r,c) - booksheet.cell_value(r-1,c)
if dis > 0:
createsheet.write(r,c,1)
elif dis < 0:
createsheet.write(r,c,-1)
else:
createsheet.write(r,c,0)
workspace.save('蔬菜价格趋势.xls')
if __name__ == '__main__':
rise_fall()
4.3 利用上面得到的数据进行Apriori分析
import xlrd
workbook = xlrd.open_workbook(r'蔬菜价格趋势.xls')
booksheet = workbook.sheet_by_index(0)
r_num = booksheet.nrows - 1; # 第一行为标题
c_num = booksheet.ncols - 1; # 第一列为时间
MAXI = 0 # 统计单日最多有多少个蔬菜存在涨跌
for i in range(1,r_num):
tmp = 0
for j in range(1,c_num):
if booksheet.cell_value(i,j) != 0:
tmp += 1
if tmp > MAXI:
MAXI = tmp
"""
将单日中出现的价格涨跌全部列入data
用up后缀表示涨的蔬菜名称,用down表示跌
"""
data = []
for i in range(1,r_num):
t = set()
for j in range(1,c_num):
if booksheet.cell_value(i,j) == 1:
t.add(booksheet.cell_value(0,j) + "_up")
elif booksheet.cell_value(i,j) == -1:
t.add(booksheet.cell_value(0,j) + "_down")
data.append(t)
all_thing = [] # 所有事件
frequent_thing = [] # 用于储存所有的频繁项集
for j in range(1,c_num):
l = []
t = set()
t.add(booksheet.cell_value(0,j) + "_up")
all_thing.append(t)
l.append(t)
frequent_thing.append(l)
for j in range(1,c_num):
l = []
t = set()
t.add(booksheet.cell_value(0,j) + "_down")
all_thing.append(t)
l.append(t)
frequent_thing.append(l)
k = 0;
support = 30; # 最小支持度
level = 2;
def frequent_1(): #生成频繁1项集
for i in range(0,len(frequent_thing)):
count = 0
for j in range(0,len(data)):
if frequent_thing[i][0].issubset(data[j]): #ft[i][0]时否包含在data[j]中
count += 1
frequent_thing[i] += [count]
i= 0
while i < len(frequent_thing):
if frequent_thing[i][1] < support:
del frequent_thing[i];
else:
i = i + 1;
def isfrequentThing(fre_thing): # 判断集合的子集是否都是频繁
deset = set()
relen = len(fre_thing)
for i in range(0,len(all_thing)):
tmp = 0
deset = fre_thing.difference(all_thing[i]) #可以得到所有真子集
if len(deset) == relen - 1:
for j in range(0,len(frequent_thing)):
if frequent_thing[j][0] == deset:
tmp = 1
break
if tmp == 0:
return 0
return 1;
def fuction(listq, k): # 循环主函数
initlist = [];
initset = set();
g = 0
for i in listq:
if len(i[0]) == k - 1: # 循环k-1项集
for j in listq:
if len(j[0]) == k - 1:
if i[0] != j[0]:
initset = i[0].union(j[0]);
if len(initset) == k:
if isfrequentThing(initset) == 1:
count = 0 # 统计出现次数
g = 0
for g in range(0,len(data)):
if initset.issubset(data[g]) == 1:
count = count + 1
tmp = 0
for kt in range(0,len(initlist)):
if initlist[kt][0] == initset:
tmp = 1
if tmp == 0:
if count >= support:
initlist = initlist + [[initset, count]]
listq = listq + initlist #不单独列出频繁 1 2 3...,直接组合时判断是否为k项集,跑得慢但是省空间且好写
return listq
def showResult():
for i in range(0, len(frequent_thing)):
if len(frequent_thing[i][0]) >= 2:
print(frequent_thing[i][0])
if __name__ == '__main__':
frequent_1()
frequent_thing = fuction(frequent_thing, level)
frequent_thing = fuction(frequent_thing, level + 1)
frequent_thing = fuction(frequent_thing, level + 2)
showResult()
处理结果:
即得到的满足最低要求的频繁项集,可以认为同一项集内的蔬菜价格具有列出的相性。