电商用户行为分析

学习任务

  随着互联网和电商的发展,人们习惯于网上购物。在国内,电商平台深受欢迎。通过对用户的行为分析,可以探索用户购买的规律,了解商品的受欢迎程度,结合店铺的营销策略,实现更加精细和精准的运营,让业务获得更好的增长。本项目采用RFM方法,对电商用户的行为进行分析。

知识点

   本章涉及到的知识点,主要包括用Python建立RFM模型,包括整体建模的五个步骤,分别是数据概览、数据清洗、维度打分、分值计算和客户分层。

1.问题描述

   要求从用户(人)、商品(货)、APP(场)三个维度分析,解决以下问题:

  1. 流量的数量和质量如何
  2. 付费用户的数量和比例如何
  3. 用户活跃情况
  4. 哪些用户是高价值用户,哪些用户是可以引导消费
  5. 用户活动时间规律
  6. 用户商品偏好
  7. 商品成交量贡献情况
  8. 交易环节转化率、跳失率,流程和页面是否合理

2.主要模型介绍

  RFM,是一种经典的用户分类、价值分析模型,同时,这个模型以直白著称,直白到把需要的字段写在了模型的名字上了:

  • R,Rencency,即每个客户有多少天没回购了,可以理解为最近一次购买到现在隔了多少天。
  • F,Frequency,是每个客户购买了多少次。
  • M,Monetary,代表每个客户平均购买金额,这里也可以是累计购买金额。

这三个维度,是RFM模型的精髓所在,帮助我们把混杂一体的客户数据分成标准的8类,然后根据每一类用户人数占比、金额贡献等不同的特征,进行人、货、场三重匹配的精细化运营。这8种类型为:

  • R F M 客户类型
  • 大 大 大 重要价值用户
  • 小 大 大 重要价值流失预警客户
  • 大 小 大 重要发展用户
  • 小 小 大 高消费唤回客户
  • 大 大 小 消费潜力客户
  • 小 大 小 一般保持客户
  • 大 小 小 一般发展客户
  • 小 小 小 流失客户

其它指标:

  • 忠诚度:最近一次消费时间、消费频率
  • 购买能力:消费总金额、最大单笔订单消费金额
  • 价格接受度:特价商品消费数量占比、最高单件商品消费金额

3.数据描述

  数据集来自于The UCI Machine Learning Repository网站,目前可以在https://www.kaggle.com/carrie1/ecommerce-data 上下载。其中包含了英国某在线零售商2010年12月1日-2011年12月9日的线上交易记录。该公司主要销售礼品。其客户中有很多批发商。

4.实验过程

  RFM模型的应用中,整体来说分成5大步骤。如下图所示: p56169554.webp

4.1读入数据

  分析的第一步是数据概览,为了便于分析,我们应用数据可视化工具包对原始数据进行处理。 matplotlib是一个相当底层的工具。可以从其基本组件中组装一个图表:数据显示(即绘图的类型:线、条、框、散点图、轮廓等)、图例、标题、刻度标记和其他注释。在pandas中,我们可能有多个数据列,并且带有行和列的标签。pandas自身有很多内建方法可以简化从DataFrame和Series对象生成可视化的过程。另一个库是seaborn,它是由Michael Waskom创建的统计图形库。seaborn简化了很多常用可视化类型的生成。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn

plt.style.use('ggplot')

raw_data = pd.read_csv('data.csv', encoding='ISO-8859-1', \
 dtype={'CustomerID': np.object})
raw_data.count()

运行结果如下:

InvoiceNo      541909
StockCode      541909
Description    540455
Quantity       541909
InvoiceDate    541909
UnitPrice      541909
CustomerID     406829
Country        541909

原始数据中共有541909条记录,其中CustomerID中有缺失。

数据集变量列表如下

InvoiceNo: 发票号码;6位整数,每次交易都有一个不重复的发票号码
StockCode: 产品代码;5为整数,每个单品都有一个唯一的商品代码
Description: 商品名称;例如:CARD I LOVE LONDON
Quantity: 每次交易中每个商品的数量
UnitPrice: 每单位商品价格,以英镑表示: £45.23
InvoiceDate: 每次交易发生的日期和时间
CustomerID: 顾客ID,每个顾客有一个唯一的5位整数
Country: 顾客所在国家/地区名称

4.2数据清洗

  根据数据集的描述,先来看一下销售量和单价是否存在着不合理的取值。其次,为了分析用户的消费特征,在清理环节检查顾客ID的正确性。由于产品代码为5位整数,因此需要编写函数检查是否存在不符合的情况,并予以剔除。

  • 查找销售量(Quantity)及单价的异常值(值小于等于0)
  • 检查产品代码的正确性(是否为5位整数)
  • 检查顾客ID的正确性(是否为5位整数)
  • 生成订单总金额变量(Amount = Quantity * UnitPrice)
  • 使用filter_data函数,保留 1.Quantity > 0; 2.顾客ID不为空; 3.地区为英国; 4.单价>0; 5.StockCode为5位数字的订单
  • 将日期变量(InvoiceDate)转化为datetime格式,并生成年份、月份、时间等分离的变量
# 显示数量 <= 0 的订单
raw_data[raw_data['Quantity'] <= 0].head(2)
>>> raw_data[raw_data['Quantity'] <= 0].head(2)
    InvoiceNo StockCode  ... CustomerID         Country
141   C536379         D  ...      14527  United Kingdom
154   C536383    35004C  ...      15311  United Kingdom

数量小于0的订单为退货订单(InvoiceNo以C开头),在接下来的清理步骤中将这些订单剔除。当然,如果为了分析退货率、退货商品特性,也可以单独分析这些订单。

# 显示单价 < 0 的订单
raw_data[raw_data['UnitPrice'] < 0].head()
>>> raw_data[raw_data['UnitPrice'] < 0].head()
       InvoiceNo StockCode  ... CustomerID         Country
299983   A563186         B  ...        NaN  United Kingdom
299984   A563187         B  ...        NaN  United Kingdom

单价小于0的2个订单,其Description为“Adjust bad debt”(调整坏账)。会在接下来的清理步骤中被剔除。

# 显示单价 = 0 的订单
raw_data[raw_data['UnitPrice'] == 0].head(3)
>>> raw_data[raw_data['UnitPrice'] == 0].head(3)
     InvoiceNo StockCode Description  ...  UnitPrice CustomerID         Country
622     536414     22139         NaN  ...        0.0        NaN  United Kingdom
1970    536545     21134         NaN  ...        0.0        NaN  United Kingdom
1971    536546     22145         NaN  ...        0.0        NaN  United Kingdom

数据中也存在单价为0的记录,没有足够的信息表明这些订单是否因为打折优惠等原因而价格为0,在本次分析中剔除这些订单。

接下来定义一个函数用来检查StockCode字段中符合5位整数的比例。

# Define a function to check how many data points of input columns 
# are 5-digit intiger or string, report the result
# For missing data points 
def check_five_digit(data,li=[]):
    for ele in li:
        result = []
        for code in data[ele]:
            if isinstance(code,str):
                if code.isdigit() and len(code) == 5:
                    result.append(1)
            if isinstance(code,int):
                if len(str(code)) == 5:
                    result.append(1)
        print("正常5位商品代码的订单数及比例: "\
        ,ele\
        ,format(np.sum(result)/len(data[ele]),".2f")\
        ,np.sum(result),"/",len(data[ele]))

check_five_digit(raw_data,li=["StockCode"])

商品代码中90%的订单为5位数字,在之后的清理步骤中剔除商品代码不是5位数字的记录

def filter_data(data):
    # 1.keep obs whose quantity >0; 2.keep obs whose CusID are not null; 3.keep UK's obs
    # 4.keep obs whose UnitPrice >0 5.keep obs whose StockCode are five-digit strings
    print("输入数据的订单总数: ", len(data))
    newdata = data[data['Quantity'] > 0].reset_index(drop=True)
    print("保留商品数量>0的订单后:", len(newdata))
    newdata = newdata[newdata['CustomerID'].notnull()].reset_index(drop=True)
    print("保留非空顾客号CustomerID后:", len(newdata))
    newdata = newdata[newdata['Country'] == 'United Kingdom'].reset_index(drop=True)
    print("保留英国顾客后:", len(newdata))
    newdata = newdata[newdata['UnitPrice'] > 0].reset_index(drop=True)
    print("保留非负单价后:", len(newdata))
    newdata = newdata[newdata['StockCode'].map(lambda x: len(str(x))) == 5].reset_index(drop=True)
    print("保留5位商品代码后:", len(newdata))
    return newdata
# sd_rds: standard records
sd_rds = filter_data(raw_data)
输入数据的订单总数:  541909
保留商品数量>0的订单后: 531285
保留非空顾客号CustomerID后: 397924
保留英国顾客后: 354345
保留非负单价后: 354321
保留5位商品代码后: 323566

根据以上的筛选规则筛选后,得到用于分析的有效记录数323566个。得到清理后的记录层面的数据集sd_rds(standard records)

修改CustomerID的类型为字符型

# rectify the type of CustomerID as string
sd_rds['CustomerID'] = sd_rds['CustomerID'].astype(str)

创建消费金额变量Amount = Quantity * Price

# Create Amount = Quantity * Price
sd_rds['Amount'] = sd_rds['Quantity'] * sd_rds['UnitPrice']

处理消费日期(InvoiceDate)变量

# Series.dt can be used to access the values of the series as datetime like and return several properties
def date_process(df):
    df['InvoiceDateA'] = pd.to_datetime(df['InvoiceDate'])
    df['Date'] = df['InvoiceDateA'].dt.date
    df['Time'] = df['InvoiceDateA'].dt.time
    df['weekday'] = df['InvoiceDateA'].dt.weekday
    df['year'] = df['InvoiceDateA'].values.astype('datetime64[Y]')
    df['day'] = df['InvoiceDateA'].values.astype('datetime64[D]')
    df['month'] = df['InvoiceDateA'].values.astype('datetime64[M]')
    return df
sd_rds = date_process(sd_rds)

这里使用了astype将日期格式的变量InvoiceDate转化并生成为以年份、天和月份表示的变量。

sd_rds.groupby('month').Quantity.sum().plot()

Figure_1.png

根据月份来看商品的销量:第一季度的销量在全年中较低,而后平稳的增长。9月份后的销量呈现井喷式增长。到11月份接近550000。总体上看,这和西方国家消费者集中在11-12月份的节日期间消费的特点大体一致。

sd_rds.groupby('month').Amount.sum().plot()

Figure_2.png

商品销售金额的月份趋势与销量图类似,相比于年初,9-12月份的销售金额增长迅速,占据了全年销售额的大部分。

出现年底消费数量与金额大幅上涨,在假定数据没有异常的情况下,可能有两种原因:

一是用户由于节日等原因集中选择在年底消费;

二是商家选择在最后一季度的一些时间节点上线促销活动。如果数据集中有关于促销活动的变量,可以对此作进一步的分析。

4.3维度打分

  在对消费记录进行简单分析的基础上,我们希望按照客户的ID进行分析,将订单按照CustomerID进行分组,并将购买数量和金额进行累加。

quantity_amount_ids = sd_rds.groupby('CustomerID').sum()
# 删除UnitPrice(单价)字段,因为它在聚合后没有意义
quantity_amount_ids.drop(['UnitPrice'], axis=1,inplace=True)
quantity_amount_ids.head()
>>> quantity_amount_ids.head()
            Quantity    Amount  weekday
CustomerID                             
12346          74215  77183.60        1
12747           1029   3569.41      157
12748          22420  29279.34    12159
12749           1432   3962.18      171
12820            716    924.64       56

生成客户层面的数据集,每天记录对应一个CustomerID

用户首次消费的月份

sd_rds.groupby('CustomerID').month.min().value_counts(sort = True)
>>> sd_rds.groupby('CustomerID').month.min().value_counts(sort = True)
2010-12-01    807
2011-03-01    417
2011-01-01    356
2011-02-01    338
2011-10-01    323
2011-11-01    296
2011-09-01    276
2011-04-01    274
2011-05-01    254
2011-06-01    215
2011-07-01    167
2011-08-01    139
2011-12-01     34

假定以10年12月作为起始时间,大部分用户的首次消费时间集中于10年12月及11年的前三个月。而11年的9、10、11月每月都有300人左右的用户首次消费,12月仅有34个用户。11年的1-3月以及9-11月是用户首次消费的集中时间段。

sd_rds.groupby('CustomerID').month.max().value_counts(sort = True)
>>> sd_rds.groupby('CustomerID').month.max().value_counts(sort = True)
2011-11-01    1158
2011-10-01     596
2011-12-01     556
2011-09-01     359
2011-08-01     188
2011-06-01     171
2011-07-01     158
2011-03-01     153
2011-05-01     150
2011-04-01     124
2010-12-01     110
2011-02-01      93
2011-01-01      80

再来看一下用户最后一次消费的月份,排名前三的为2011年的11、10和12月。由于数据截止到11年12月,因此这一结果更多的和数据本身的截止时间有关。

在原始数据中,一个客户的一个订单中往往购买了不同类型的商品,购买的每一种商品(StockCode)都会生成一条记录。

为了接下来分析用户的复购率与回购率,将同一订单号发生的不同商品的交易合并,同一订单的数量和购买金额进行加总,使得新的数据集sd_orders的订单号唯一。

sd_orders = sd_rds.groupby(['InvoiceNo','CustomerID','InvoiceDate','month']).sum().reset_index()
sd_orders.head()
  InvoiceNo CustomerID     InvoiceDate  ... UnitPrice  Amount  weekday
0    536365      17850  12/1/2010 8:26  ...     15.29   61.14        6
1    536366      17850  12/1/2010 8:28  ...      3.70   22.20        4
2    536367      13047  12/1/2010 8:34  ...     58.24  278.73       24
3    536368      13047  12/1/2010 8:34  ...     19.10   70.05        8
4    536369      13047  12/1/2010 8:35  ...      5.95   17.85        2

复购率与回购率

复购率:在某时间窗口内消费两次及以上的用户在总消费用户中占比。

回购率:某一时间窗口内消费的用户,在下一时间窗口仍旧消费的占比。

其中,用户的每一张订单号视为一次消费

pivoted_counts_by_month = sd_orders.pivot_table(index = 'CustomerID', columns = 'month'\
, values='InvoiceDate', aggfunc = 'count').fillna(0)
columns_month = sd_orders.month.sort_values().unique()
pivoted_counts_by_month.columns = columns_month

pivoted_counts_by_month.head()
            2010-12-01  2011-01-01  ...  2011-11-01  2011-12-01
CustomerID                          ...                        
12346              0.0         1.0  ...         0.0         0.0
12747              2.0         1.0  ...         1.0         1.0
12748             34.0         3.0  ...        43.0         8.0
12749              0.0         0.0  ...         1.0         1.0
12820              0.0         1.0  ...         0.0         1.0

统计得到的每个客户在每个月的消费次数

计算复购率

pivoted_counts_by_month_transf = pivoted_counts_by_month.applymap(lambda x: 1 if x>1\
else np.NaN if x==0 else 0)
pivoted_counts_by_month_transf.head()
            2010-12-01  2011-01-01  ...  2011-11-01  2011-12-01
CustomerID                          ...                        
12346              NaN         0.0  ...         NaN         NaN
12747              1.0         0.0  ...         0.0         0.0
12748              1.0         1.0  ...         1.0         1.0
12749              NaN         NaN  ...         0.0         0.0
12820              NaN         0.0  ...         NaN         0.0

如果客户在某个月消费两次以上,值为1; 只消费一次,值为0; 未消费,值缺失

# 每个月份复购率的折线图
(pivoted_counts_by_month_transf.sum() / pivoted_counts_by_month_transf.count()).\
plot(figsize=(10,4))

Figure_3.png 复购率=每个月有复购的客户数/每个月发生消费的客户数

从各月份复购率的折线图可以看出,11年1-10月复购率在20-26%之间。10年12月和11年11月份为两个高点,分别为29.5%和32.5%。各月份的复购率总体上没有超过32%。可见在同一月份内再次购买的比例不高。

计算回购率

pivoted_amount = sd_orders.pivot_table(index = 'CustomerID', columns = 'month'\
,values='Amount',aggfunc = 'mean').fillna(0)

columns_month = sd_orders.month.sort_values().unique()
pivoted_amount.columns = columns_month
pivoted_amount.head(5)
            2010-12-01    2011-01-01  ...  2011-11-01  2011-12-01
CustomerID                            ...                        
12346         0.000000  77183.600000  ...    0.000000     0.00000
12747       299.585000    267.640000  ...  256.530000   338.80000
12748       106.047353    124.306667  ...  212.634884   125.43875
12749         0.000000      0.000000  ...  522.590000   702.06000
12820         0.000000    152.760000  ...    0.000000   210.35000

统计每个客户每个月份的消费金额

pivoted_purchase = pivoted_amount.applymap(lambda x: 1 if x>0 else 0)
pivoted_purchase.head()
            2010-12-01  2011-01-01  ...  2011-11-01  2011-12-01
CustomerID                          ...                        
12346                0           1  ...           0           0
12747                1           1  ...           1           1
12748                1           1  ...           1           1
12749                0           0  ...           1           1
12820                0           1  ...           0           1

消费金额>0,值为1,消费金额=0,值为0

def purchase_return(data, column):
    status = []
    for i in range(12):
        if data[i] == 1:
            if data[i+1] == 1:
                status.append(1)
            if data[i+1] == 0:
                status.append(0)
        else:
            status.append(np.NaN)
    status.append(np.NaN)
    return pd.Series(status, index = column)

pivoted_purchase_return = pivoted_purchase.\
apply(purchase_return, axis = 1, args=(columns_month,))
pivoted_purchase_return.head(5)
# 绘制回购率的折线图
(pivoted_purchase_return.sum() / pivoted_purchase_return.count()).plot(figsize = (6,4))

Figure_4.png

回购率 = 某个月份消费且下个月份仍然消费的客户数/某个月份消费的客户数

总体来看,全年回购率均在30%以上,并且在8月份超过了45%。8-10月份的回购率在全年中保持了较高的水平。12月份的销量占全年比重较低,这也使得11月份的回购率不到25%。

用户分层

接下来对用户进行分层,分析不同类型用户的消费特点

  • 新用户 —— 第一次消费
  • 活跃用户 —— 老用户,某个月份发生过消费
  • 不活跃用户 —— 某时间窗口内没发生消费的 老客
  • 回流用户 —— 上一个时间窗口内没消费,当前时间窗口内有过消费
# 根据不同消费人群的定义编写生成用户状态的函数
def active_status(data, column):
    status = []
    for i in range(13):
        
        # no purchase in current month
        if data[i] == 0:
            if len(status) > 0:
                if status[i-1] == 'unreg':
                    status.append('unreg')
                else:
                    status.append('unactive')
            else:
                status.append('unreg')
        # purchase in current month  
        else:
            if len(status) == 0:
                status.append('new')
            else:
                if status[i-1] == 'unactive':
                    status.append('return')
                elif status[i-1] == 'unreg':
                    status.append('new')
                else:
                    status.append('active')
    return pd.Series(status, index = column)
    
pivoted_purchase_status = pivoted_purchase.\
apply(active_status, axis = 1, args = (columns_month, ))
pivoted_purchase_status.head()
           2010-12-01 2011-01-01 2011-02-01  ... 2011-10-01 2011-11-01 2011-12-01
CustomerID                                   ...                                 
12346           unreg        new   unactive  ...   unactive   unactive   unactive
12747             new     active   unactive  ...     return     active     active
12748             new     active     active  ...     active     active     active
12749           unreg      unreg      unreg  ...   unactive     return     active
12820           unreg        new   unactive  ...     active   unactive     return

unreg=还未发生消费,new=新用户,unactive=不活跃,active=活跃,return=回流

接下来根据月份汇总统计各个月份中,不同状态用户的频次

purchase_status_counts = pivoted_purchase_status.\
replace('unreg', np.NaN).apply(lambda x:pd.value_counts(x))
purchase_status_counts
          2010-12-01  2011-01-01  ...  2011-11-01  2011-12-01
active           NaN       285.0  ...         549         341
new            807.0       356.0  ...         296          34
return           NaN         NaN  ...         654         181
unactive         NaN       522.0  ...        2363        3340
purchase_status_counts.fillna(0).T.plot.area(figsize = (12,6))

Figure_5.png 各月份不同类型用户的面积占比图

图中为各个月份用户的消费行为。红色部分的活跃用户数在各个月份中都比较稳定。新用户在起始几个月份较多,后面数量有所下降。而回流用户在8月份后显著增加。

status_ratio = purchase_status_counts.apply(lambda x:x / x.sum(),axis = 1)
status_ratio.loc['return'].plot(figsize = (8,6))

Figure_6.png 用户回流占比在3%-18%。在一年中呈现上升趋势。

status_ratio.loc['active'].plot(figsize = (8,6))

Figure_7.png 活跃用户在7月份前的活跃率低于10%,8月份后快速上涨,到11月达到峰值14%。

在消费人群中,往往是少量大客户的销售额占据了整个销售额的大部分,因此我们也希望了解不同用户销售额的占比。

consumer_amount = sd_orders.groupby('CustomerID').Amount.sum().sort_values().reset_index()
consumer_amount['Amount'] = consumer_amount.Amount.cumsum()
consumer_amount.tail()
     CustomerID      Amount
3891      12346  5939173.75
3892      17511  6020126.14
3893      16446  6188598.64
3894      17450  6369445.67
3895      18102  6623368.43
total_amount = consumer_amount.Amount.max()
consumer_amount['fraction'] = consumer_amount.apply(lambda x:x.Amount / total_amount, axis = 1)
consumer_amount.tail()
     CustomerID      Amount  fraction
3891      12346  5939173.75  0.896700
3892      17511  6020126.14  0.908922
3893      16446  6188598.64  0.934358
3894      17450  6369445.67  0.961663
3895      18102  6623368.43  1.000000

这里的计算字段fraction,表示销售额的累积百分比。

consumer_amount.fraction.plot()

Figure_8.png

在趋势图中,横坐标为用户数,纵坐标为有多少对应的用户数贡献了总销售额的百分比。例如,前3500个用户只贡献了销售额的40%,而之后的不到500个用户贡献了总销售额的60%。可见用户的销售额分布总体上符合“二八法则”,少数的用户贡献了大部分的销售额。

用户生命周期

定义:第一次消费至最后一次消费为一个用户的完整生命周期

sd_orders['InvoiceDateA'] = pd.to_datetime(sd_orders['InvoiceDate'])
user_max_date = sd_orders.groupby('CustomerID').InvoiceDateA.max()
user_min_date = sd_orders.groupby('CustomerID').InvoiceDateA.min()
user_max_min_date = pd.DataFrame({'min_date':user_min_date\
, 'max_date':user_max_date}).reset_index()
user_max_min_date.head()
  CustomerID            min_date            max_date
0      12346 2011-01-18 10:01:00 2011-01-18 10:01:00
1      12747 2010-12-05 15:38:00 2011-12-07 14:34:00
2      12748 2010-12-01 12:48:00 2011-12-09 12:20:00
3      12749 2011-05-10 15:25:00 2011-12-06 09:56:00
4      12820 2011-01-17 12:34:00 2011-12-06 15:12:00

此处计算出客户的第一次和最后一次消费时间。

life_cycle = pd.DataFrame()
life_cycle['CustomerID'] = user_max_min_date['CustomerID']
life_cycle['LifeCycle'] = user_max_min_date['max_date'] - user_max_min_date['min_date']
life_cycle.head()

根据用户最后一次消费和第一次消费的时间相减,得到用户的生命周期。用户生命周期的均值为130天(约为4.3个月份)。

  CustomerID         LifeCycle
0      12346   0 days 00:00:00
1      12747 366 days 22:56:00
2      12748 372 days 23:32:00
3      12749 209 days 18:31:00
4      12820 323 days 02:38:00

LifeCycle为用户的生命周期

life_cycle.LifeCycle.mean()
Timedelta('130 days 20:13:37.592402464')

根据用户最后一次消费和第一次消费的时间相减,得到用户的生命周期。用户生命周期的均值为130天(约为4.3个月份)。

# Convert timedelta to values
life_cycle['LifeCycle'] = (life_cycle.LifeCycle/np.timedelta64(1,'D'))
life_cycle['LifeCycle'].hist(bins = 15)

Figure_9.png

消费者的生命周期柱状图

绝大多数的用户只消费了1次,其生命周期为0天。0天以上的消费者分布比较均匀,在200人左右。然而350天以上的消费者超过了300人。

接下来尝试计算在剔除这些只消费一次的消费者(即生命周期为0)后的生命周期。

life_cycle[life_cycle['LifeCycle'] > 0].LifeCycle.hist(bins = 80, figsize=(8,6))

Figure_10.png 仅考虑消费大于一次的用户以后,发现仍然有相当比例的用户生命周期接0天,这些用户可以想办法延长其生命周期。而100-350天的普通用户分布相对均匀。350天以上的高质量用户占有相当一部分比例。

life_cycle[life_cycle['LifeCycle']>0].LifeCycle.mean()
200.30001337044314

消费大于1次的用户,其生命周期均值为200天,远远高于总体均值130天。看来对于消费一次的群体应该想办法引导其多次消费。

留存率的计算

用户在第一次消费后,某个周期内进行第二次消费的比例

customer_retention = pd.merge(left = sd_orders, right = user_max_min_date\
, how = 'inner', on = 'CustomerID', suffixes = ('','_source2'))
customer_retention.head()
  InvoiceNo CustomerID  ...            min_date            max_date
0    536365      17850  ... 2010-12-01 08:26:00 2010-12-02 15:27:00
1    536366      17850  ... 2010-12-01 08:26:00 2010-12-02 15:27:00
2    536372      17850  ... 2010-12-01 08:26:00 2010-12-02 15:27:00
3    536373      17850  ... 2010-12-01 08:26:00 2010-12-02 15:27:00
4    536375      17850  ... 2010-12-01 08:26:00 2010-12-02 15:27:00

使用pd.merge将消费记录和用户的第一次消费时间合并

customer_retention['InvoiceDateDiff'] = ((customer_retention.InvoiceDateA - customer_retention.min_date)\
/np.timedelta64(1,'D')).apply(lambda x:int(x))
customer_retention.head()
  InvoiceNo CustomerID  ...            max_date InvoiceDateDiff
0    536365      17850  ... 2010-12-02 15:27:00               0
1    536366      17850  ... 2010-12-02 15:27:00               0
2    536372      17850  ... 2010-12-02 15:27:00               0
3    536373      17850  ... 2010-12-02 15:27:00               0
4    536375      17850  ... 2010-12-02 15:27:00               0

计算第一次消费与每一订单消费时间的间隔

bin = [0,3,7,15,30,60,90,180,365]
customer_retention['DateDiffBin'] = pd.cut(customer_retention.InvoiceDateDiff, bins = bin)
customer_retention.head(5)
  InvoiceNo CustomerID  ... InvoiceDateDiff DateDiffBin
0    536365      17850  ...               0         NaN
1    536366      17850  ...               0         NaN
2    536372      17850  ...               0         NaN
3    536373      17850  ...               0         NaN
4    536375      17850  ...               0         NaN

当距首次消费天数为0的时候(InvoiceDateDiff=0),订单不会被分到(0,3]天这一分组。如果消费者仅仅消费了一次,那么就没有发生留存的行为。即使一天内消费了多次,而之后没有消费,那么也没有发生留存。

按照0-3天、3-7天、7-15天的时间段进行分桶,代表用户当前消费时间距第一次消费属于哪个时间段。如果某个消费日期距第一次消费的天数为0,则不会划入0-3天。这意味着消费者仅消费了一次,留存率应该是0。同时,消费者同一天内消费多次,也不算做多次消费。

customer_retention_pivot = customer_retention.pivot_table\
(index = 'CustomerID', columns = 'DateDiffBin', values = 'Amount', aggfunc = sum)
customer_retention_pivot.head(5).replace(0, np.NaN)
DateDiffBin  (0, 3]   (3, 7]  (7, 15]  ...  (60, 90]  (90, 180]  (180, 365]
CustomerID                             ...                                 
12346           NaN      NaN      NaN  ...       NaN        NaN         NaN
12747           NaN   286.51      NaN  ...    271.78     753.61     1338.41
12748        244.15  1395.52  1795.68  ...    349.14    3612.24    20467.13
12749           NaN      NaN      NaN  ...   1878.43        NaN     1224.65
12820           NaN      NaN      NaN  ...       NaN        NaN      771.88

使用pivot_table进行数据透视,其中数据值为每个消费者在各个时间段内消费的总金额

customer_retention_pivot.mean()
DateDiffBin
(0, 3]         463.359032
(3, 7]         359.872671
(7, 15]        403.714204
(15, 30]       346.775461
(30, 60]       469.316873
(60, 90]       491.706363
(90, 180]      791.476178
(180, 365]    1834.226279

从各段时间内消费额的平均值来看,90-180天、180-365天的消费金额更高,尽管这两段时间的跨度更大。如果从平均消费时长来看,0-3天内的用户在消费一次后再次消费的金额更多。

customer_retention_pivot_binary = customer_retention_pivot.fillna(0)\
.applymap(lambda x:1 if x >0 else 0)
customer_retention_pivot_binary.head(5)
DateDiffBin  (0, 3]  (3, 7]  (7, 15]  ...  (60, 90]  (90, 180]  (180, 365]
CustomerID                            ...                                 
12346             0       0        0  ...         0          0           0
12747             0       1        0  ...         1          1           1
12748             1       1        1  ...         1          1           1
12749             0       0        0  ...         1          0           1
12820             0       0        0  ...         0          0           1

对于有交易金额的数据点,转化为1,没有发生交易的转化为0.

(customer_retention_pivot_binary.sum() / customer_retention_pivot_binary.count()).plot.bar()

Figure_11.png

0-3天的留存率约为3%左右,3-7天的留存率约为7%左右,而7-15天的留存率为10%。而在90天后的留存率达到了60%。消费者在最初购买后的几天里再次购买的频率不高;但从一个相对长期的时间段(90天以上)上看,留存率却可以达到60%。这可能和数据集中大部分用户在年初发生第一次消费行为,而大量的购买记录发生在下半年(尤其是10月份后)。如果能够根据购买商品的品类进行更具体的分析,可以对留存率有一个更具体的理解。

计算用户的平均购买周期

sum(每一段购物时间的间隔)/时间段总数

def purchase_gap(group):
    gap = group.InvoiceDateDiff - group.InvoiceDateDiff.shift(-1)
    return gap
    
customer_purchase_gap = customer_retention.groupby('CustomerID').apply(purchase_gap)
customer_purchase_gap.tail(10)
CustomerID       
18283       7541     -53.0
            7542     -14.0
            7543       0.0
            7544     -12.0
            7545      -7.0
            7546      -6.0
            7547       NaN
18287       13476   -142.0
            13477    -16.0
            13478      NaN
customer_purchase_gap.mean()

-40.525262990117945

用户平均消费周期为40.5天。

customer_purchase_gap.hist(bins = 20)

Figure_12.png

用户消费周期的直方图来看,大部分用户的消费周期间隔比较短。消费周期在50天以上的比例不高。可以在30天、60天等节点以优惠券、短信推送的方式来召回用户。

可以进一步展开的分析

  1. 在能够获得识别批发/个人消费者的变量的情况下,可以针对批发商/个人消费者做进一步的比较分析。当然,目前缺少相关的变量,也可以根据销量的大小,人为的设定一个区分二者的阈值。
  2. 由于数据集的起始日期为2010年12月,这样计算出来的新用户事实上在更长的时间跨度内可能是老用户,计算的第一次消费可能是不准确的。而复购率、留存率的高低也应当结合更久一点的历史数据来看。
  3. 可以根据商品的描述,使用文本挖掘手段整理出商品的类别。并分析不同商品类别的销售情况。

4.4基于RFM模型的用户聚类分析

RFM中的R、F、M分别指的是:最近一次消费(Recency)、消费频率(Frequency)、消费金额(Monetary)。理论上,上一次消费时间越近的顾客应该是比较好的顾客,可以向这些顾客发送促销信息,激励顾客再次购买。消费频率可以衡量顾客的忠诚度。最常购买的消费者,忠诚度也就越高。消费金额可以衡量顾客的消费能力和消费需求。绝大多数顾客的购买金额分布都符合二八法则——80%的营业收入来源于20%的顾客。

生成订单层面的数据集rfm_orders

在上一节中,在清理数据集后得到商品的销售记录数据集sd_rds。将sdrds中同一订单的多个销售记录合并,生成rfm_orders数据集。

columns = ['InvoiceNo', 'InvoiceDateA', 'CustomerID', 'Quantity', 'Amount']
rfm_orders = sd_rds[columns].groupby(['InvoiceNo','InvoiceDateA', 'CustomerID']).sum().reset_index()
rfm_orders.head()
  InvoiceNo     InvoiceDate CustomerID  Quantity  Amount
0    536365  12/1/2010 8:26      17850        14   61.14
1    536366  12/1/2010 8:28      17850        12   22.20
2    536367  12/1/2010 8:34      13047        83  278.73
3    536368  12/1/2010 8:34      13047        15   70.05
4    536369  12/1/2010 8:35      13047         3   17.85

在此基础上,将订单层面(order level)上的数据集rfmorders转换为顾客层面(customer level)的数据集,并创建Recency、Frequency、Monetary、First_Purchase四个变量。

# 数据转换顾客层面, 创建 Recency, Frequency, Monetary, First_Purchase 变量
rfm_data = rfm_orders.pivot_table(index = 'CustomerID',
            values = ['InvoiceDateA', 'Quantity', 'Amount'],
            aggfunc = {'InvoiceDateA':['max','min'],
                        'Quantity':'count',
                        'Amount':'sum'
            }).reset_index()

# rfm_data['InvoiceDate']['max'].max() means the last date for this year
rfm_data['Recency'] = (rfm_data['InvoiceDateA']['max'].max() - rfm_data['InvoiceDateA']['max'])/ np.timedelta64(1, 'M')
# 'First' means the 
rfm_data['First'] = (rfm_data['InvoiceDateA']['max'].max() - rfm_data['InvoiceDateA']['min'])/ np.timedelta64(1, 'M')
rfm_data['Recency'] = rfm_data['Recency'].astype('int')
rfm_data['First'] = rfm_data['First'].astype('int')
rfm_data['CustomerID'] = rfm_data['CustomerID'].astype('str')
rfm_data.rename(columns={'Recency':'R', 'First':'1st','Quantity':'F', 'Amount':'M'}, inplace = True)
rfm_data.pop('InvoiceDateA')
# drop intermediate level 'sum' and 'count'
rfm_data.columns = rfm_data.columns.droplevel(1)
rfm_data.head()
  CustomerID         M    F   R  1st
0      12346  77183.60    1  10   10
1      12747   3569.41   11   0   12
2      12748  29279.34  200   0   12
3      12749   3962.18    5   0    6
4      12820    924.64    4   0   10

Recency(R): 最近一次购买的日期距2011年12月31日的月份数。例如,某用户最后一次购买距2011年12月31日为1个月,那么其Recency为1。

Frequency(F): 用户购买的频率,一张订单号记为一次购买

Money(M): 一个用户购买的金额总数

1st:第一次购买的日期距2011年12月31日的月份数。例如,某用户第一次购买距2011年12月31日为12个月,那么1st变量的值为12。

plt.figure(figsize=(8,6))
rfm_data['R'].plot(kind = 'hist', color = 'gray', bins = 12)
plt.ylabel('Count')
plt.title('Recency\'s Distribution')

Figure_13.png 上图为用户Regency的分布,可以看到大部分用户最近一次购买在0-2个月内。

plt.figure(figsize=(8,6))
rfm_data['F'].plot(kind = 'hist', color = 'gray', bins = 50)
plt.ylabel('Count')
plt.title('Frequency\'s Distribution')

Figure_14.png 用户的Frequency分布呈现出了长尾分布的形态。大部分用户的频率集中于0-10次。然而少数用户的频率分布于50-200次之间。

plt.figure(figsize=(8,6))
rfm_data['M'].plot(kind = 'hist', color = 'gray', bins = 50)
plt.ylabel('Count')
plt.title('Money\'s Distribution')
plt.show()

Figure_15.png 用户消费金额也呈现出长尾分布的特点。少量的大客户贡献了大部分的销售业绩。

剔除异常值

由于聚类分析对于数据中的异常值非常敏感,这会对于分析的结果造成大的影响。因此我们列出前1%用户的消费金额和频次。

print("消费榜前10名", '\n', rfm_data.sort_values(by='M', ascending=False)['M'].head(10))
print("消费前1%记录数", '\n', rfm_data.sort_values(by='M', ascending=False)['M'].quantile(0.99))
消费前10名 
 3762    253922.76
3299    180847.03
2589    168472.50
3341     80952.39
0        77183.60
2285     65153.49
2756     60156.60
3655     57767.68
189      54149.93
1574     53771.76
消费前1%记录数 
 16210.248000000138

根据Money的数值进行倒序排序,然后输出Money前10的记录。排名前三的记录在160000以上,同时前1%分位数的Money值只有16000左右。仅剔除金额在100000以上的记录是不够的,可以将Money大于前1%分位数数值(16210)的所有记录列为异常值。

print("频率前10名", '\n', rfm_data.sort_values(by='F', ascending=False)['F'].head(10))
print("频率前1%记录数", '\n', rfm_data.sort_values(by='F', ascending=False)['F'].quantile(0.99))
频率前10名 
 2       200
3574    122
189      93
1766     91
1263     91
109      85
420      61
2285     61
3762     60
1205     55
频率前1%记录数 
 30.0

与Money的分析类似,Frequency在前1%分位数的值为30。接下来将Frequency大于前1%分位数数值(30)的所有记录列为异常值。

rfm_clean = rfm_data.copy(deep=True)
# drop outliers in Money and Frequency
outlier_M_thres = rfm_data.sort_values(by='M', ascending=False)['M'].quantile(0.99)
rfm_clean.drop(rfm_clean[rfm_clean['M'] > outlier_M_thres].index, inplace=True)
outlier_F_thres = rfm_data.sort_values(by='F', ascending=False)['F'].quantile(0.99)
rfm_clean.drop(rfm_clean[rfm_clean['F'] > outlier_F_thres].index, inplace=True)

print('{0} {1}'.format(len(rfm_data)-len(rfm_clean), "obs were dropped after censoring"))
print(len(rfm_clean))
print(rfm_clean.describe())
                  M            F            R          1st
count   3840.000000  3840.000000  3840.000000  3840.000000
mean    1182.892003     3.689583     2.600260     6.776042
std     1687.904315     4.012159     3.262797     3.900474
min        4.250000     1.000000     0.000000     0.000000
25%      258.310000     1.000000     0.000000     3.000000
50%      590.675000     2.000000     1.000000     8.000000
75%     1382.585000     4.000000     4.000000    10.000000
max    16172.050000    30.000000    12.000000    12.000000

最终得到清除异常值后的数据集rfm_clean,其中有3840个消费者。

数据标准化(Normalization)

用于聚类分析的三个变量Money、Frequency以及Recency的单位不同,其取值范围差别很大。可能会使得Money这种取值范围很大的变量在聚类过程中占据了过高的权重。因此在聚类分析前对三个变量进行按比例缩放。

rfm_norm = rfm_clean.copy(deep=True)
rfm_norm['M'] = rfm_norm['M'].apply(lambda x:\
(x - rfm_norm['M'].min())/(rfm_norm['M'].max() - rfm_clean['M'].min()))
rfm_norm['F'] = rfm_norm['F'].apply(lambda x:\
(x - rfm_norm['F'].min())/(rfm_norm['F'].max() - rfm_clean['F'].min()))
rfm_norm['R'] = rfm_norm['R'].apply(lambda x:\
(x - rfm_norm['R'].min())/(rfm_norm['R'].max() - rfm_clean['R'].min()))
rfm_norm.head()
  CustomerID         M         F         R  1st
1      12747  0.220510  0.344828  0.000000   12
3      12749  0.244803  0.137931  0.000000    6
4      12820  0.056927  0.103448  0.000000   10
5      12821  0.005472  0.000000  0.583333    7
6      12822  0.058427  0.034483  0.166667    2

聚类分析

from sklearn.cluster import KMeans
from mpl_toolkits.mplot3d import Axes3D

在聚类分析中,对于类别的设定往往并没有一个特别清晰的标准,通常要结合实际的业务情况来确定。

组内平方和法

该方法是画出不同聚类数目(通常是1到10)对应的组内平方和,以组内平方和基本不再明显变化为标准(较为主观),从而确定聚类的数目。

km_input_matrix = rfm_norm[['M','R','F']].values.copy()

clusters = np.arange(2,11)
# get sum of square within group for each number of cluster
inertia_list = []
for i in range(2,11):
    km = KMeans(n_clusters=i, random_state = 42).fit(km_input_matrix)
    inertia_list.append(km.inertia_)
fig = plt.figure(figsize=(8,6))
plt.plot(clusters, inertia_list, marker = 'o')
plt.xlabel('聚类数量')
plt.ylabel('组内平方和')
plt.show()

Figure_16.png

从图中来看,Cluster的数目设定在3时组内平方和发生了大幅的下降,到5时趋于平稳。相比之下,分为5组是一个较优的选择。我们在这里分别对Cluster=3及Cluster=5绘制出3d散点图。

3 Clusters

首先将分类的Cluster数值设定为3,并从两个角度绘制三维散点图

estimators = [('km_3_c', KMeans(n_clusters=3, random_state = 42)),
             ('km_5_c', KMeans(n_clusters=5, random_state = 42))]
titles = ['3 clusters', '5clusters']

estimators[0][1].fit(km_input_matrix)
labels = estimators[0][1].labels_

fig1 = plt.figure(1, figsize=(8, 6))
ax1 = Axes3D(fig1, rect=[0,0,.95, 1], elev=48, azim=134)
# c means inner color
c1_ax1 = ax1.scatter(rfm_clean['R'][labels == 0], rfm_clean['F'][labels == 0], rfm_clean['M'][labels == 0],
             edgecolor='k', color = 'r')
c2_ax1 = ax1.scatter(rfm_clean['R'][labels == 1], rfm_clean['F'][labels == 1], rfm_clean['M'][labels == 1],
             edgecolor='k', color = 'b')
c3_ax1 = ax1.scatter(rfm_clean['R'][labels == 2], rfm_clean['F'][labels == 2], rfm_clean['M'][labels == 2],
             edgecolor='k', color = 'c')
ax1.legend([c1_ax1,c2_ax1,c3_ax1], ['Cluster 1', 'Cluster 2', 'Cluster 3'])
ax1.invert_xaxis()
ax1.set_xlabel('Recency')
ax1.set_ylabel('Frequency')
ax1.set_zlabel('Money')
ax1.set_title(titles[0])
ax1.dist = 12

fig2 = plt.figure(2, figsize=(8, 6))
ax2 = Axes3D(fig2, rect=[0,0,.95, 1], elev=30, azim=-60)
# c means inner color
c1_ax2 = ax2.scatter(rfm_clean['R'][labels == 0], rfm_clean['F'][labels == 0], rfm_clean['M'][labels == 0],
             edgecolor='k', color = 'r')
c2_ax2 = ax2.scatter(rfm_clean['R'][labels == 1], rfm_clean['F'][labels == 1], rfm_clean['M'][labels == 1],
             edgecolor='k', color = 'b')
c3_ax2 = ax2.scatter(rfm_clean['R'][labels == 2], rfm_clean['F'][labels == 2], rfm_clean['M'][labels == 2],
             edgecolor='k', color = 'c')
ax2.legend([c1_ax2,c2_ax2,c3_ax2], ['Cluster 1', 'Cluster 2', 'Cluster 3'])
ax2.invert_xaxis()
ax2.set_xlabel('Recency')
ax2.set_ylabel('Frequency')
ax2.set_zlabel('Money')
ax2.set_title(titles[0])
ax2.dist = 12

Figure_17.png 由于聚类分析中簇的个数选择会影响分析的结论。我们先选择3个簇进行分类,得到红色、靛蓝和深蓝三个群体。红色群体(Cluster1):最近一次购买时间集中于年初、购买频率低、购买金额少。靛蓝群体(Cluster3):最近一次购买集中于半年内、购买频率相对较低、购买金额相比紫色群体略高。深蓝群体(Cluster2):最近一次购买集中于半年内、购买频率高、相当多的点落在购买金额高的区域。

可以粗略的将这三个群体分为低价值群体、中等价值群体以及高价值群体。

5 Clusters

接下来将聚类的Cluster值设定为5。

estimators[1][1].fit(km_input_matrix)
labels = estimators[1][1].labels_

fig1 = plt.figure(1, figsize=(8, 6))
ax1 = Axes3D(fig1, rect=[0,0,.95, 1], elev=48, azim=134)
# c means inner color
c1_ax1 = ax1.scatter(rfm_clean['R'][labels == 0], rfm_clean['F'][labels == 0], rfm_clean['M'][labels == 0],
             edgecolor='k', color = 'r')
c2_ax1 = ax1.scatter(rfm_clean['R'][labels == 1], rfm_clean['F'][labels == 1], rfm_clean['M'][labels == 1],
             edgecolor='k', color = 'b')
c3_ax1 = ax1.scatter(rfm_clean['R'][labels == 2], rfm_clean['F'][labels == 2], rfm_clean['M'][labels == 2],
             edgecolor='k', color = 'c')
c4_ax1 = ax1.scatter(rfm_clean['R'][labels == 3], rfm_clean['F'][labels == 3], rfm_clean['M'][labels == 3],
             edgecolor='k', color = 'g')
c5_ax1 = ax1.scatter(rfm_clean['R'][labels == 4], rfm_clean['F'][labels == 4], rfm_clean['M'][labels == 4],
             edgecolor='k', color = 'm')
ax1.legend([c1_ax1,c2_ax1,c3_ax1,c4_ax1,c5_ax1], ['Cluster 1', 'Cluster 2', 'Cluster 3', 'Cluster 4', 'Cluster 5'])
ax1.invert_xaxis()
ax1.set_xlabel('Recency')
ax1.set_ylabel('Frequency')
ax1.set_zlabel('Money')
ax1.set_title(titles[1])
ax1.dist = 12

fig2 = plt.figure(2, figsize=(8, 6))
ax2 = Axes3D(fig2, rect=[0,0,.95, 1], elev=30, azim=-60)
# c means inner color
c1_ax2 = ax2.scatter(rfm_clean['R'][labels == 0], rfm_clean['F'][labels == 0], rfm_clean['M'][labels == 0],
             edgecolor='k', color = 'r')
c2_ax2 = ax2.scatter(rfm_clean['R'][labels == 1], rfm_clean['F'][labels == 1], rfm_clean['M'][labels == 1],
             edgecolor='k', color = 'b')
c3_ax2 = ax2.scatter(rfm_clean['R'][labels == 2], rfm_clean['F'][labels == 2], rfm_clean['M'][labels == 2],
             edgecolor='k', color = 'c')
c4_ax2 = ax2.scatter(rfm_clean['R'][labels == 3], rfm_clean['F'][labels == 3], rfm_clean['M'][labels == 3],
             edgecolor='k', color = 'g')
c5_ax2 = ax2.scatter(rfm_clean['R'][labels == 4], rfm_clean['F'][labels == 4], rfm_clean['M'][labels == 4],
             edgecolor='k', color = 'm')
ax2.legend([c1_ax2,c2_ax2,c3_ax2,c4_ax2,c5_ax2], ['Cluster 1', 'Cluster 2', 'Cluster 3', 'Cluster 4', 'Cluster 5'])
ax2.invert_xaxis()
ax2.set_xlabel('Recency')
ax2.set_ylabel('Frequency')
ax2.set_zlabel('Money')
ax2.set_title(titles[1])
ax2.dist = 12

Figure_18.png 如果起始簇的个数设定为5的话,得到的结果如上图。

深蓝群体(Cluster2):Recency数值大(最近一次购买距今时间长)、Frequency频次很低、购买金额很少

粉色群体(Cluster5):Recency距今3-6个月、Frequency在5-10次、购买金额较少

红色群体(Cluster1):Recency距今3个月以内、Frequency在5-10次、购买金额相比群体2更多

靛蓝群体(Cluster3):Recency距今3个月以内、Frequency在10-20次、购买金额在2000以上

绿色群体(Cluster4):Recency距今3个月以内、Frequency在15次以上、购买金额高

labels_5_clusters = labels
for i in range(len(set(labels_5_clusters))):
    print("Stats of Cluster {}".format(i+1),'\n',rfm_clean[labels_5_clusters == i].describe())
Stats of Cluster 1 
                  M            F            R          1st
count  1829.000000  1829.000000  1829.000000  1829.000000
mean    711.111296     2.463641     0.733734     4.696009
std     588.326085     1.368773     0.788084     3.955924
min       4.250000     1.000000     0.000000     0.000000
25%     279.820000     1.000000     0.000000     1.000000
50%     558.700000     2.000000     1.000000     3.000000
75%     962.060000     3.000000     1.000000     8.000000
max    4314.720000     6.000000     2.000000    12.000000
Stats of Cluster 2 
                  M           F           R         1st
count   676.000000  676.000000  676.000000  676.000000
mean    598.074586    2.184911    4.439349    6.948225
std     581.293230    1.495616    1.136931    2.756592
min       4.950000    1.000000    3.000000    3.000000
25%     207.465000    1.000000    3.000000    5.000000
50%     390.710000    2.000000    4.000000    6.000000
75%     765.350000    3.000000    5.000000    9.000000
max    3920.040000   12.000000    7.000000   12.000000
Stats of Cluster 3 
                  M           F           R         1st
count   595.000000  595.000000  595.000000  595.000000
mean   2740.457193    8.191597    0.359664    9.386555
std    1342.094788    2.552532    0.687836    2.669535
min     159.220000    3.000000    0.000000    0.000000
25%    1762.705000    6.000000    0.000000    8.000000
50%    2481.690000    8.000000    0.000000   10.000000
75%    3412.910000   10.000000    1.000000   11.000000
max    9452.300000   17.000000    5.000000   12.000000
Stats of Cluster 4 
                  M           F           R         1st
count   603.000000  603.000000  603.000000  603.000000
mean    349.067778    1.325041    8.978441    9.379768
std     479.108641    0.803121    1.592507    1.651554
min      12.750000    1.000000    7.000000    7.000000
25%     133.640000    1.000000    8.000000    8.000000
50%     234.000000    1.000000    9.000000    9.000000
75%     378.505000    1.000000   10.000000   11.000000
max    7084.120000    8.000000   12.000000   12.000000
Stats of Cluster 5 
                   M           F           R         1st
count    137.000000  137.000000  137.000000  137.000000
mean    7272.440949   18.335766    0.102190   10.897810
std     3076.413439    5.077745    0.407371    1.694648
min     2186.470000    3.000000    0.000000    0.000000
25%     4974.990000   15.000000    0.000000   11.000000
50%     6718.160000   18.000000    0.000000   11.000000
75%     8895.960000   21.000000    0.000000   12.000000
max    16172.050000   30.000000    3.000000   12.000000

从5个群体的描述性统计来看,

Cluster1(红色)其消费水平、消费频次和距今消费时间在一个合理的区间上,其数量最多(1809)。

Cluster2(深蓝)其低消费水平、低消费频次和距今消费时间长,其数量为603。

Cluster3(靛蓝)其消费水平较高、消费频次较高、距今消费时间短,是较优质的客户,其数量为614。

Cluster5(粉色)相比于Cluster1(红色)要优质一些,其数量为676。

Cluster4(绿色)作为购买金额最大的群体,对利润的贡献率最高。但其数量较少,只有138。

未来可以进行的深入分析

针对分群后的各个顾客群体,分析不同群体的顾客购买不同商品(根据StockCode区分)的情况,例如哪些商品经常放在一起购买,以及不同商品下订单的顺序 尽管数量不多,但那些对金额贡献最大的群体是Cluster4(绿色),大部分可能是企业采购这样的大客户。可以进一步分析这些群体偏好购买那些产品,购买的集中时间点。可以适当的为这些客户做推荐。

5.小结

  通过对一段时间内的用户行为数据分析,可以为客户提供更精准的隐式反馈推荐。从用户角度:帮助用户快速、准确找到想要的产品,提高用户满意度、忠诚度;从网站角度:提高网站交叉销售能力,提高成交转化率,提高销售额。

备注: 本文所列运行结果使用的环境为python 3.7.6及pandas 1.3.1