销售数据 聚类 关联分析

/ 技术文章 / 0 条评论 / 1590浏览

销售数据 聚类 关联分析

目的

从销售数据中,分别通过聚类和关联分析,分析出哪些商品组合是最经常购买的。此外,聚类还常常用于RFM模型分析。

  1. K-means 聚类分析:传统上根据RFM模型分析客户,这里使用聚类分析对客户合理分类
  2. Apriori 关联分析:为热销商品组合,提供关联依据
  3. K-means 聚类分析:分析商品,商品市场细分(感觉上,对商品用k-means可能不是太适合)

数据来源

数据来源是来自于UCI数据库的Online + Retail 数据库http://archive.ics.uci.edu/ml/datasets/Online+Retail.

UCI数据库是加州大学欧文分校(University of CaliforniaIrvine)提出的用于机器学习的数据库,这个数据库目前共有335个数据集,其数目还在不断增加,UCI数据集是一个常用的标准测试数据集。

数据库描述

引用的数据库包含自2010-01-12至2011-09-12所有发生在英国的非实体店线上零售数据。 来源自:Dr Daqing Chen, Director: Public Analytics group. chend '@' lsbu.ac.uk, School of Engineering, London South Bank University, London SE1 0AA, UK.

数据库字段

数据预处理

不管做什么操作之前,都要预处理一下数据,将无用数据或垃圾数据清洗掉

数据抽取

生产过程中,应该是通过数据分析平台,收集电子小票或者消费明细记录。这里,使用的是上文提到的UCI数据库的数据。

探索性分析

探索性分析,主要对数据进行缺失值和异常值分析。

发现,存在非5位数字的StockCode, 负值的数量或单价。可能的原因是这些操作为手工操作或者冲正操作,在数据清理阶段需要筛选。

import pandas as pd
import numpy as np
dataset = pd.read_csv('Online_Retail.csv', encoding = 'UTF-8')

# 定义正确的字段类型
# 当前类型
print("----\n修改前类型:")
print(dataset.dtypes)

# 类型转换
dataset[["InvoiceNo"]] = dataset[["InvoiceNo"]].astype('str')
dataset[["StockCode"]] = dataset[["InvoiceNo"]].astype('str')
dataset[["CustomerID"]] = dataset[["InvoiceNo"]].astype('str')

dataset.loc[:,'InvoiceDate']=pd.to_datetime(dataset.loc[:,'InvoiceDate'],
                                    format='%Y-%m-%d', 
                                    errors='coerce')

# 删除转换后的空值
dataset = dataset[(dataset['InvoiceDate'].notnull())]


# 修改后类型
print("----\n修改后类型")
print(dataset.dtypes)

----
修改前类型:
InvoiceNo       object
StockCode       object
Description     object
Quantity         int64
InvoiceDate     object
UnitPrice      float64
CustomerID     float64
Country         object
dtype: object
----
修改后类型
InvoiceNo              object
StockCode              object
Description            object
Quantity                int64
InvoiceDate    datetime64[ns]
UnitPrice             float64
CustomerID             object
Country                object
dtype: object

数据清洗

StockCode 含非数字:包含一系列手工操作,包括银行缴费,邮政缴费,退款,测试样例等等

丢弃下列数据:

# 丢弃 Quantity 小于0的数据
# 丢弃 UnitPrice 小于等于0
dataset_clean = dataset[(dataset['Quantity'] >= 0) & (dataset['UnitPrice'] > 0)]
dataset_clean.to_csv('clean_online_retail.csv')
# 该数据可以同样给其他的应用使用

现在,我们已经可以对数据做一些简单的处理,比如:

# 计算 单价 x 数量
dataset_clean.eval('Sale_Amount = UnitPrice * Quantity', inplace = True)
dataset_clean.head()
InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice CustomerID Country Sale_Amount
0 536365 536365 WHITE HANGING HEART T-LIGHT HOLDER 6 2010-12-01 08:26:00 2.55 536365 United Kingdom 15.30
1 536365 536365 WHITE METAL LANTERN 6 2010-12-01 08:26:00 3.39 536365 United Kingdom 20.34
2 536365 536365 CREAM CUPID HEARTS COAT HANGER 8 2010-12-01 08:26:00 2.75 536365 United Kingdom 22.00
3 536365 536365 KNITTED UNION FLAG HOT WATER BOTTLE 6 2010-12-01 08:26:00 3.39 536365 United Kingdom 20.34
4 536365 536365 RED WOOLLY HOTTIE WHITE HEART. 6 2010-12-01 08:26:00 3.39 536365 United Kingdom 20.34
# 花费最多客户Top5
dataset_clean.groupby('CustomerID')[['Sale_Amount']].sum().sort_values('Sale_Amount', ascending = False).head(5)

Sale_Amount
CustomerID
581483 168469.60
541431 77183.60
574941 52940.94
576365 50653.91
556444 38970.00
# 销量最高商品Top5
dataset_clean.groupby('StockCode')[['Quantity']].sum().sort_values('Quantity', ascending = False).head(5)

Quantity
StockCode
581483 80995
541431 74215
556917 15049
563076 14730
574941 14149
# 销售额最高商品Top5
dataset_clean.groupby('StockCode')[['Sale_Amount']].sum().sort_values('Sale_Amount', ascending = False).head(5)

Sale_Amount
StockCode
581483 168469.60
541431 77183.60
574941 52940.94
576365 50653.91
556444 38970.00

K-means RFM客户分类

RFM模型是识别客户价值,应用最广泛的客户细分模型是通过三个指标:

有一个特别简单的累积金额划分方法:将1/2的客单价作为累积消费金额的分段,比如客单价是300元,则按照150元进行累计消费金额分段,得出十个分段。

但是实际操作过程中,即使配个指标都只有5个分段,排列组合后就会产生125种组合,这对营销或者运营人员是很难操作的。此时,使用数据分析工具,通过历史销售记录找出较为合理的客户分类是较为可行的方案。参考销售数据聚类、关联分析

数据格式转换

# 探索性分析
explore = dataset_clean.describe(include = 'all').T
print(explore)
              count unique                                 top    freq  \
InvoiceNo    530104  19960                              573585    1114   
StockCode    530104  19960                              573585    1114   
Description  530104   4023  WHITE HANGING HEART T-LIGHT HOLDER    2323   
Quantity     530104    NaN                                 NaN     NaN   
InvoiceDate  530104  18499                 2011-10-31 14:41:00    1114   
UnitPrice    530104    NaN                                 NaN     NaN   
CustomerID   530104  19960                              573585    1114   
Country      530104     38                      United Kingdom  485123   
Sale_Amount  530104    NaN                                 NaN     NaN   

                           first                 last     mean      std  \
InvoiceNo                    NaN                  NaN      NaN      NaN   
StockCode                    NaN                  NaN      NaN      NaN   
Description                  NaN                  NaN      NaN      NaN   
Quantity                     NaN                  NaN   10.542  155.524   
InvoiceDate  2010-12-01 08:26:00  2011-12-09 12:50:00      NaN      NaN   
UnitPrice                    NaN                  NaN  3.90763  35.9157   
CustomerID                   NaN                  NaN      NaN      NaN   
Country                      NaN                  NaN      NaN      NaN   
Sale_Amount                  NaN                  NaN  20.1219  270.357   

               min   25%   50%   75%      max  
InvoiceNo      NaN   NaN   NaN   NaN      NaN  
StockCode      NaN   NaN   NaN   NaN      NaN  
Description    NaN   NaN   NaN   NaN      NaN  
Quantity         1     1     3    10    80995  
InvoiceDate    NaN   NaN   NaN   NaN      NaN  
UnitPrice    0.001  1.25  2.08  4.13  13541.3  
CustomerID     NaN   NaN   NaN   NaN      NaN  
Country        NaN   NaN   NaN   NaN      NaN  
Sale_Amount  0.001  3.75   9.9  17.7   168470  

大致统计了一下会员销售数据,可以看出大概有25900个会员,25900样商品,客单价为20.12英镑, 可以看出大概有25900个会员, 25900笔订单。没有重复购买的用户,所以RFM在这个数据集上似乎完成不了,但不管怎样,先试试把。

# data frame for rfm

# 针对 K-means 客户分类,只需要带CustomerID的数据,
# 删除不相关、弱相关或冗余属性,如货品描述
df_rfm = dataset_clean[(dataset_clean['CustomerID'].notnull())]
df_rfm = df_rfm[['InvoiceNo','InvoiceDate','CustomerID','Sale_Amount']]
df_rfm.head()
InvoiceNo InvoiceDate CustomerID Sale_Amount
0 536365 2010-12-01 08:26:00 536365 15.30
1 536365 2010-12-01 08:26:00 536365 20.34
2 536365 2010-12-01 08:26:00 536365 22.00
3 536365 2010-12-01 08:26:00 536365 20.34
4 536365 2010-12-01 08:26:00 536365 20.34

接下来,要将数据转换成适合RFM分类的格式:属性构造,数据标准化

为了分析客户RFM,需要将数据转换成合适的格式:

下面画图用到了matplotlib

python3 -m pip install --upgrade matplotlib
# 计算 最近消费时间(R) 消费笔数(F) 总消费金额 (M)
df_R_source = df_rfm.groupby('CustomerID')[['InvoiceDate']].max()
df_F = df_rfm.groupby('CustomerID')[['InvoiceNo']].nunique()
df_M = df_rfm.groupby('CustomerID')[['Sale_Amount']].sum()

# 合并数据集
df_rfm_merged = df_R_source.merge(df_F,on='CustomerID').merge(df_M,on='CustomerID')
df_rfm_merged.rename(columns={'InvoiceDate':'LastInvoiceDate', 'InvoiceNo':'InvoiceCount', 'Sale_Amount':'TotalAmount'}, inplace = True)

# 计算 R = Today - LastInvoiceDate
df_rfm_merged['R'] = (pd.to_datetime('today') - df_rfm_merged['LastInvoiceDate']).dt.days
df_rfm_merged.rename(columns={'InvoiceCount':'F', 'TotalAmount':'M'}, inplace = True)

df_rfm_merged.head()
LastInvoiceDate F M R
CustomerID
536365 2010-12-01 08:26:00 1 139.12 2905
536366 2010-12-01 08:28:00 1 22.20 2905
536367 2010-12-01 08:34:00 1 278.73 2905
536368 2010-12-01 08:34:00 1 70.05 2905
536369 2010-12-01 08:35:00 1 17.85 2905
# 散点图
import matplotlib.pyplot as plt
df_rfm_merged.plot.scatter(x='R',y='M',c='F',cmap='viridis_r', s=50);

从散点图可以看到,大部分数据都聚在一起,特别是因为数据源的原因,F全都是1;M只有极个别的消费额特别高,因此需要人工干预数据,让数据分布更均匀,再将数据标准化。

我们将使用log函数对数据进行处理,这样保证整体的平均值,方差等指标不变,但数据将更离散,方便下面的处理。取对数有一下好处,百度的:

  1. 基于对数函数在其定义域内是单调增函数,取对数后不会改变数据的相对关系
  2. 缩小数据的绝对数值,方便计算,如果每个数据项的值都很大,许多这样的值进行计算可能对超过常用数据类型的取值范围,这时取对数,就把数值缩小了
  3. 取对数后,可以将乘法计算转换称加法计算
  4. 某些情况下,在数据的整个值域中的在不同区间的差异带来的影响不同
  5. 所得到的数据易消除异方差问题
  6. 在经济学中,常取自然对数再做回归

实践中,取对数的一般是水平量,而不是比例数据,例如变化率等。

df_rfm_merged['R_log'] = np.log10(df_rfm_merged['R'])
df_rfm_merged['F_log'] = np.log10(df_rfm_merged['F'])
df_rfm_merged['M_log'] = np.log10(df_rfm_merged['M'])
         
df_rfm_merged.plot.scatter(x='R_log',y='M_log',c='F_log',cmap='viridis_r', s=50);

pic-1

可以看到现在数据分布的已经比较均匀了,这为我们进行聚类打下一个比较好的基础。但同时我们也会发现RFM这三个属性的单位却并不相同,分别是天数,交易笔数和交易金额。这就造成了其数值差别巨大。而聚类算法一般都是使用不同向量间的距离进行计算划分的,属性单位不同造成的数值差异过大会造成计算距离时的权重分布不均衡,也并不能反映实际情况,所以我们还要对数据进行标准化处理,这里我们使用z-score对RFM属性进行加工运算。参考

from scipy import stats

# z-score 缩小单位带来的差异
df_rfm_merged['R_zscore'] = stats.zscore(df_rfm_merged['R_log'])
# df_rfm_merged['F_zscore'] = stats.zscore(df_rfm_merged['F_log'])
df_rfm_merged['F_zscore'] = 0
df_rfm_merged['M_zscore'] = stats.zscore(df_rfm_merged['M_log'])

df_rfm_merged.plot.scatter(x='R_zscore',y='M_zscore',c='F_zscore',cmap='viridis_r', s=50);

pci-2

聚类分析

数据格式总算处理完了,现在数据分布比较均匀,不同维度间的差异也变小了。

现在使用k-means对客户进行分类,预定分成5类; 接下来的分析需要用到另一个数据分析库 sklearn

安装

python3 -m pip install --upgrade scikit-learn

from sklearn.cluster import KMeans

model = KMeans(n_clusters=5, random_state=0)

# 拟合
model.fit(df_rfm_merged.iloc[:,7:])
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
    n_clusters=5, n_init=10, n_jobs=None, precompute_distances='auto',
    random_state=0, tol=0.0001, verbose=0)
df_rfm_merged.plot.scatter(x='R_zscore',y='M_zscore',c=model.labels_,cmap='viridis_r',norm=None, s=50);

pic-3

上图中以不同的颜色区分不同类型的用户 绿色: 消费很低,可以认为是流失客户 深绿色:消费一般,最近消费日期很远,需要挽留 紫色:消费中等,最近消费日期中等,一般客户 蓝色:消费中等,最近消费日期很近,需要留住的新客户 黄色:消费很高,是高价值客户

这样我们就可以对不同的群体使用适合的营销策略了,同时当有新的用户加入后,我们也可以使用得到的k-means模型对其进行预测划分。

# 换个展现方式

# 打印结果
# 各个类别条目数
cluster_counts = pd.Series(model.labels_).value_counts()


# 聚类中心
cluster_centers = pd.DataFrame(model.cluster_centers_)
cluster_centers.columns = {'中心R','中心F','中心M'}

results = pd.concat([cluster_centers, cluster_counts], axis = 1)
results.columns = ['中心R','中心F','中心M', '类别个数']
results
中心R 中心F 中心M 类别个数
0 -0.534074 0.0 1.252324 2793
1 0.165175 0.0 -2.338939 1594
2 -1.037117 0.0 -0.078073 5986
3 1.319934 0.0 0.267700 4649
4 0.265483 0.0 -0.112706 4938

Apriori商品分析

接下来根据客户的购买记录进行聚类,首先生成 CustomerID - StockCode - Sale_Amount 二维表


K-means商品市场细分