数据分析第四周作业

 数据探索分析

1.数据特征

探索数据的特征,查看每列属性、最大值、最小值是了解数据的第一步。查看数据特征,如代码清单8-1所示。

复制代码
# -*- coding: utf-8 -*-

# 代码8-1 查看数据特征

import numpy as np
import pandas as pd

inputfile = 'C:/Users/admin/Desktop/实验/GoodsOrder.csv'   # 输入的数据文件
data = pd.read_csv(inputfile,encoding = 'gbk')  # 读取数据
data .info()  # 查看数据属性

data = data['id']
description = [data.count(),data.min(), data.max()]  # 依次计算总数、最小值、最大值
description = pd.DataFrame(description, index = ['Count','Min', 'Max']).T  # 将结果存入数据框
print('描述性统计结果:\n',np.round(description))  # 输出结果

复制代码

 根据代码清单8-1可得,每列属性共有43367个观测值,并不存在缺失值。查看“id”属性的最大值和最小值,可知某商品零售企业共收集了9835个购物篮数据,其中包含169个不同的商品类别,售出商品总数为43367件。

 

2.分析热销商品

商品热销情况分析是商品管理中不可或缺的一部分,热销情况分析可以助力商品优选。计算销量排行前10的商品销量及占比,并绘制条形图显示销量前10的商品销量情况,如代码清单8-2所示。根据代码清单8-2可得销量排行前10的商品销量及其占比情况。

复制代码
# 代码8-2 分析热销商品

# 销量排行前10商品的销量及其占比
import pandas as pd
inputfile = 'C:/Users/admin/Desktop/实验/GoodsOrder.csv'  # 输入的数据文件
data = pd.read_csv(inputfile,encoding = 'gbk')  # 读取数据
group = data.groupby(['Goods']).count().reset_index()  # 对商品进行分类汇总
sorted=group.sort_values('id',ascending=False)
print('销量排行前10商品的销量:\n', sorted[:10])  # 排序并查看前10位热销商品

# 画条形图展示出销量排行前10商品的销量
import matplotlib.pyplot as plt
x=sorted[:10]['Goods']
y=sorted[:10]['id']
plt.figure(figsize = (8, 4))  # 设置画布大小 
plt.barh(x,y)
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.xlabel('销量')  # 设置x轴标题
plt.ylabel('商品类别')  # 设置y轴标题
plt.title('商品的销量TOP10            3055郑佰强')  # 设置标题
plt.savefig('C:/Users/admin/Desktop/实验/top10.png')  # 把图片以.png格式保存
plt.show()  # 展示图片

# 销量排行前10商品的销量占比
data_nums = data.shape[0]
for idnex, row in sorted[:10].iterrows():
    print(row['Goods'],row['id'],row['id']/data_nums)
复制代码

 

 通过分析热销商品的结果可知,全脂牛奶的销售量最高,为2513件,占比5.795%;其次是其他蔬菜、面包卷和苏打,占比分别为4.388%、4.171%、3.955%。

 

 

3.分析商品结构

对每一类商品的热销程度进行分析,有利于商家制定商品在货架上的摆放策略和位置,若是某类商品较为热销,商场可以把此类商品摆放到商场的中心位置,以方便顾客选购;或者是放在商场深处的位置,使顾客在购买热销商品前经过非热销商品所在位置,增加在非热销商品处的停留时间,以促进非热销以促进非热销商品的销量。

原始数据中的商品本身已经经过归类处理,但是部分商品还是存在一定的重叠,故需要再次对其进行归类处理。分析归类后各类别商品的销量及其占比后,绘制饼图来显示各类商品的销量占比情况,

复制代码
# 代码8-3 各类别商品的销量及其占比

import pandas as pd
inputfile1 = 'C:/Users/admin/Desktop/实验/GoodsOrder.csv'
inputfile2 = 'C:/Users/admin/Desktop/实验/GoodsTypes.csv'
data = pd.read_csv(inputfile1,encoding = 'gbk')
types = pd.read_csv(inputfile2,encoding = 'gbk')  # 读入数据

group = data.groupby(['Goods']).count().reset_index()
sort = group.sort_values('id',ascending = False).reset_index()
data_nums = data.shape[0]  # 总量
del sort['index']

sort_links = pd.merge(sort,types)  # 合并两个datafreame 根据type
# 根据类别求和,每个商品类别的总量,并排序
sort_link = sort_links.groupby(['Types']).sum().reset_index()
sort_link = sort_link.sort_values('id',ascending = False).reset_index()
del sort_link['index']  # 删除“index”列

# 求百分比,然后更换列名,最后输出到文件
sort_link['count'] = sort_link.apply(lambda line: line['id']/data_nums,axis=1)
sort_link.rename(columns = {'count':'percent'},inplace = True)
print('各类别商品的销量及其占比:\n',sort_link)
outfile1 = 'C:/Users/admin/Desktop/实验/percent.csv'
sort_link.to_csv(outfile1,index = False,header = True,encoding='gbk')  # 保存结果

# 画饼图展示每类商品销量占比
import matplotlib.pyplot as plt
data = sort_link['percent']
labels = sort_link['Types']
plt.figure(figsize=(8, 6))  # 设置画布大小   
plt.pie(data,labels=labels,autopct='%1.2f%%')
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.title('每类商品销量占比                 3055郑佰强')  # 设置标题
plt.savefig('C:/Users/admin/Desktop/实验/persent.png')  # 把图片以.png格式保存
plt.show()
复制代码

 通过分析各类别商品的销量及其占比情况可知,非酒精饮料、西点、果蔬3类商品的销量差距不大,占总销量的50%左右,同时,根据大类划分发现,和食品类的销量总和接近90%,说明顾客倾向于购买此类商品,而其余商品仅是商场为满足顾客的其他需求而设定的,并非销售的主力军。

 

 

进一步查看销量第一的非酒精饮料类商品的内部商品结构,并绘制饼图显示其销量占比情况。

根据代码清单8-4可得非酒精饮料内部商品的销量及其占比情况。

复制代码
# 代码8-4 非酒精饮料内部商品的销量及其占比

# 先筛选“非酒精饮料”类型的商品,然后求百分比,然后输出结果到文件。
selected = sort_links.loc[sort_links['Types'] == '非酒精饮料']  # 挑选商品类别为“非酒精饮料”并排序
child_nums = selected['id'].sum()  # 对所有的“非酒精饮料”求和
selected['child_percent'] = selected.apply(lambda line: line['id']/child_nums,axis = 1)  # 求百分比
selected.rename(columns = {'id':'count'},inplace = True)
print('非酒精饮料内部商品的销量及其占比:\n',selected)
outfile2 = 'C:/Users/admin/Desktop/实验/child_percent.csv'
sort_link.to_csv(outfile2,index = False,header = True,encoding='gbk')  # 输出结果

# 画饼图展示非酒精饮品内部各商品的销量占比
import matplotlib.pyplot as plt
data = selected['child_percent']
labels = selected['Goods']
plt.figure(figsize = (8,6))  # 设置画布大小 
explode = (0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.08,0.3,0.1,0.3)  # 设置每一块分割出的间隙大小
plt.pie(data,explode = explode,labels = labels,autopct = '%1.2f%%',
        pctdistance = 1.1,labeldistance = 1.2)
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.title("非酒精饮料内部各商品的销量占比                     3055郑佰强")  # 设置标题
plt.axis('equal')
plt.savefig('C:/Users/admin/Desktop/实验/child_persent.png')  # 保存图形
plt.show()  # 展示图形
复制代码

 

 通过分析非酒精饮料内部商品的销量及其占比情况可知,全脂牛奶的销量在非酒精饮料的总销量中占比超过33%,前3种非酒精饮料的销量在非酒精饮料的总销量中的占比接近70%,这就说明大部分顾客到店购买的饮料为这3种,而商场就需要时常注意货物的库存,定期补货。

 

将研究对象换为西点

 

复制代码
# 代码8-4 西点内部商品的销量及其占比

# 先筛选“西点”类型的商品,然后求百分比,然后输出结果到文件。
selected = sort_links.loc[sort_links['Types'] == '西点']  # 挑选商品类别为“西点”并排序
child_nums = selected['id'].sum()  # 对所有的“西点”求和
selected['child_percent'] = selected.apply(lambda line: line['id']/child_nums,axis = 1)  # 求百分比
selected.rename(columns = {'id':'count'},inplace = True)
print('西点内部商品的销量及其占比:\n',selected)
outfile2 = 'C:/Users/admin/Desktop/实验/child_percent.csv'
sort_link.to_csv(outfile2,index = False,header = True,encoding='gbk')  # 输出结果

# 画饼图展示西点内部各商品的销量占比
import matplotlib.pyplot as plt
data = selected['child_percent']
labels = selected['Goods']
plt.figure(figsize = (8,6))  # 设置画布大小 
explode = (0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.08,0.3,0.1,0.3,0.03,0.04,0.05,0.06,0.07,0.08,0.08,0.3,0.1,0.3)  # 设置每一块分割出的间隙大小
plt.pie(data,explode = explode,labels = labels,autopct = '%1.2f%%',
        pctdistance = 1.1,labeldistance = 1.2)
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.title("西点内部各商品的销量占比                     3055郑佰强")  # 设置标题
plt.axis('equal')
plt.savefig('C:/Users/admin/Desktop/实验/child_persent.png')  # 保存图形
plt.show()  # 展示图形
复制代码

 

 

 

数据预处理

通过对数据探索分析发现数据完整,并不存在缺失值。建模之前需要转变数据的格式,才能使用Apriori函数进行关联分析。对数据进行转换,如代码清单8-5所示。

复制代码
# -*- coding: utf-8 -*-

# 代码8-5 数据转换

import pandas as pd
inputfile='C:/Users/admin/Desktop/实验/GoodsOrder.csv'
data = pd.read_csv(inputfile,encoding = 'gbk')

# 根据id对“Goods”列合并,并使用“,”将各商品隔开
data['Goods'] = data['Goods'].apply(lambda x:','+x)
data = data.groupby('id').sum().reset_index()

# 对合并的商品列转换数据格式
data['Goods'] = data['Goods'].apply(lambda x :[x[1:]])
data_list = list(data['Goods'])

# 分割商品名为每个元素
data_translation = []
for i in data_list:
    p = i[0].split(',')
    data_translation.append(p)
print('数据转换结果的前5个元素:\n', data_translation[0:5])
复制代码

 

构建模型

本案例的目标是探索商品之间的关联关系,因此采用关联规则算法,以挖掘它们之间的关联关系。关联规则算法主要用于寻找数据中项集之间的关联关系,它揭示了数据项间的未知关系。基于样本的统计规律,进行关联规则分析。根据所分析的关联关系,可通过一个属性的信息来推断另一个属性的信息。当置信度达到某一阈值时,就可以认为规则成立。Apriori算法是常用的关联规则算法之一,也是最为经典的分析频繁项集的算法,它是第一次实现在大数据集上可行的关联规则提取的算法。除此之外,还有FP-Tree算法,Eclat算法和灰色关联算法等。本案例主要使用Apriori算法进行分析。

1.商品购物篮关联规则模型构建

本次商品购物篮关联规则建模型主要由输入、算法处理、输出3个部分组成。输入部分包括建模样本数据的输入和建模参数的输入。算法处理部分是采用Apriori关联规则算法进行处理。输出部分为采用Apriori关联规则算法进行处理后的结果。

模型具体实现步骤:首先设置建模参数最小支持度、最小置信度,输入建模样本数据;然后采用Apriori关联规则算法对建模的样本数据进行分析,以模型参数设置的最小支持度、最小置信度以及分析目标作为条件,如果所有的规则都不满足条件,则需要重新调整模型参数,否则输出关联规则结果。目前,如何设置最小支持度与最小置信度并没有统一的标准。大部分都是根据业务经验设置初始值,然后经过多次调整,获取与业务相符的关联规则结果。本案例经过多次调整并结合实际业务分析,选取模型的输入参数为:最小支持度0.02、最小置信度0.35。其关联规则代码如代码清单8-6所示。

复制代码
# -*- coding: utf-8 -*-

# 代码8-6 构建关联规则模型

from numpy import *
 
def loadDataSet():
    return [['a', 'c', 'e'], ['b', 'd'], ['b', 'c'], ['a', 'b', 'c', 'd'], ['a', 'b'], ['b', 'c'], ['a', 'b'],
            ['a', 'b', 'c', 'e'], ['a', 'b', 'c'], ['a', 'c', 'e']]
 
def createC1(dataSet):
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    C1.sort()
    # 映射为frozenset唯一性的,可使用其构造字典
    return list(map(frozenset, C1))     
    
# 从候选K项集到频繁K项集(支持度计算)
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  # 不含设为1
                else:
                    ssCnt[can] += 1  # 有则计数加1
    numItems = float(len(D))  # 数据集大小
    retList = []  # L1初始化
    supportData = {}  # 记录候选项中各个数据的支持度
    for key in ssCnt:
        support = ssCnt[key] / numItems  # 计算支持度
        if support >= minSupport:
            retList.insert(0, key)  # 满足条件加入L1中
            supportData[key] = support  
    return retList, supportData
 
def calSupport(D, Ck, min_support):
    dict_sup = {}
    for i in D:
        for j in Ck:
            if j.issubset(i):
                if not j in dict_sup:
                    dict_sup[j] = 1
                else:
                    dict_sup[j] += 1
    sumCount = float(len(D))
    supportData = {}
    relist = []
    for i in dict_sup:
        temp_sup = dict_sup[i] / sumCount
        if temp_sup >= min_support:
            relist.append(i)
# 此处可设置返回全部的支持度数据(或者频繁项集的支持度数据)
            supportData[i] = temp_sup
    return relist, supportData
 
# 改进剪枝算法
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:  # 前k-1项相等,则可相乘,这样可防止重复项出现
                # 进行剪枝(a1为k项集中的一个元素,b为它的所有k-1项子集)
                a = Lk[i] | Lk[j]  # a为frozenset()集合
                a1 = list(a)
                b = []
                # 遍历取出每一个元素,转换为set,依次从a1中剔除该元素,并加入到b中
                for q in range(len(a1)):
                    t = [a1[q]]
                    tt = frozenset(set(a1) - set(t))
                    b.append(tt)
                t = 0
                for w in b:
                    # 当b(即所有k-1项子集)都是Lk(频繁的)的子集,则保留,否则删除。
                    if w in Lk:
                        t += 1
                if t == len(b):
                    retList.append(b[0] | b[1])
    return retList

def apriori(dataSet, minSupport=0.2):
# 前3条语句是对计算查找单个元素中的频繁项集
    C1 = createC1(dataSet)
    D = list(map(set, dataSet))  # 使用list()转换为列表
    L1, supportData = calSupport(D, C1, minSupport)
    L = [L1]  # 加列表框,使得1项集为一个单独元素
    k = 2
    while (len(L[k - 2]) > 0):  # 是否还有候选集
        Ck = aprioriGen(L[k - 2], k)
        Lk, supK = scanD(D, Ck, minSupport)  # scan DB to get Lk
        supportData.update(supK)  # 把supk的键值对添加到supportData里
        L.append(Lk)  # L最后一个值为空集
        k += 1
    del L[-1]  # 删除最后一个空集
    return L, supportData  # L为频繁项集,为一个列表,1,2,3项集分别为一个元素

# 生成集合的所有子集
def getSubset(fromList, toList):
    for i in range(len(fromList)):
        t = [fromList[i]]
        tt = frozenset(set(fromList) - set(t))
        if not tt in toList:
            toList.append(tt)
            tt = list(tt)
            if len(tt) > 1:
                getSubset(tt, toList)
 
def calcConf(freqSet, H, supportData, ruleList, minConf=0.7):
    for conseq in H:  #遍历H中的所有项集并计算它们的可信度值
        conf = supportData[freqSet] / supportData[freqSet - conseq]  # 可信度计算,结合支持度数据
        # 提升度lift计算lift = p(a & b) / p(a)*p(b)
        lift = supportData[freqSet] / (supportData[conseq] * supportData[freqSet - conseq])
 
        if conf >= minConf and lift > 1:
            print(freqSet - conseq, '-->', conseq, '支持度', round(supportData[freqSet], 6), '置信度:', round(conf, 6),
                    'lift值为:', round(lift, 6))
            ruleList.append((freqSet - conseq, conseq, conf))
 
# 生成规则
def gen_rule(L, supportData, minConf = 0.7):
    bigRuleList = []
    for i in range(1, len(L)):  # 从二项集开始计算
        for freqSet in L[i]:  # freqSet为所有的k项集
            # 求该三项集的所有非空子集,1项集,2项集,直到k-1项集,用H1表示,为list类型,里面为frozenset类型,
            H1 = list(freqSet)
            all_subset = []
            getSubset(H1, all_subset)  # 生成所有的子集
            calcConf(freqSet, all_subset, supportData, bigRuleList, minConf)
    return bigRuleList
 
if __name__ == '__main__':
    dataSet = data_translation
    L, supportData = apriori(dataSet, minSupport = 0.02)
    rule = gen_rule(L, supportData, minConf = 0.35)
复制代码

根据输出结果,对其中4条进行解释分析如下:

{‘其他蔬菜’,‘酸奶’}=>{‘全脂牛奶’}支持度约为2.23%,置信度约为51.29%。说明同时购买酸奶、其他蔬菜和全脂牛奶这3种商品的概率达51.29%,而这种情况发生的可能性约为2.23%。
{‘其他蔬菜’}=>{‘全脂牛奶’}支持度最大约为7.48%,置信度约为38.68%。说明同时购买其他蔬菜和全脂牛奶这两种商品的概率达38.68%,而这种情况发生的可能性约为7.48%。
{‘根茎类蔬菜’}=>{‘全脂牛奶’}支持度约为4.89%,置信度约为44.87%。说明同时购买根茎类蔬菜和全脂牛奶这3种商品的概率达44.87%,而这种情况发生的可能性约为4.89%。
{‘根茎类蔬菜’}=>{‘其他蔬菜’}支持度约为4.74%,置信度约为43.47%。说明同时购买根茎类蔬菜和其他蔬菜这两种商品的概率达43.47%,而这种情况发生的可能性约为4.74%。
由上分析可知,顾客购买酸奶和其他蔬菜的时候会同时购买全脂牛奶,其置信度最大达到51.29%。因此,顾客同时购买其他蔬菜、根茎类蔬菜和全脂牛奶的概率较高。

总结:顾客购买其他商品的时候会同时购买全脂牛奶。因此,商场应该根据实际情况将全脂牛奶放在顾客购买商品的必经之路上,或是放在商场显眼的位置,以方便顾客拿取。顾客同时购买其他蔬菜、根茎类蔬菜、酸奶油、猪肉、黄油、本地蛋类和多种水果的概率较高,因此商场可以考虑捆绑销售,或者适当调整商场布置,将这些商品的距离尽量拉近,从而提升顾客的购物体验。

 

 

拓展

使用FP-Tree算法、Eclat算法和灰色关联算法等之一探索商品之间的关联关系,建立商品零售购物篮关联规则模型,得出商品关联规则的结果,并结合实际,使用关联规则提升商品销量

 

复制代码
from collections import defaultdict, Counter, deque
import math
import copy
from pandas import DataFrame as df
import numpy as np
import pandas as pd
import networkx as nx
import os
import pydot
from networkx.drawing.nx_pydot import graphviz_layout
from networkx.drawing.nx_agraph import * 
import random
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False
print("numpy      :  ",np.__version__)
print("pandas     :  ",pd.__version__)
print("networkx   :  ",nx.__version__)
print("matplotlib :  ",mpl.__version__)
print("pydot      :  ",pydot.__version__)

def randomcolor():
    colorArr = ['1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']
    color = ""
    for i in range(6):
        color += colorArr[random.randint(0,14)]
    return "#"+color 
class node:
    def __init__(self, item, count, parent_id):
        self.item = item                 #结点的标签也就是某个事务某个中元素
        self.count = count
        '''父亲结点的id 
          在后面FP—tree中insert_node会给每个新的结点分配整型的id,
          记录到  FP_Tree.fp_tree中 '''            
        self.parent_id = parent_id  
        self.child_ids = []      #同理
        
 
class FP_Tree:
   
    def __init__(self, minsup=0.5):
        
        self.minsup = minsup   #最小支持度
        self.minsup_num = None
        self.num = None  # 一个传入数据集的事务数目
 
        self.item_head = defaultdict(list)  # 项头表如{['A']:[1,2,4,5]}由item和对应的结点id list组成  
        self.node_num = 0                # 树的最大节点id
        self.frequent_one_itemsets = defaultdict(lambda: 0)  # 频繁一项集
        self.frequent_k_itemsets = []       # 所有频繁k项集 
        self.frequent_k_itemsets_sup = []   # 所有频繁k项集对应的数目
        self.sort_keys = None               # 用于对每个事务进行排序的依据
        self.G = nx.DiGraph()               # 本tree的networkx图对象,用来画图
        '''
        将树形结构的图转化为字典类型fp_tree,键为节点id,值为node类
        如{[3]:node} 3号结点对应的node,node中本身又保存着父母id,子女id,这样进而表示成tree
          逻辑结构-->存储结构
        '''
        self.fp_tree = defaultdict()
        
        
        
    def ini_param(self, data):
        '''
         使用数据生成一些参数
         data = [[x,x,x],[x,x,x],....]
           
        '''
        self.num = len(data)
        self.minsup_num = math.ceil(self.num * self.minsup)
        self.get_frequent_one_itemsets(data)
        self.build_tree(data)
 
    def get_frequent_one_itemsets(self, data):
        '''
          计算一项集,这一步比较简单
        '''
        c = Counter()
        for t in data:
            c += Counter(t)
        for key, value in c.items():
            if value >= self.minsup_num:
                self.frequent_one_itemsets[key] = value
        #排序
        self.frequent_one_itemsets = dict(sorted(self.frequent_one_itemsets.items(), key=lambda x: x[1], reverse=True))
        # 后续事务排序依据sortkeys
        self.sort_keys = sorted(self.frequent_one_itemsets, key=self.frequent_one_itemsets.get, reverse=True)
        return
 
    def build_tree(self, data):
        one_itemsets = set(self.frequent_one_itemsets.keys())
        self.G.add_node(0,item = 'ROOT',count = 1)
        # 创建根节点
        self.fp_tree[0] = node(item=None, count=0, parent_id=-1)   # 根节点
        self.G.add_node(0,item = 'start',count = 1)
        '''
        扫描整个事务集data,对每个事务排序然后插入到tree中

        '''
        for transaction in data:
            transaction = list(set(transaction) & one_itemsets)  # 去除非频繁项
            if len(transaction) > 0:
                transaction = sorted(transaction, key=self.sort_keys.index)  # 根据排序依据对事务筛进行排序
                parent_id = 0
                #self.G.add_node(0,item = 'start',count = 1)
                for item in transaction:
                    parent_id = self.insert_fptree(parent_id, item) 
            '''
              将item插入到parent_id后,然后返回插入结点的id,
               用于作为下一个item插入的父节点id
               具体看后面insert_fptree注释
            '''
                    
        return
 
    def insert_fptree(self, parent_id, item):
        '''
        插入item:
        如果item没有没有出现在parent_id对应结点的儿子集的item中,
        则创建新结点node,加入到arent_id对应结点的儿子集
        初始画node成员item= item,count=1 等信息  
        然后树的总节点数+1,然后用这数字用来作为此节点的唯一标识id 
        如果存在,直接让对应儿子count+1
        最后返回新结点(或被操作结点id)
        '''
        child_ids = self.fp_tree[parent_id].child_ids
        for child_id in child_ids:
            child_node= self.fp_tree[child_id]
            if child_node.item == item:
                self.fp_tree[child_id].count += 1
                self.G.nodes[child_id]['count'] +=1 
                return child_id
            # if return降低圈复杂度的同时,再判断当前的父节点的子节点中没有项与之匹配,所以新建子节点,更新项头表和树
        self.node_num += 1
        next_node_id = copy.copy(self.node_num)
        self.fp_tree[next_node_id] = node(item=item, count=1, parent_id=parent_id)  # 更新树,添加节点
        self.G.add_node(next_node_id,item = item,count =1)
        self.G.add_edge(parent_id,next_node_id)
        self.fp_tree[parent_id].child_ids.append(next_node_id)  # 更新父节点的孩子列表
        self.item_head[item].append(next_node_id)  # 项头表的建立是和树的建立一并进行的
        return next_node_id
 
    def fit(self, data):
        #构建树
        self.ini_param(data)
        
        ''' 
        现在已经构造好的数据类型有fp树,项头表,频繁一项集。
        现在提取频繁k项集,这时候需要用到项头表里面的节点列表来向上搜索条件FP树,
        后通过条件FP树形成条件模式基,递归得出频繁k项集
        '''
        
        suffix_items_list = []
        suffix_items_id_list = []
        for key, value in self.frequent_one_itemsets.items():
            suffix_items = [key]
            suffix_items_list.append(suffix_items)
            suffix_items_id_list.append(self.item_head[key])
            self.frequent_k_itemsets.append(suffix_items)
            self.frequent_k_itemsets_sup.append(value)
        pre_tree = copy.deepcopy(self.fp_tree)
        self.dfs_search(pre_tree, suffix_items_list, suffix_items_id_list)
        return
 
    def dfs_search(self, pre_tree, suffix_items_list, suffix_items_id_list):
        '''
        suffix_items_list: 后缀元素item集[x,y....]
        suffix_items_id_list:  每个item对应了一些id(一对多)对应的id集
        所以格式为[[idx1,idx2...],[idy1,idy2,idy3].....]
        '''
        for suffix_items, suffix_items_ids in zip(suffix_items_list, suffix_items_id_list):
            '''生成条件树'''
            
            condition_fp_tree = self.get_condition_fp_tree(pre_tree, suffix_items_ids)
            # 根据条件模式基,获取频繁k项集
            new_suffix_items_list, new_suffix_items_id_list = self.extract_frequent_k_itemsets(condition_fp_tree,
                                                                                               suffix_items)
            if new_suffix_items_list:  # 如果后缀有新的项添加进来,则继续递归深度搜索
                # 以开始的单项'G'后缀项为例,经过第一次提取k项频繁集后。单一后缀变为新的后缀项列表[['C', 'G'], ['A', 'G'],
                # ['E', 'G']],其计数5 5 4也加入到k项集的计数列表里面去了,new_suffix_items_id_list记录了新的后缀项节点id。
                # 此时把原本的pre_tree参数变为条件树,原本的单一后缀项参数变为new_suffix_items_list, 原本的后缀项id列表参数变
                # 为新的id项列表参数。
                # 在这样的递归过程中完成了对k项频繁集的挖掘。
                self.dfs_search(condition_fp_tree, new_suffix_items_list, new_suffix_items_id_list)
        return
 
    def get_condition_fp_tree(self, pre_tree, suffix_items_ids):
        '''
        在pretree中找到suffix_items_ids中一个id的nodes,不断通过parentid生成伸引到根节点的路径
        对每个在suffix_items_ids的id做上述同样的事情最后得到条件树
        condit_tree :字典  key = 条件树每个结点id,value = 对应的node
        '''
        condition_tree = defaultdict()
        # 从各个后缀叶节点出发,综合各条路径形成条件FP树
        for suffix_items_id in suffix_items_ids:
            suffix_items_count = copy.copy(pre_tree[suffix_items_id].count)
            suffix_items_parent_id = pre_tree[suffix_items_id].parent_id
            #本id搜索路径添加到condition_Tree中
            self.get_path(pre_tree, condition_tree, suffix_items_parent_id, suffix_items_count)
        return condition_tree
 
    def get_path(self, pre_tree, condition_tree, suffix_items_parent_id, suffix_items_count):
        # 递归结束条件:牵引到树根
        if suffix_items_parent_id == 0:
            return
        if suffix_items_parent_id not in condition_tree.keys(): 
            parent_node = copy.deepcopy(pre_tree[suffix_items_parent_id])
            parent_node.count = suffix_items_count
            condition_tree[suffix_items_parent_id] = parent_node
        else:  # 如果叶节点有多个,则肯定是重复路径
            condition_tree[suffix_items_parent_id].count += suffix_items_count
        suffix_items_parent_id = condition_tree[suffix_items_parent_id].parent_id
        self.get_path(pre_tree, condition_tree, suffix_items_parent_id, suffix_items_count)
        return
 
    def extract_frequent_k_itemsets(self, condition_fp_tree, suffix_items):
        ''' 根据条件模式基,提取频繁项集, suffix_item为该条件模式基对应的后缀
           返回新的后缀,以及新添加项(将作为下轮的叶节点)的id,
           不断递归得到不同长度的不同组合的k项集,每次递归一次本函数都会项集长度都会增加1
         '''
        new_suffix_items_list = []          #  后缀中添加的新项
        new_item_head = defaultdict(list)  # 基于当前的条件FP树,更新项头表, 新添加的后缀项
        item_sup_dict = defaultdict(int)
        for key, val in condition_fp_tree.items():
            item_sup_dict[val.item] += val.count  # 对项出现次数进行统计
            new_item_head[val.item].append(key)
 
        for item, sup in item_sup_dict.items():
            if sup >= self.minsup_num:  # 若条件FP树中某个项是频繁的,则添加到后缀中
                current_item_set = [item] + suffix_items
                self.frequent_k_itemsets.append(current_item_set)
                self.frequent_k_itemsets_sup.append(sup)
                new_suffix_items_list.append(current_item_set)
            else:
                new_item_head.pop(item)
        return new_suffix_items_list, new_item_head.values()
    
    def group_by_len(self):
        '''函数名字就是意思……'''
        L = copy.deepcopy(self.frequent_k_itemsets)
        re = {}
        for items in L:
            set_ =  re.get(len(items),set())
            set_.add(frozenset(items))
            re[len(items)] = set_
       
        return sorted(list(re.values()),key= lambda x:len(list(x)[0])  )
        

    
    def get_rules(self, min_conf = 0.5):
      """
      生成关联规则
      用到的变量:
        L:被按长度分组了的k项集
        support_data: 字典. key=  项集  value =  support.
        min_conf: 最小信任度
      返回:
        关联规则: 3元组. 第一项==》第二项 conf= 第三项
      """
      L = copy.deepcopy(self.frequent_k_itemsets)
      L =  list(map(lambda x:frozenset(x),L))
      support_data = dict(zip(L,self.frequent_k_itemsets_sup))
      L = self.group_by_len()
      big_rule_list = []
      sub_set_list = []
      for i in range(0, len(L)):
        for freq_set in L[i]:
          for sub_set in sub_set_list:
            if sub_set.issubset(freq_set):
              conf = support_data[freq_set] / support_data[freq_set - sub_set]
              big_rule = (freq_set - sub_set, sub_set, conf)
              if conf >= min_conf and big_rule not in big_rule_list:
                # print freq_set-sub_set, " => ", sub_set, "conf: ", conf
                big_rule_list.append(big_rule)
          sub_set_list.append(freq_set)
      return big_rule_list
   
    
    def show(self,node_size =1800,node_num = 10):
        '''画树  存储结构转又变回逻辑结构的过程...'''
        try:
            asize = 0
            c_ids = self.fp_tree[0].child_ids
            if len(c_ids)==0:
                raise Exception()
                
             
            for id_ in c_ids:
                c_size = self.fp_tree[id_].count
                if asize < c_size:
                    asize = c_size    
            
            self.G.nodes[0]['count']= asize*1.3
        except:   
            asize = self.G.nodes[0]['count']
            print("----------------------------------------------warning:----------------------------------------------------\n\
要保证存在事务中的item中至少存在一个item的支持度是大于支持度。否则只有一个树中只有一个根节点")
            return 
        nodelist  = sorted(list(self.G.nodes(data=True))  ,key= lambda x:x[1]["count"],reverse= True)
        nodelist= nodelist[:min(node_num,len(self.G.nodes))]
        nodelist= [  nodedata[0]  for nodedata in nodelist ]
       
        H = nx.DiGraph(self.G.subgraph(nodelist))
#         plt.figure()
#         nx.draw(H)
#         plt.show()
#         print(type(H))
        labels = { node[0]:node[1]['item']+"\n"+str(node[1]["count"]) for node in H.nodes(data=True) if not node[0] == 0}
        labels[0]= ''
        labels =pd.Series(labels)
        #labels.index  = range(len(labels))

        size= { node[0]:int(node_size*(node[1]['count']/asize+0.6)) for node in H.nodes(data=True) }
        size = pd.Series(size )
        size.index = range(len(size))

        
        colors=  pd.Series({ node[0]:randomcolor() for node in H.nodes(data=True) })
        
        pos = graphviz_layout(H,prog='dot')
        plt.figure()
        nx.draw(H,pos,with_labels = True, labels = labels, node_color = colors,node_size= size,font_size= 8)
        plt.show()
复制代码

 

 

 

 

 
 

posted on   什么都不会的萌新  阅读(120)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示