天天看點

商品零售購物籃分析

1 案例背景

購物籃分析是通過發現顧客在一次購買行為中放入購物籃中不同商品之間的關聯,研究顧客的購買行為,進而輔助零售企業制定營銷政策的一種資料分析方法。

通過對商場銷售資料進行分析,得到顧客的購買行為特征,并根據發現的規律而采取有效的行動,制定商品擺放、商品定價、新商品采購計劃,對增加銷量并擷取最大利潤有重要意義。

本案例使用Apriori關聯規則算法實作購物籃分析,發現超市不同商品之間的關聯關系,并根據商品之間的關聯規則制定銷售政策。

2 目标

  • 建構零售商品的Apriori關聯規則模型,分析商品之間的關聯性。
  • 根據模型結果給出銷售政策。

3 分析方法

購物籃關聯規則挖掘的主要步驟如下:

(1) 對原始資料進行資料探索性分析,分析商品的熱銷情況與商品結構。

(2) 對原始資料進行資料預處理,轉換資料形式,使之符合Apriori關聯規則算法要求。

(3) 在步驟2得到的模組化資料基礎上,采用Apriori關聯規則算法調整模型輸入參數,完成商品關聯性分析。

(4) 結合實際業務,對模型結果進行分析,根據分析結果給出銷售建議,最後輸出關聯規則結果。

4 資料探索分析

檢視資料特征以及對商品熱銷情況和商品結構進行分析

4.1 資料特征

%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False

data =pd.read_csv('./data/GoodsOrder.csv',encoding = 'gbk')
data.info()  # 檢視資料屬性
print("-"*40)

print('描述性統計結果:\n',data.describe().T)  
# 輸出結果
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 43367 entries, 0 to 43366
Data columns (total 2 columns):
id       43367 non-null int64
Goods    43367 non-null object
dtypes: int64(1), object(1)
memory usage: 677.7+ KB
----------------------------------------
描述性統計結果:
       count         mean          std  min     25%     50%     75%     max
id  43367.0  4908.589504  2843.118248  1.0  2455.5  4828.0  7380.5  9835.0
'''
           

4.2 分析熱銷商品

銷量排行前10商品的銷量及其占比

# 對商品進行分類彙總
Top10 = data.groupby(['Goods']).count().reset_index()  
Top10 = Top10.sort_values('id',ascending=False)

x = Top10[:10]['Goods'][::-1]
y = Top10[:10]['id'][::-1]
plt.figure(figsize=(18,12), dpi=80)
plt.barh(x, y, height=0.5, color='#6699CC')
plt.xlabel('銷量',size=16)
plt.ylabel('商品類别',size=16) 
plt.title('商品的銷量TOP10', size=24)
plt.xticks(size=16) # x軸字型大小調整
plt.yticks(size=16) # y軸字型大小調整
plt.show()
           
商品零售購物籃分析

銷量排行前10商品的銷量占比

# 銷量排行前10商品的銷量占比
data_nums = data.shape[0]
for index, row in Top10[:10].iterrows():
    print(row['Goods'],row['id'],row['id']/data_nums)

'''
全脂牛奶 2513 0.25551601423487547
其他蔬菜 1903 0.1934926283680732
面包卷 1809 0.18393492628368074
蘇打 1715 0.17437722419928825
酸奶 1372 0.13950177935943062
瓶裝水 1087 0.11052364006100661
根莖類蔬菜 1072 0.10899847483477376
熱帶水果 1032 0.10493136756481952
購物袋 969 0.09852567361464158
香腸 924 0.09395017793594305
'''
           

各類别商品的銷量及其占比

inputfile1 = './data/GoodsOrder.csv'
inputfile2 = './data/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']

# 合并兩個datafreame,on='Goods'
sort_links = pd.merge(sort,types)

# 根據類别求和,每個商品類别的總量,并排序
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 = './percent.csv'
sort_link.to_csv(outfile1,index = False,header = True,encoding='gbk')

'''
各類别商品的銷量及其占比:
    Types    id   percent
0  非酒精飲料  7594  0.175110
1     西點  7192  0.165840
2     果蔬  7146  0.164780
3   米糧調料  5185  0.119561
4     百貨  5141  0.118546
5     肉類  4870  0.112297
6   酒精飲料  2287  0.052736
7    食品類  1870  0.043120
8     零食  1459  0.033643
9     熟食   541  0.012475
'''
           

每類商品銷量占比

data = sort_link['percent']
labels = sort_link['Types']
plt.figure(figsize=(7, 7))
plt.pie(data,labels=labels,autopct='%1.2f%%',startangle=90)
plt.title('每類商品銷量占比')
# plt.savefig('./persent.png')  # 把圖檔以.png格式儲存
plt.show()
           
商品零售購物籃分析

分析:

通過分析各類别商品的銷量及其占比情況可知,非酒精飲料、西點、果蔬3類商品的銷量差距不大,占總銷量的50%左右。

進一步檢視銷量第一的非酒精飲料類商品的内部商品結構,并繪制餅圖顯示其銷量占比情況

# 先篩選“非酒精飲料”類型的商品,然後求百分比,然後輸出結果到檔案。
selected = sort_links.loc[sort_links['Types'] == '非酒精飲料']
# 對所有的“非酒精飲料”求和
child_nums = selected['id'].sum()
# 求百分比
selected.loc[:,'child_percent'] = selected.apply(lambda line: line['id']/child_nums,axis = 1)
selected.rename(columns = {'id':'count'},inplace = True)
print('非酒精飲料内部商品的銷量及其占比:\n',selected)
outfile2 = './child_percent.csv'
sort_link.to_csv(outfile2,index = False,header = True,encoding='gbk')  # 輸出結果

'''
非酒精飲料内部商品的銷量及其占比:
         Goods  count  Types  child_percent
0        全脂牛奶   2513  非酒精飲料       0.330919
3          蘇打   1715  非酒精飲料       0.225836
5         瓶裝水   1087  非酒精飲料       0.143139
16     水果/蔬菜汁    711  非酒精飲料       0.093627
22         咖啡    571  非酒精飲料       0.075191
38   超高溫殺菌的牛奶    329  非酒精飲料       0.043324
45       其他飲料    279  非酒精飲料       0.036740
51       一般飲料    256  非酒精飲料       0.033711
101      速溶咖啡     73  非酒精飲料       0.009613
125         茶     38  非酒精飲料       0.005004
144      可可飲料     22  非酒精飲料       0.002897
'''
           

展示非酒精飲品内部各商品的銷量占比

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.title("非酒精飲料内部各商品的銷量占比")
# 把機關長度都變的一樣
plt.axis('equal')
 # 儲存圖形
# plt.savefig('./child_persent.png')
plt.show()
           
商品零售購物籃分析

分析:

通過分析非酒精飲料内部商品的銷量及其占比情況可知,全脂牛奶的銷量在非酒精飲料的總銷量中占比超過33%,前3種非酒精飲料的銷量在非酒精飲料的總銷量中的占比接近70%,這就說明大部分顧客到店購買的飲料為這3種,而商場就需要時常注意貨物的庫存,定期補貨。

5 資料預處理

前面對資料探索分析發現資料完整,并不存在缺失值。模組化之前需要轉變資料的格式,才能使用Apriori函數進行關聯分析。這裡對資料進行轉換。

inputfile = 'data/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])

'''
資料轉換結果的前5個元素:
 [['柑橘類水果', '人造黃油', '即食湯', '半成品面包'], ['咖啡', '熱帶水果', '酸奶'], ['全脂牛奶'], ['奶油乳酪', '肉泥', '仁果類水果', '酸奶'], ['煉乳', '長面包', '其他蔬菜', '全脂牛奶']]
'''
           

6 模型建構

本案例的目标是探索商品之間的關聯關系,是以采用關聯規則算法,以挖掘它們之間的關聯關系。關聯規則算法主要用于尋找資料中項集之間的關聯關系,它揭示了資料項間的未知關系。基于樣本的統計規律,進行關聯規則分析。根據所分析的關聯關系,可通過一個屬性的資訊來推斷另一個屬性的資訊。當置信度達到某一門檻值時,就可以認為規則成立。

Apriori算法是常用的關聯規則算法之一,也是最為經典的分析頻繁項集的算法,它是第一次實作在大資料集上可行的關聯規則提取的算法。除此之外,還有FP-Tree算法,Eclat算法和灰色關聯算法等。本案例主要使用Apriori算法進行分析。

模型具體實作步驟:

  • 設定模組化參數最小支援度、最小置信度,輸入模組化樣本資料
  • 采用Apriori關聯規則算法對模組化的樣本資料進行分析,以模型參數設定的最小支援度、最小置信度以及分析目标作為條件,如果所有的規則都不滿足條件,則需要重新調整模型參數,否則輸出關聯規則結果。

目前,如何設定最小支援度與最小置信度并沒有統一的标準。大部分都是根據業務經驗設定初始值,然後經過多次調整,擷取與業務相符的關聯規則結果。本案例經過多次調整并結合實際業務分析,選取模型的輸入參數為:最小支援度0.02、最小置信度0.35。其關聯規則代碼如代碼所示。

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%。是以,顧客同時購買其他蔬菜、根莖類蔬菜和全脂牛奶的機率較高。

對于模型結果,從購物者角度進行分析:現代生活中,大多數購物者為“家庭煮婦”,購買的商品大部分是食品,随着生活品質的提高和健康意識的增加,其他蔬菜、根莖類蔬菜和全脂牛奶均為現代家庭每日飲食的所需品。是以,其他蔬菜、根莖類蔬菜和全脂牛奶同時購買的機率較高,符合人們的現代生活健康意識。

7 模型應用

模型結果表明:顧客購買其他商品的時候會同時購買全脂牛奶。是以,商場應該根據實際情況将全脂牛奶放在顧客購買商品的必經之路上,或是放在商場顯眼的位置,以友善顧客拿取。顧客同時購買其他蔬菜、根莖類蔬菜、酸奶油、豬肉、黃油、本地蛋類和多種水果的機率較高,是以商場可以考慮捆綁銷售,或者适當調整商場布置,将這些商品的距離盡量拉近,進而提升顧客的購物體驗。