CD案例分析

本文是对CD案例的一个总结,主要是根据用户消费记录,分析用户消费行为,建立RFM模型,分析复购率、回购率等关键指标。可以更清楚了解用户行为习惯,为进一步制定营销策略提供依据。

目录:

  • 项目背景

  • 分析目标

  • 分析过程

  • 小结

一、项目背景

本次分析报告的数据来源于这家CD网站上的用户消费记录,旨在分析用户消费行为,建立RFM模型,分析复购率、回购率等关键指标。

二、分析目标

本次分析报告的数据来源于这家CD网站上的用户消费记录,旨在分析用户消费行为,建立RFM模型,分析复购率、回购率等关键指标。

三、分析过程

  • 准备工作(数据集观察与数据清洗)

  • 用户消费趋势分析(按月)——每月消费总金额、消费次数、产品销量、消费人数。

  • 用户个体消费分析——用户消费金额,产品购买量的描述性统计、用户消费金额和产品购买量分布、用户累计消费金额占比。

  • 用户消费行为分析——用户第一次消费(首购)时间分布、用户最后一次消费时间分布、新老用户占比、用户分层RFM模型、各类用户(新用户、活跃用户、流失用户、回流用户)数量和占比、用户购买周期、用户生命周期

  • 用户复购率和回购率分析——复购率、回购率

(1) 准备工作(数据集观察与数据清洗)

(1)导入常用包

import pandas as pd 

import numpy as np

(2)导入数据集
数据集为txt文件格式,没有列名,并且字段之间使用多个空格进行分隔。

这里读取文件时使用sep='\s+',如果在分隔符里存在多个字符串,可以用s+,+类似于正则匹配,无论分隔符有多少个空格都可以自动处理。

columns=['user_id','order_dt','order_products','order_amount']
df=pd.read_table(r'D:\exercise_data\python\bicycle_master.txt',names=columns, sep='\s+')
导入数据集形式.png

(3)数据清洗
检查数据,发现数据集不存在缺失值,但是order_dt数据类型为int64,需要转换为日期型datetime64[ns]。


数据集摘要信息.png

由于接下来需要按月分析用户消费趋势,这里新建一个month字段。将order_dt列转换为datetime64[M],默认会是每月的第一天。
同理,如果设置为datetime64[Y]就是每年的1月1日。

df['order_dt']=pd.to_datetime(df.order_dt,format="%Y%m%d")
df['month']=df.order_dt.values.astype('datetime64[M]')
对日期处理后的数据集摘要信息.png

(4)数据初步了解

  • 大部分订单只消费了少量商品(平均2.4),有一定极值的干扰。
  • 用户的消费金额比较稳定,平均消费35元,中位数在25元,有一定极值的干扰。


    数据集基本描述统计.png

(2)用户消费趋势分析(按月)

  • 产品每月销量
  • 每月的消费总金额
  • 消费次数
  • 消费人数

1. 产品每月销量

grouped_month=df.groupby('month')
order_month_amount=grouped_month.order_amount.sum()
order_month_amount.head()

加载可视化数据包

import matplotlib.pyplot as plt
# 魔法函数,用于可视化自动显示
%matplotlib inline
plt.rcParams['font.family'] = ['sans-serif']
plt.rcParams['font.sans-serif'] = ['SimHei']
#更改设计风格
plt.figure(1, figsize=(10, 4))
plt.title('每月用户购买张数')
plt.ylabel('CD碟数(张)')
grouped_month.order_products.sum().plot()
每月销量走势图

从图中可以看到,销量在前几个月异常高涨,并在3月达到最高峰,后续销量较为稳定,且有轻微下降趋势。

  1. 每月的消费总金额
plt.figure(1, figsize=(10, 4))
plt.title('每月消费总金额')
plt.ylabel('消费总金额(元)')
order_month_amount.plot()
消费总金额走势图

由图可以看到,消费金额也一样呈现早期销售多,后期平稳下降趋势,而且前三月数据都呈现出异常状态,为什么呈现这样的原因呢?我们可以假设前三个月有促销活动,或者用户本身出了问题,早期用户有异常值。但这里只有消费数据,因此不能做出判断。

  1. 每月消费次数
plt.figure(1, figsize=(10, 4))
plt.title('每月订单数')
plt.ylabel('订单数')
grouped_month.user_id.count().plot()
每月消费次数走势图

由上图可知,前三个月订单数在 10000 笔左右,后续月份的平均消费订单数则在 2500 笔左右。

  1. 消费人数
plt.figure(1, figsize=(10, 4))
plt.title('每月客户量')
plt.ylabel('客户量(人)')
grouped_month.user_id.apply(lambda x: len(x.drop_duplicates())).plot()
消费人数走势

每月消费人数低于每月消费次数,但差异不大。
前三个月每月消费人数在 8000~10000 之间,后续月份,平均消费人数在 2000 人不到。

  1. 数据透视表
    上面进行的汇总分析,其实可以用数据透视的方法更快实现,一次求出每月的消费总金额、每月的消费次数、每月的产品购买量。
df.pivot_table(index='month',
              values=['order_products','order_amount','user_id'],
              aggfunc={'order_products':'sum',
                      'order_amount':'sum',
                      'user_id':'count'}).head()
每月销售金额、销量、订单的透视表

但是每月的消费人数这个指标需要去重操作,不适合用数据透视表。

(3) 用户个体消费分析

  • 用户消费金额、 产品购买量的描述性统计
  • 用户消费金额和产品购买量分布
  • 用户累计消费金额占比
    1. 用户消费金额、产品购买量的描述性统计
grouped_user=df.groupby('user_id')
grouped_user.sum().describe()
用户消费金额、购买量描述性统计
  • 从用户购买量角度

用户数量:23570,每位用户平均购买 7 张 CD,但是中位数值只有3,且有狂热用户购买了1033 张。平均值大于中位数,是右偏分布,说明小部分用户购买了大量的 CD。

从消费金额角度

用户平均消费 106 元,中位值只有 43,且有土豪用户消费 13990,结合分位数和最大值看,平均数仅和 75 分位接近,肯定存在小部分的高频消费用户。

2. 用户消费金额和产品购买量分布

plt.scatter(x = 'order_amount', y = 'order_products',data=df)
plt.xlabel('每笔订单消费金额')
plt.ylabel('每笔订单购买数量')
每笔订单消费金额和订单购买量的散点图
  • 由上图可知,订单消费金额和订单商品有规律性,每个商品均价 10元,且订单极值较少,超出 1000 的不多,所以不是造成异常波动的原因。
plt.grid()
plt.scatter(x = 'order_amount',y = 'order_products', 
            data = grouped_user.sum()) 
plt.xlabel('每位用户消费金额')
plt.ylabel('每位用户购买数量')
用户消费金额和产品购买量的散点图
  • 由上图可知,用户比较健康,且线性关系比订单更强,由于这是 CD 网站的销售数据,商品较为单一,金额和商品的关系因此呈线性关系,离群点不多。

3. 用户消费金额的分布图
为了排除小部分异常值干扰了判断,使用切比雪夫定理过滤掉异常值,因为切比雪夫定理说明,因为切比雪夫定理说明,所有数据中至少有96%的数据位于平均数5个标准差之内,剩下4%的极值就过滤掉。
order_amount:mean+5std = 106+241*5=1311

ax = grouped_user.order_amount.sum().hist(bins=50)
ax.set_xlabel('金额(美元)')
ax.set_ylabel('用户人数')
ax.set_xlim(0, 1400)
ax.set_title('用户消费金额分布图')
  • 从直方图可知,用户消费金额,绝大部分呈现集中趋势;可以明显地观察到,用户消费金额集中在0~200元左右。

4. 用户产品购买量的分布图
根据切比雪夫定理:
order_products: mean+5std = 7+17*5=92

ax1 = grouped_user.order_products.sum().hist(bins = 50)
ax1.set_xlabel('CD 数(张)')
ax1.set_ylabel('用户人数')
ax1.set_xlim(0, 100)
ax1.set_title('购买 CD 数分布图')
  • 从直方图可知,产品购买量,绝大部分呈现集中趋势;大部分购买CD数20张内,高消费用户在图上几乎看不到,这是符合消费行为的行业规律。

5. 用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)

# 按照用户消费金额进行升序排序,cumsum 是求累加值
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
# reset_index() 是为了得到一个自然数的行标签,表示累计用户数量
plt.title('用户累计消费金额占比')
plt.xlabel('人数')
plt.ylabel('百分比 %')
user_cumsum.reset_index().order_amount.plot()
  • 通过分析累计销售额占比,(总共用户量为23570名)从图中不难看出用户消费行为基本符合二八定律,50%的用户仅贡献了15%的消费额度,70%的用户仅贡献了20%的消费额度,85%的用户仅贡献了40%的消费额度,而排名前3000多的用户就贡献了60%的消费额度,就能完成 60% 的KPI。

(4)用户消费行为

  • 用户第一次消费(首购)时间
  • 用户最后一次消费时间
  • 新老用户占比
  • 用户分层
    (1) RFM (RFM模型是衡量客户价值和客户创利能力的重要工具和手段)
    (2) 新、老、活跃、回流、流失
  • 用户购买周期(按订单)
    用户消费周期描述
    用户消费周期分布
  • 用户生命周期(按第一次&最后一次消费)
    用户生命周期描述
    用户生命周期分布
    1.用户第一次消费(首购)时间分布
grouped_user.min().month.value_counts()
用户首购消费时间
grouped_user.min().order_dt.value_counts().plot()

用户首购时间分布

2. 用户最后一次消费时间分布

grouped_user.month.max().value_counts()
最后一次消费时间
grouped_user.max().order_dt.value_counts().plot() # 最后一次消费
plt.grid()
最后一次消费时间分布

通过以上两个维度观察,可以看出

  • 用户第一次购买分布,集中在前三个月,其中,在 2 月 11 日至 2 月 25 日有一次剧烈波动。
  • 用户最后一次购买的分布比第一次分布广,但是大部分最后一次购买也集中在前三个月,说明忠诚用户较少,随着时间的递增,最后一次购买数在递增,消费呈现流失上升的趋势,所以可以推测,这份数据选择的是前三个月消费的用户在后面18个月的跟踪记录数据,前三个月消费金额和购买数量的异常趋势获得解释。

3. 新老用户占比

# 第一次和最后一次消费日期相同,说明只消费了一次
user_life=grouped_user.order_dt.agg(['min','max'])
# 统计只消费了一次的用户
(user_life['min']==user_life['max']).value_counts()
消费一次的客户人数
  • 结果表明,有一半的用户,只消费了一次

每月新用户占比

# 按照month、userid分组,第一次和最后一次消费日期
user_life_month=df.groupby(['month','user_id']).order_dt.agg(['min','max']).reset_index()
# 新增is_new字段,用于标记新用户
user_life_month['is_new']=(user_life_month['min']==user_life_month['max'])
# 再次按month分组,计算新用户占比
user_life_month_pct=user_life_month.groupby('month').is_new.apply(lambda x:x.value_counts()/x.count()).reset_index()
# level_1为True的作图
user_life_month_pct[user_life_month_pct.level_1].plot(x='month',y='is_new')
plt.grid()
每月新用户占比分布图

1997年1月新用户占比高达90%以上,后续有所下降,并逐渐趋于平稳,1997年4月到1998年6月维持在81%左右,1998年6月以后无新用户。

4. 用户分层

  • 4.1 RFM模型
# 画RFM,先对原始数据进行透视
rfm=df.pivot_table(index='user_id',
                  values=['order_products','order_amount','order_dt'],
                  aggfunc={'order_dt':'max',
                          'order_amount':'sum',
                          'order_products':'count'})
rfm.head()
输出结果
# R:最近一次消费(距今天数),这里因为消费时间距今太久远,就不用今天了,而是选用一个时间最大值
# 这里是时间格式相减,得到xxx days,所以需要除以一个单位'D'
rfm['R']= -(rfm.order_dt - rfm.order_dt.max())/np.timedelta64(1,'D')
# F:消费频次
# M:消费金额
rfm.rename(columns={'order_products':"F",'order_amount':'M'},inplace=True)
rfm.head()
输出结果
def rfm_func(x):
    level=x.apply(lambda x:'1' if x>=0 else '0')
    # level是Series,R/F/M都是其索引
    # 字符串拼接
    label=level.R + level.F + level.M
    d={
        '111':'重要价值客户',
        '011':'重要保持客户',
        '101':'重要发展客户',
        '001':'重要挽留客户',
        '110':'一般价值客户',
        '010':'一般保持客户',
        '100':'一般发展客户',
        '000':'一般挽留客户',
    }
    result=d[label]
    return result
# 这里要一行行的传递进来,所以 axis=1,传递一行得到一个 label,然后匹配返回一个值
rfm['label']=rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
rfm.head()
输出结果
# 其实应该设置8个颜色,这里做了简化:
rfm.loc[rfm.label=='重要价值客户','color']='g'
rfm.loc[~(rfm.label=='重要价值客户'),'color']='r'
rfm.plot.scatter('F','R',c=rfm.color)
  • 绿色部分是重要价值客户,消费频次较高,但最近一次消费时间距今较远。
# 8种客户类型的总消费金额、消费频次
rfm.groupby('label').sum() 
  • 8种客户中,重要保持客户(011)的消费频次和消费金额最高。其次是一般发展客户(100)。
# 8种客户类型的人数
rfm.groupby('label').count()

8种客户中,一般发展客户(100)的人数最多,其次是重要保持客户(011)。

  • 从RFM分层可知,大部分用户为一般发展客户,但是这是由于极值的影响,所以RFM的划分标准应该以业务为准,也可以通过切比雪夫定理去除极值后求均值,并且 RFM 的各个划分标准可以都不一样。

尽量用小部分的用户覆盖大部分的额度
不要为了数据好看划分等级

  • 4.2 新用户、活跃用户、流失用户、回流用户
# 每月的消费次数,缺失值用0填充
pivoted_counts=df.pivot_table(index='user_id',
                             columns='month',
                             values='order_dt',
                             aggfunc='count').fillna(0)
pivoted_counts.head()
# 上面填充了一些null值为0,而实际可能用户在当月根本就没有注册,这样会误导第一次消费数据的统计,所以写一个函数来处理
def active_status(data):
    status=[]
    # 数据一共有18个月份,每次输入一行数据,也就是一个user_id的信息,进行逐月判断
    for i in range(18):
        # 若本月没有消费
        if data[i]==0:
            # 判断之前有没有数据,之前有数据
            if len(status)>0:
                # 判断上个月是否为未注册(如果上个月未注册,本月没有消费,仍为未注册)
                if status[i-1]=='unreg':
                    status.append('unreg')
                # 上月有消费,本月没有消费,则为不活跃
                else:
                    status.append('unactive')
            # 之前一个数据都没有,就认为是未注册
            else:
                status.append('unreg')
 
        # 若本月消费
        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 status
  • 关于active_status的说明:

若本月没有消费,这里只是和上个月判断是否注册,有一定的缺陷,应该判断是否存在就可以了

若之前有数据,是未注册,则依旧为未注册
若之前有数据,不是未注册,则为流失/不活跃
若之前没有数据,为未注册
若本月有消费

若是第一次消费,则为新用户
若之前有过消费,上个月为不活跃,则为回流
若之前有过消费,上个月为未注册,则为新用户
若之前有过消费,其他情况为活跃
return:回流 new:新客 unreg:未注册 active:活跃

注意:主流写法还是使用 etl ,不是通过透视表。

purchase_stats=df_purchase.apply(lambda x: pd.Series(active_status(x),index=df_purchase.columns),axis=1)
purchase_stats.head()
# 把未注册的替换为空值,这样count不会计算到,得到每个月的用户分布
purchase_stats_ct=purchase_stats.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
purchase_stats_ct
# 把null值填充为0,并进行转置
# 绘制面积图,描述用户分布
purchase_stats_ct.fillna(0).T.plot.area()
用户状态分布图

橙色是新用户,前3个月大量涌入,后面没有新增。

蓝色是活跃用户,前几个月较多,后面有所下降。

绿色是回流用户,数量趋于稳定,每月1000多。

红色是流失/不活跃用户,数量非常多,基本上每月都在20000以上。

# 计算每月各类用户占比
purchase_stats_ct.fillna(0).T.apply(lambda x:x/x.sum(),axis=1)
各类用户占比
purchase_stats_ct.fillna(0).T.apply(lambda x:x/x.sum(),axis=1).plot.area()
  • 前三个月有大量新用户涌入,新用户占比很高,而后面几个月不活跃用户占比非常高,普遍在90%以上。

  • 活跃用户,持续消费的用户,对应的是消费运营的质量。

  • 回流用户,之前不消费,本月才消费,对应的是唤回运营。

  • 不活跃用户,对应的是流失
    5.用户购买周期(按订单)

  • 5.1 用户购买周期描述

# 计算相邻两个订单的时间间隔,shift函数是对数据进行错位,所有数据会往下平移一下
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
order_diff.head(10)
输出结果
# 描述性统计
order_diff.describe()
  • 5.2 用户购买周期分布
# 去除单位
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
订单周期分布图
  • 订单周期呈指数分布
  • 用户的平均购买周期是 68 天
  • 绝大部分用户的购买周期低于 100 天
    6.用户生命周期(按第一次和最后一次消费)
  • 6.1用户生命周期描述
# 用户生命周期(按第一次和最后一次消费)
(user_life['max']-user_life['min']).describe()
用户生命周期描述统计
  • 6.2用户生命周期分布
((user_life['max']-user_life['min'])/np.timedelta64(1,"D")).hist(bins=40)
用户生命周期分布
  • 用户平均生命周期134天,但中位数仅0天。
  • 用户的生命周期分布受只购买一次的用户(用户生命周期0天)影响比较严重,可以排除。
# 提取用户生命周期大于0的数据
u_1=(user_life['max']-user_life['min']).reset_index()[0]/np.timedelta64(1,"D")
u_1[u_1>0].hist(bins=40)
用户生命周期分布
u_1[u_1>0].describe()
用户生命周期描述统计
  • 排除了只购买一次的用户(生命周期0天)影响,这部分用户占了接近一半。
  • 用户平均生命周期276天,中位数302天。
  • 消费两次以上的用户平均生命周期是 276 天,远高于总体,所以如何在用户首次消费后引导其进行多次消费,可以有效提高用户生命周期。

(5)复购率和回购率分析

  • 复购率:自然月内,购买多次的用户占比。

  • 回购率:曾经购买过的用户在某一时期再次购买的占比。

1.复购率

# 消费次数
pivoted_counts.head(10)
消费次数
# 区分消费1次,和1次以上的情况,以便于计算复购率,大于1为1,等于1为0,等于0为NaN
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
purchase_r.head()
输出结果
(purchase_r.sum()/purchase_r.count()).plot(figsize=(10,4))
每月复购率
  • 复购率稳定在20%左右,前3个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致复购率较低。

2.回购率

# 1代表这个月消费了,0代表没消费
df_purchase.head()
# 使用函数判断是否回购,这里定义的回购指当月消费过的用户下个月也消费了
def purchase_back(data):
    # 判断每一个月是否是回购,根据下个月是否购买来判断
    status=[]
    for i in range(17):
        # 本月消费
        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)
    # 第18个月补充NaN,因为没有下个月的数据了
    status.append(np.NaN)
    return status
# 一行行传递,返回的0代表当月消费下个月未消费,1代表当月消费下个月仍消费,NaN代表当月未消费
purchase_b = df_purchase.apply(lambda x :pd.Series(purchase_back(x),index = indexs),axis =1)
purchase_b.head()
# 回购率,回购的次数/总购买次数
(purchase_b.sum()/purchase_b.count()).plot(figsize=(10,4))
每月回购率折线图

-回购率稳定在30%左右,前3个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致回购率较低。

四、小结

1、用户消费趋势(每月)方面,前3个月有大量新用户涌入,消费金额、消费订单数、产品购买量均达到高峰,后续每月较为稳定。前3个月消费次数都在10000笔左右,后续月份的平均2500;前3个月产品购买量达到20000甚至以上,后续月份平均7000;前3个月消费人数在8000-10000之间,后续月份平均2000不到。

2、用户个体消费方面,小部分用户购买了大量的CD,拉高了平均消费金额。消费金额绝大部分呈现集中趋势,集中在0~200元左右。产品购买量绝大部分呈现集中趋势,大部分购买CD数20张内;用户消费行为基本符合二八定律,50%的用户仅贡献了15%的消费额度,70%的用户仅贡献了20%的消费额度,85%的用户仅贡献了40%的消费额度,而排名前3000多的用户就贡献了60%的消费额度,就能完成 60% 的KPI。

3、用户消费行为方面,首购和最后一次购买的时间,集中在前三个月,说明很多用户购买了一次后就不再进行购买。而且最后一次购买的用户数量也在随时间递增,消费呈现流失上升的状况。

4、从整体消费记录来看,有一半的用户,只消费了一次。从每月新用户占比来看,1997年1月新用户占比高达90%以上,后续有所下降,1997年4月到1998年6月维持在81%左右,1998年6月以后无新用户。

5、从RFM模型来看,在8种客户中,重要保持客户的消费频次和消费金额最高,人数排在第二位;而一般发展客户消费频次和消费金额排第二位,人数却是最多。

6、从用户分层情况来看,新用户从第4月份以后没有新增;活跃用户有所下降;回流用户数量趋于稳定,每月1000多。流失/不活跃用户,数量非常多,基本上每月都在20000以上。

7、用户购买周期方面,平均购买周期是68天,最小值0天,最大值533天。绝大部分用户的购买周期都低于100天。

8、用户生命周期方面,由于只购买一次的用户(生命周期为0天)占了接近一半,排除这部分用户的影响之后,用户平均生命周期276天,中位数302天。

9、复购率和回购率方面,复购率稳定在20%左右,回购率稳定在30%左右,前3个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致复购率和回购率都比较低。

你可能感兴趣的:(CD案例分析)