一、实验目的
- 掌握对数据进行预处理和探索性分析的方法;
- 掌握如何利用Apriori关联规则算法进行购物篮分析。
二实验内容
- 构建零售商品的Apriori关联规则模型,分析商品之间的关联性;
- 根据模型结果给出销售策略。
三、实验操作步骤和结果分析
首先导入需要用到的库
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
加载数据集
# 加载数据
order_data = pd.read_csv("GoodsOrder.csv")
types_data = pd.read_csv("GoodsTypes.csv")
查看原始数据集的缺失值情况
# 原始数据集缺失值情况。
print("GoodsOrder数据的缺失值情况:\n",order_data.isnull().sum())
print("GoodsTypes数据的缺失情况:\n",types_data.isnull().sum())
运行截图:
查看数据形状
# 数据形状
print("GoodsOrder表形状:",order_data.shape)
print("GoodsTypes表形状:",types_data.shape)
运行截图:
绘制销量前十的商品柱状图
# 统计销量前10的商品
# 按照Goods列进行分组然后计数,并重新设置索引。
group = data1.groupby(['Goods']).count().reset_index()
print("group:\n",group)
# 按照id进行降序排列
sort = group.sort_values(by='id',ascending = False).reset_index(drop=True)
print("sort:\n",sort)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置图形里面中文为黑体
# 绘制销量前10的商品数理统计图
plt.figure(figsize=(8, 5))
sns.barplot(x=sort.iloc[:10,0],y=sort.iloc[:10,1])
plt.ylabel('商品销量',fontsize=15)
plt.xlabel('商品类别',fontsize=15)
plt.title('销量排名前10的商品销量情况',fontsize=20)
绘制销量前十商品的占比折线图
# 销量前十的商品销量占比统计图
plt.figure(figsize=(8, 5))
plt.plot(sort.iloc[:10, 0], part)
plt.title("销量前十的商品占比折线图", fontsize=20)
plt.xlabel("商品类别", fontsize=14)
plt.ylabel("比率", fontsize=14)
绘制销量最后10位的商品数量统计图
plt.figure(figsize=(8,5))
# plt.plot(sort.iloc[-10:, 0],sort.iloc[-10:, 1]) # 绘制折线图
sns.barplot(x=sort.iloc[-10:, 1],y=sort.iloc[-10:, 0])
plt.xlabel("商品销量")
plt.ylabel("商品类别")
plt.title("销量排名最后10位的商品销量情况",fontsize=20)
统计每个商品类别的销量及占比
#
type_count = outer.groupby("Types").count().reset_index()
type_count["rate"] = type_count["Goods"]/len(data1) # 计算商品类别销量占比
type_count.sort_values("id",ascending=False,inplace=True) # 对销量进行降序排序
print(type_count)
# 绘制每个商品类别的销量占比饼状图
plt.figure(figsize=(6,5))
plt.pie(x=type_count["rate"],labels=type_count["Types"],autopct='%1.2f%%',labeldistance=1.05)
plt.axis('equal') # 显示为圆(避免比例压缩为椭圆)
plt.title('不同商品类型销量占比',fontsize=15)
绘制每个商品类别的销量占比饼状图
#
plt.figure(figsize=(6,5))
plt.pie(x=type_count["rate"],labels=type_count["Types"],autopct='%1.2f%%',labeldistance=1.05)
plt.axis('equal') # 显示为圆(避免比例压缩为椭圆)
plt.title('不同商品类型销量占比',fontsize=15)
# 绘制非酒精饮料类别中不同商品占比的条形图
plt.figure(figsize=(10, 5))
sns.barplot(x=list(drink["id"]), y=list(drink["Goods"]))
plt.title("非酒精饮料类别中不同商品的销量", fontsize= 15)
plt.xlabel("商品销量")
plt.ylabel("商品类别")
# 西点
pastry = outer[outer["Types"] == "西点"].groupby("Goods").count().reset_index()
# 绘制西点类别中不同商品占比的条形图
plt.figure(figsize=(10, 5))
sns.barplot(x=list(pastry["id"]), y=list(pastry["Goods"]))
plt.title("西点类别中不同商品的销量", fontsize= 15)
plt.xlabel("商品销量")
plt.ylabel("商品类别")
# 果蔬
pastry = outer[outer["Types"] == "果蔬"].groupby("Goods").count().reset_index()
# 绘制果蔬类别中不同商品占比的条形图
plt.figure(figsize=(10, 5))
sns.barplot(x=list(pastry["id"]), y=list(pastry["Goods"]))
plt.title("果蔬类别中不同商品的销量", fontsize= 15)
plt.xlabel("商品销量")
plt.ylabel("商品类别")
plt.show()
通过不断的修改最小支持度和置信度,最终综合考虑不同的结果,我将最小支持度设为0.02,最小置信度设置为0.3,得到122个支持度大于0.02的频繁项集。37个关联规则,如下图所示。
保存到csv文件中的强关联规则:
(三) 手动实现Apriori算法
- 伪代码
# 找出频繁 1 项集
L1 =find_frequent_1 - itemsets(D);
For(k=2; !=Null; k++){
# 产生候选,并剪枝
= Apriori_gen(Lk-1);
# 扫描 D 进行候选计数
For each 事务t in D{
=subset( , t); # 得到 t 的子集
For each 候选 c 属于
c.count++;
}
# 返回候选项集中不小于最小支持度的项集
={
c 属于 | c.count>=min_sup
}
}
Return L= 所有的频繁集;
连接(join)
Procedure Apriori_gen ( : frequent(k-1)-itemsets)
For each 项集 属于
For each 项集 属于
If( ( [1]= [1])&&( [2]= [2])&& ……&&( [k-2]= [k-2])&&( [k-1]< [k-1]) )
then{
c = 连接 # 连接步:产生候选
# 若k-1项集中已经存在子集c则进行剪枝
if has_infrequent_subset(c, ) then
delete c; # 剪枝步:删除非频繁候选
else add c to ;
}
Return ;
剪枝
Procedure has_infrequent_sub (c: candidate k-itemset;
: frequent(k-1)-itemsets)
For each (k-1)-subset s of c
If s 不属于 then
Return true;
Return false;
- 手敲Apriori算法代码
def DealData(): # 数据预处理
Order_data = pd.read_csv("GoodsOrder.csv")
# 遍历每一个商品名,在其后面加上一个空格,便于合并每一张订单中的所有商品
Order_data["Goods"] = Order_data["Goods"].map(lambda x : x+" ")
Order_data = Order_data.groupby("id").sum().reset_index()
# print(Order_data)
# 将每一张订单中的每一个商品提取出来
Order_data["Goods"] = Order_data["Goods"].map(lambda x:x.strip().split(" "))
order = list(Order_data["Goods"])
# print(order)
return order
def findOneSet(data):
"""
计算1项候选集C1
:param data: 数据集
:return: 1项候选集
"""
C1 = []
for i in data: # i为每一行的数据
for j in i: # j为每一列的数据,即一张订单中的单个商品元素
if [j] not in C1:
C1.append([j])
C1 = list(map(frozenset, C1)) # 将集合C1冻结,不能添加或者删除元素。
return C1
def CalculateSupport(data, C, min_support):
"""
计算1项候选集的支持度,剔除不满足最小支持度的项集
:param data:
:param C:
:param min_support: 最小支持度
:return: 频繁1项集及其支持度
"""
dict_c = dict() # 存储中间值,用于计数
# 统计项集中的每一项元素
for i in data:
for j in C:
if j.issubset(i): # 判断j是否为i的子集
if j not in dict_c: # 判断是否统计过该元素
dict_c[j] = 1 # 没有统计过就初始化为1
else:
dict_c[j] += 1
sumc = float(len(data)) # 事务的总数
# 计算支持度
sd = dict() # 用于存放频繁集的支持度
listc = [] # 存放频繁项集
for i in dict_c:
t = dict_c[i] /sumc # 计算支持度,临时存放
# 存下满足最小支持度的频繁项集
if t > min_support:
listc.append(i) # 添加评频繁1项集
sd[i] = t # 存入支持度
return listc, sd # 返回频繁一项集和支持度
def AprioriGenerate(lk, k):
"""
利用剪枝算法,找出k项候选集
:param lk: 频繁k-1项集
:param k: 第k项
:return: 第k项候选集
"""
listk = [] # 存放第k项候选集
length_lk = len(lk) # 第k-1项频繁集的长度
# 两两组合
for i in range(length_lk):
for j in range(i+1, length_lk):
l1 = list(lk[i])[:k-2]
l2 = list(lk[j])[:k-2]
l1.sort()
l2.sort()
#前k-1项相等,就可以相乘,从而防止重复项出现
if l1 == l2:
s = lk[i] | lk[j] # s是被冻结的集合,不能增加删除元素
# 进行剪枝
s1 = list(s)
son = [] # 她的所有k-1项子集
# 构造son,遍历每一个商品元素,转换成集合set,依次从s1中删除该元素,并加入到son中
for x in range(len(s1)):
t = [s1[x]]
t1 = frozenset(set(s1)- set(t))
son.append(t1)
# 当son都是频繁项集时,保留s1
c = 0
for r in son:
if r in lk: # 判断是否属于候选集
c += 1
# 所有子集都是候选集则其自身为候选集
if len(son) == c:
listk.append(son[0] | son[1])
return listk
def scanData(data, Ck, min_support):
"""
计算候选k项集的支持度,找出满足最小支持度的频繁k项集及其支持度
:param data: 数据集
:param Ck: 候选k项集
:param min_support 最小支持度:
:return: 返回频繁k项集
"""
dicts = dict() # 存放支持度
for i in data:
for j in Ck:
if j.issubset(i):
if j not in dicts:
dicts[j] = 1
else:
dicts[j] += 1
# 计算支持度
sumi = len(data) # 事务总数
listck = [] # 存放频繁k项集
sd = dict() # 存放频繁集的支持度
for q in dicts:
s = dicts[q] / sumi
if s > min_support: # 判段支持度是否满足最小支持度
listck.insert(0, q)
sd[q] = s
# 返回频繁k项集和其支持度
return listck, sd
def Apriori(data, min_support=0.1):
"""
Apriori关联规整算法
:param data: 数据集
:param min_support: 最小支持度
:return: 频繁项集和支持度
"""
global frequentItem
# 先找到1项候选集
C1 = findOneSet(data)
# 将数据转换成列表,方便计算支持度
data = list(map(set,data))
# 计算1项候选集的支持度,找到满足最小支持度的项集
l1, support_data = CalculateSupport(data, C1, min_support)
l = [l1] #添加到列表之中,使得1项频繁集成一个单独的元素
k = 2 # k项
while len(l[k-2]) > 0: # 循环直到没有候选集结束
# 产生k项候选集Ck
Ck = AprioriGenerate(l[k-2], k) # 产生k项候选集Ck
# 计算候选k项集的支持度,找出满足最小支持度的候选集
lk, supportk = scanData(data, Ck, min_support)
support_data.update(supportk) # 添加支持度
l.append(lk) #将k项的频繁集添加到l中
k += 1
del l[-1] # 注意l的最后一个值为空值,需要删除
for i in l:
# print(i[:])
for j in i:
# print(list(j)[:])
frequentItem.append(list(j)[:])
frequentItem = pd.DataFrame({"频繁项集": frequentItem})
return l, support_data # 返回频繁项集和支持度,l是一个频繁项集列表
def obtainSubset(list1, list2):
"""
获取集合的所有子集
"""
for i in range(len(list1)):
t = [list1[i]]
t1 = frozenset(set(list1) - set(t)) # k-1项集
if t1 not in list2:
list2.append(t1)
t1 = list(t1)
if len(t1) > 1:
obtainSubset(t1, list2) # 递归所有非1项子集
def CalculateConfidence(frequent, h, support_data, relation, min_confidence):
"""
计算置信度,找出满足最小置信度的数据
:param frequent: k项频繁集
:param h: k项频繁集对应的所有子集
:param support_data: 支持度
:param relation: 强关联规整
:param min_confidence: 最小置信度
"""
global allresult
# 遍历frequent中的所有子集并计算其置信度
for i in h:
con = support_data[frequent] / support_data[frequent - i]
# 提升度
lift = support_data[frequent] / (support_data[i] * support_data[frequent - i])
if con >= min_confidence and lift > 1:
print("{}-->{},支持度:{},置信度:{},lift值:{}".format(list(frequent - i)[:], list(i)[:], round(support_data[frequent], 6),
round(con, 6), round(lift, 6)))
allresult.loc[len(allresult)] = ["(" + " ".join(list(frequent - i)[:]).strip()+")"+"-->"+ "("+ " ".join(list(i)[:]).strip()+")",
round(support_data[frequent], 6),round(con, 6),round(lift, 6)]
# print("{}-->{},支持度:{},置信度:{},lift值:{}".format(frequent - i, i,
# round(support_data[frequent], 6),
# round(con, 6), round(lift, 6)))
relation.append((frequent- i, i, con))
def AttainRelation(l, support_data, min_confidence = 0.65):
"""
生成强关联规则
:param l: 频繁集
:param support_data: 支持度
:param min_confidence: 最小置信度
:return: 强关联规则
"""
rela = [] # 存放强关联规整
# 从2项频繁集开始计算置信度
for i in range(1, len(l)):
for j in l[i]:
h = list(j)
sonlist = [] # 存放h的所有子集
# 生成所有子集
obtainSubset(h, sonlist)
# print(sonlist)
# 计算置信度,找出满足最小置信度的数据
CalculateConfidence(j, sonlist, support_data, rela, min_confidence)
return rela # 强相关规整
if __name__ == "__main__":
data = DealData()
global frequentItem # 定义全局变量,用于存储频繁项集
frequentItem = []
global allresult # 定义全局变量,用于存储强关联
allresult = pd.DataFrame(columns=["rule", "support", "confidence", "lift"])
# 返回频繁集及其对应的支持度
L, supportData = Apriori(data, min_support=0.02)
# 生成强关联规则
rule = AttainRelation(L, supportData, min_confidence=0.30)
# 按照置信度降序排序
allresult.sort_values(by="confidence",ascending=False, inplace=True)
print(frequentItem)
print(allresult.reset_index(drop=True))
由于前面以经使用python封装好的第三方库调用了Apriori算法进行计算,所以这里就直接使用前面已经得出的结果,将最小支持度设定为0.02,最小置信度设定为0.3,得到122个频繁项集,37条关联规则,如下图所示。
销售策略
针对以上计算的结果,我发现(其他蔬菜,酸奶)全脂牛奶这一条的关联规则是置信度最高的。
置信度为0.51。但是它们之间并不是十分明显的强关联,而且酸奶和全脂牛奶在人们的日常生活中是可以互相替代的商品,根据市场产品销售波动理论,互相替代的产品彼此之间有着明显的竞争关系,是很难出现一荣俱荣,一损俱损的。因此在日常销售中,应该将全脂牛奶和酸奶放在一起销售,减小空间上的距离有助于顾客消费这两种商品,如果这两种商品距离过远,很多顾客出于少走两步买不必要商品的心理就会减少购物欲望。
(面包卷)—>(全脂牛奶)是支持度最高的关联规则
其支持度为0.05634,这个值是比较低的,因此也要将面包卷放在全脂牛奶的附近,有利于销量的增加。
根据前面的数据分析阶段,我大致推测该店铺靠近居民区,主要为社区居民提供日常餐饮类商品,通过前面的计算,并没有发现很明显的强关联规则,因此很多彼此有联系的商品对于消费者来说都是可买可不买的,如果能够在消费者购物的时候增加这些可买可不买的商品曝光度,那么就会大大增加顾客购买消费的概率,从而提高店铺的销售收入,尤其是销量前三的商品,全脂牛奶、其他蔬菜、面包卷,通过前面的计算,我发现酸奶、黄油、凝乳、根茎类蔬菜、本地蛋类、冷冻蔬菜、人造黄油、热带水果、仁果类水果、黑面包、猪肉、柑橘类水果、香肠等商品都和销量前三的商品有关联。将这些与销量前三有关系的商品放在一起,或者放在顾客的必经之路上,将会大大增加这些商品的销量。
四、实验代码
数据分析代码
'''
python3.7
-*- coding: UTF-8 -*-
@Project -> File :Code -> d1
@IDE :PyCharm
@Author :YangShouWei
@USER: 296714435
@Date :2021/4/19 14:47:16
@LastEditor:
'''
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 加载数据
order_data = pd.read_csv("GoodsOrder.csv")
types_data = pd.read_csv("GoodsTypes.csv")
# 原始数据集缺失值情况。
print("GoodsOrder数据的缺失值情况:\n",order_data.isnull().sum())
print("GoodsTypes数据的缺失情况:\n",types_data.isnull().sum())
# 数据形状
print("GoodsOrder表形状:",order_data.shape)
print("GoodsTypes表形状:",types_data.shape)
# 将两张表中的数据进行连接
outer = pd.merge(order_data, types_data, on='Goods', how='outer')
print("两个原始数据表外连接后的形状:\n",outer.shape)
print(outer)
inner = pd.merge(order_data, types_data, on='Goods')
print("两个原始数据表内连接后的形状:\n",inner.shape)
print(inner)
# 重新设置order数据索引
data1 = order_data.reset_index()
print("order数据集的重复值情况",data1.duplicated().sum())
# 对购物篮数据进行描述性分析
print("描述性分析:\n",data1.describe())
# 统计销量前10的商品
# 按照Goods列进行分组然后计数,并重新设置索引。
group = data1.groupby(['Goods']).count().reset_index()
# print("group:\n",group)
# 按照id进行降序排列
sort = group.sort_values(by='id',ascending = False).reset_index(drop=True)
# print("sort:\n",sort)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置图形里面中文为黑体
# 绘制销量前10的商品数理统计图
plt.figure(figsize=(8, 5))
sns.barplot(x=sort.iloc[:10,0],y=sort.iloc[:10,1])
plt.ylabel('商品销量',fontsize=15)
plt.xlabel('商品类别',fontsize=15)
plt.title('销量排名前10的商品销量情况',fontsize=20)
# 销量前十的商品销售情况
print("商品名称\t商品销量\t商品销量占所有商品总销量的比例")
part = []
for index, i in sort[:10].iterrows():
print("{}\t{}\t{}".format(i['Goods'],i["id"], i["id"]/len(data1)))
part.append(i["id"]/len(data1))
# 销量前十的商品销量占比统计图
plt.figure(figsize=(8, 5))
plt.plot(sort.iloc[:10, 0], part)
plt.title("销量前十的商品占比折线图", fontsize=20)
plt.xlabel("商品类别", fontsize=14)
plt.ylabel("比率", fontsize=14)
# 绘制销量最后10位的商品数量统计图
plt.figure(figsize=(8,5))
# plt.plot(sort.iloc[-10:, 0],sort.iloc[-10:, 1]) # 绘制折线图
sns.barplot(x=sort.iloc[-10:, 1],y=sort.iloc[-10:, 0])
plt.xlabel("商品销量")
plt.ylabel("商品类别")
plt.title("销量排名最后10位的商品销量情况",fontsize=20)
# 销量最后十位的商品销售情况
print("商品名称\t商品销量\t商品销量占所有商品总销量的比例")
part = []
for index, i in sort[-10:].iterrows():
print("{}\t{}\t{}".format(i['Goods'],i["id"], i["id"]/len(data1)))
part.append(i["id"]/len(data1))
# 绘制销量最后10位的商品销量占比统计图
plt.figure(figsize=(8, 5))
plt.plot(sort.iloc[-10:, 0], part)
plt.title("销量最后十位的商品占比折线图", fontsize=20)
plt.xlabel("商品类别", fontsize=14)
plt.ylabel("比率", fontsize=14)
# 统计每个商品类别的销量及占比
type_count = outer.groupby("Types").count().reset_index()
type_count["rate"] = type_count["Goods"]/len(data1) # 计算商品类别销量占比
type_count.sort_values("id",ascending=False,inplace=True) # 对销量进行降序排序
print(type_count.reset_index(drop=True))
# 绘制每个商品类别的销量占比饼状图
plt.figure(figsize=(6,5))
plt.pie(x=type_count["rate"],labels=type_count["Types"],autopct='%1.2f%%',labeldistance=1.05)
plt.axis('equal') # 显示为圆(避免比例压缩为椭圆)
plt.title('不同商品类型销量占比',fontsize=15)
# 非酒精饮料的商品销量统计
drink = outer[outer["Types"] == "非酒精饮料"].groupby("Goods").count().reset_index()
# print(drink)
# print(list(drink["id"]))
# print(list(drink["Goods"]))
# 绘制非酒精饮料类别中不同商品占比的饼状图
# plt.figure(figsize=(6,5))
# plt.pie(x=list(drink["id"]),labels=list(drink["Goods"]),autopct='%1.2f%%',labeldistance=1.05)
# plt.axis('equal') # 显示为圆(避免比例压缩为椭圆)
# plt.title('非酒精饮料类别中不同商品销量占比',fontsize=15)
# 绘制非酒精饮料类别中不同商品占比的条形图
plt.figure(figsize=(10, 5))
sns.barplot(x=list(drink["id"]), y=list(drink["Goods"]))
plt.title("非酒精饮料类别中不同商品的销量", fontsize= 15)
plt.xlabel("商品销量")
plt.ylabel("商品类别")
# 西点
pastry = outer[outer["Types"] == "西点"].groupby("Goods").count().reset_index()
# 绘制西点类别中不同商品占比的条形图
plt.figure(figsize=(10, 5))
sns.barplot(x=list(pastry["id"]), y=list(pastry["Goods"]))
plt.title("西点类别中不同商品的销量", fontsize= 15)
plt.xlabel("商品销量")
plt.ylabel("商品类别")
# 果蔬
pastry = outer[outer["Types"] == "果蔬"].groupby("Goods").count().reset_index()
# 绘制果蔬类别中不同商品占比的条形图
plt.figure(figsize=(10, 5))
sns.barplot(x=list(pastry["id"]), y=list(pastry["Goods"]))
plt.title("果蔬类别中不同商品的销量", fontsize= 15)
plt.xlabel("商品销量")
plt.ylabel("商品类别")
plt.show()
# 将同一张订单里面的商品合并
# 遍历订单数据中的每一行,在每个商品名后面添加一个空格,便于将同一张订单里面的商品合并到一起。
order_data["Goods"] = order_data["Goods"].map(lambda x : x+" ")
order_data = order_data.groupby("id").sum().reset_index() # 按照订单号进行分组,然后进行同一张订单的商品合并。
order = [] # 存放每一张订单中的商品,二维矩阵
for index, i in order_data.iterrows():
t = i["Goods"].split(" ") # 将订单中的商品分割成每一个商品
t.pop(-1) # 删除空格元素
order.append(t)
# print(order)
调用Python封装好的Apriori算法库
'''
python3.7
-*- coding: UTF-8 -*-
@Project -> File :Code -> Apriori
@IDE :PyCharm
@Author :YangShouWei
@USER: 296714435
@Date :2021/4/20 13:47:49
@LastEditor:
'''
import pandas as pd
import numpy as np
from mlxtend.frequent_patterns import apriori # 生成频繁项集
from mlxtend.frequent_patterns import association_rules # 生成强关联规则
import warnings # 消除警告
warnings.filterwarnings("ignore") # 排除程序运行时的警告
def DealData(): # 数据预处理
Order_data = pd.read_csv("GoodsOrder.csv")
# 遍历每一个商品名,在其后面加上一个空格,便于合并每一张订单中的所有商品
Order_data["Goods"] = Order_data["Goods"].map(lambda x : x+" ")
Order_data = Order_data.groupby("id").sum().reset_index()
# print(Order_data)
# 将每一张订单中的每一个商品提取出来
Order_data["Goods"] = Order_data["Goods"].map(lambda x:x.strip().split(" "))
order = list(Order_data["Goods"])
# print(order)
return order
if __name__ =="__main__":
order = DealData()
column_list = []
for var in order:
column_list = set(column_list) | set(var)
# print(column_list)
print('将订单数据转换成0-1矩阵')
# 建立一个
data = pd.DataFrame(np.zeros((len(order), 169)), columns=column_list)
# print(data.index)
for i in range(len(order)):
for j in order[i]:
# print(i, j)
data.loc[i, j] += 1
# print(data)
# Apriori算法
output = apriori(data, min_support=0.02, use_colnames=True)
output = pd.DataFrame(output) # 将结果转换成DataFrame形式
# 按照支持度进行降序排序
output.sort_values(by="support", ascending=False, inplace=True)
# print(output.head(20))
print(output.reset_index(drop=True))
# 将结果保存至CSV文件
output.to_csv("frequent.csv", index=False, encoding="gbk")
# 生成关联准则
relation = association_rules(output, metric="confidence", min_threshold=0.3)
relation = pd.DataFrame(relation)
# 按照置信度的大小进行降序排序
relation.sort_values(by="confidence", ascending=False, inplace=True)
# pd.set_option('display.max_columns', 5) # 设置最大显示列数的多少
# print(relation)
print(relation.iloc[:,:6])
# 将结果保存至CSV文件
relation.to_csv("relation.csv", index = False, encoding="gbk")
手动实现Apriori算法
'''
python3.7
-*- coding: UTF-8 -*-
@Project -> File :Code -> hand
@IDE :PyCharm
@Author :YangShouWei
@USER: 296714435
@Date :2021/4/20 19:14:02
@LastEditor:
'''
import pandas as pd
def DealData(): # 数据预处理
Order_data = pd.read_csv("GoodsOrder.csv")
# 遍历每一个商品名,在其后面加上一个空格,便于合并每一张订单中的所有商品
Order_data["Goods"] = Order_data["Goods"].map(lambda x : x+" ")
Order_data = Order_data.groupby("id").sum().reset_index()
# print(Order_data)
# 将每一张订单中的每一个商品提取出来
Order_data["Goods"] = Order_data["Goods"].map(lambda x:x.strip().split(" "))
order = list(Order_data["Goods"])
# print(order)
return order
def findOneSet(data):
"""
计算1项候选集C1
:param data: 数据集
:return: 1项候选集
"""
C1 = []
for i in data: # i为每一行的数据
for j in i: # j为每一列的数据,即一张订单中的单个商品元素
if [j] not in C1:
C1.append([j])
C1 = list(map(frozenset, C1)) # 将集合C1冻结,不能添加或者删除元素。
return C1
def CalculateSupport(data, C, min_support):
"""
计算1项候选集的支持度,剔除不满足最小支持度的项集
:param data:
:param C:
:param min_support: 最小支持度
:return: 频繁1项集及其支持度
"""
dict_c = dict() # 存储中间值,用于计数
# 统计项集中的每一项元素
for i in data:
for j in C:
if j.issubset(i): # 判断j是否为i的子集
if j not in dict_c: # 判断是否统计过该元素
dict_c[j] = 1 # 没有统计过就初始化为1
else:
dict_c[j] += 1
sumc = float(len(data)) # 事务的总数
# 计算支持度
sd = dict() # 用于存放频繁集的支持度
listc = [] # 存放频繁项集
for i in dict_c:
t = dict_c[i] /sumc # 计算支持度,临时存放
# 存下满足最小支持度的频繁项集
if t > min_support:
listc.append(i) # 添加评频繁1项集
sd[i] = t # 存入支持度
return listc, sd # 返回频繁一项集和支持度
def AprioriGenerate(lk, k):
"""
利用剪枝算法,找出k项候选集
:param lk: 频繁k-1项集
:param k: 第k项
:return: 第k项候选集
"""
listk = [] # 存放第k项候选集
length_lk = len(lk) # 第k-1项频繁集的长度
# 两两组合
for i in range(length_lk):
for j in range(i+1, length_lk):
l1 = list(lk[i])[:k-2]
l2 = list(lk[j])[:k-2]
l1.sort()
l2.sort()
#前k-1项相等,就可以相乘,从而防止重复项出现
if l1 == l2:
s = lk[i] | lk[j] # s是被冻结的集合,不能增加删除元素
# 进行剪枝
s1 = list(s)
son = [] # 她的所有k-1项子集
# 构造son,遍历每一个商品元素,转换成集合set,依次从s1中删除该元素,并加入到son中
for x in range(len(s1)):
t = [s1[x]]
t1 = frozenset(set(s1)- set(t))
son.append(t1)
# 当son都是频繁项集时,保留s1
c = 0
for r in son:
if r in lk: # 判断是否属于候选集
c += 1
# 所有子集都是候选集则其自身为候选集
if len(son) == c:
listk.append(son[0] | son[1])
return listk
def scanData(data, Ck, min_support):
"""
计算候选k项集的支持度,找出满足最小支持度的频繁k项集及其支持度
:param data: 数据集
:param Ck: 候选k项集
:param min_support 最小支持度:
:return: 返回频繁k项集
"""
dicts = dict() # 存放支持度
for i in data:
for j in Ck:
if j.issubset(i):
if j not in dicts:
dicts[j] = 1
else:
dicts[j] += 1
# 计算支持度
sumi = len(data) # 事务总数
listck = [] # 存放频繁k项集
sd = dict() # 存放频繁集的支持度
for q in dicts:
s = dicts[q] / sumi
if s > min_support: # 判段支持度是否满足最小支持度
listck.insert(0, q)
sd[q] = s
# 返回频繁k项集和其支持度
return listck, sd
def Apriori(data, min_support=0.1):
"""
Apriori关联规整算法
:param data: 数据集
:param min_support: 最小支持度
:return: 频繁项集和支持度
"""
global frequentItem
# 先找到1项候选集
C1 = findOneSet(data)
# 将数据转换成列表,方便计算支持度
data = list(map(set,data))
# 计算1项候选集的支持度,找到满足最小支持度的项集
l1, support_data = CalculateSupport(data, C1, min_support)
l = [l1] #添加到列表之中,使得1项频繁集成一个单独的元素
k = 2 # k项
while len(l[k-2]) > 0: # 循环直到没有候选集结束
# 产生k项候选集Ck
Ck = AprioriGenerate(l[k-2], k) # 产生k项候选集Ck
# 计算候选k项集的支持度,找出满足最小支持度的候选集
lk, supportk = scanData(data, Ck, min_support)
support_data.update(supportk) # 添加支持度
l.append(lk) #将k项的频繁集添加到l中
k += 1
del l[-1] # 注意l的最后一个值为空值,需要删除
for i in l:
# print(i[:])
for j in i:
# print(list(j)[:])
frequentItem.append(list(j)[:])
frequentItem = pd.DataFrame({"频繁项集": frequentItem})
return l, support_data # 返回频繁项集和支持度,l是一个频繁项集列表
def obtainSubset(list1, list2):
"""
获取集合的所有子集
"""
for i in range(len(list1)):
t = [list1[i]]
t1 = frozenset(set(list1) - set(t)) # k-1项集
if t1 not in list2:
list2.append(t1)
t1 = list(t1)
if len(t1) > 1:
obtainSubset(t1, list2) # 递归所有非1项子集
def CalculateConfidence(frequent, h, support_data, relation, min_confidence):
"""
计算置信度,找出满足最小置信度的数据
:param frequent: k项频繁集
:param h: k项频繁集对应的所有子集
:param support_data: 支持度
:param relation: 强关联规整
:param min_confidence: 最小置信度
"""
global allresult
# 遍历frequent中的所有子集并计算其置信度
for i in h:
con = support_data[frequent] / support_data[frequent - i]
# 提升度
lift = support_data[frequent] / (support_data[i] * support_data[frequent - i])
if con >= min_confidence and lift > 1:
# print("{}-->{},支持度:{},置信度:{},lift值:{}".format(list(frequent - i)[:], list(i)[:], round(support_data[frequent], 6),
# round(con, 6), round(lift, 6)))
allresult.loc[len(allresult)] = ["(" + " ".join(list(frequent - i)[:]).strip()+")"+"-->"+ "("+ " ".join(list(i)[:]).strip()+")",
round(support_data[frequent], 6),round(con, 6),round(lift, 6)]
# print("{}-->{},支持度:{},置信度:{},lift值:{}".format(frequent - i, i,
# round(support_data[frequent], 6),
# round(con, 6), round(lift, 6)))
relation.append((frequent- i, i, con))
def AttainRelation(l, support_data, min_confidence = 0.65):
"""
生成强关联规则
:param l: 频繁集
:param support_data: 支持度
:param min_confidence: 最小置信度
:return: 强关联规则
"""
rela = [] # 存放强关联规整
# 从2项频繁集开始计算置信度
for i in range(1, len(l)):
for j in l[i]:
h = list(j)
sonlist = [] # 存放h的所有子集
# 生成所有子集
obtainSubset(h, sonlist)
# print(sonlist)
# 计算置信度,找出满足最小置信度的数据
CalculateConfidence(j, sonlist, support_data, rela, min_confidence)
return rela # 强相关规整
if __name__ == "__main__":
data = DealData()
global frequentItem # 定义全局变量,用于存储频繁项集
frequentItem = []
global allresult # 定义全局变量,用于存储强关联
allresult = pd.DataFrame(columns=["rule", "support", "confidence", "lift"])
# 返回频繁集及其对应的支持度
L, supportData = Apriori(data, min_support=0.02)
# 生成强关联规则
rule = AttainRelation(L, supportData, min_confidence=0.30)
# 按照置信度降序排序
allresult.sort_values(by="confidence",ascending=False, inplace=True)
print(frequentItem)
print(allresult.reset_index(drop=True))
实验总结
这次实验是我第一次接触关联挖掘Apriori的底层算法,虽然之前也看过关联挖掘Apriori算法,但是并没有进行深入的研究。虽然上课老师讲解了Apriori算法并且书上也给出了伪代码的示例,但是真的去自己手敲一遍底层算法难度还是挺大的,研究了课本的伪代码,也在网上找了很多参考资料才磕磕绊绊勉强把Apriori算法手动实现。
虽然这个过程难度挺大的,但是我也花了很多时间去钻研,学到了很多新的知识,对于关联挖掘有了深入的了解和认识,真的受益匪浅。