CD网站用户消费行为分析

本文参考自秦路老师《如何七周成为数据分析师》的第二十三篇教程:https://zhuanlan.zhihu.com/p/27910430
作为练习,我在模仿的基础上进行修改,整理出了分析思路,并添加了RFM模型对用户进行分类。

概述

为了提升CD网站的盈利水平,从运营角度出发,对网站18个月内的订单信息进行分析,得出各项运营指标;根据消费模型对用户进行分层分类,便于提供有针对性的管理与维护。

分析思路

第一部分:数据整理、清洗
第二部分:数据整体描述

订单与用户的构成、销量/销售额/消费人数/订单数随时间变化

第三部分:消费行为分析

用户生命周期、用户生命周期占比、复购率、回购率、用户分层、留存率、用户购买周期

第四部分:采用RFM模型分析用户价值

1. 数据整理、清洗

1.1 导入工具包以及数据

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
%matplotlib inline
plt.style.use('ggplot') #ggplot是R语言风格的绘图包
columns = ['user_id','order_dt','order_products','order_amount'] #命名列
df = pd.read_table("CDNOW_master.txt",names = columns,sep = '\s+') #导入数据
  • 对数据进行观察,可以看到数据比较干净,没有空值
df.info()
Pic.1.1
df.head()
Pic.1.2
#数据转换order_dt
df['order_date']=pd.to_datetime(df['order_dt'],format='%Y%m%d')
df['day']=df['order_date'].astype('datetime64[D]')
df['month']=df['order_date'].astype('datetime64[M]')
df['year']=df['order_date'].astype('datetime64[Y]')
  • 对于日期字段,需要调用to_datetime函数将字符串信息采用条件格式转换成日期信息
  • astype函数可以将日期信息的精度转化为年、月、日,分别对应‘datetime64[Y]’,‘datetime64[M]’,‘datetime64[D]’。

1.2 数据概述

1.2.1 订单数据概述

df.describe()
Pic.1.3
print('金额为0的订单=')
print(df[df.order_amount==0].count())
print('数量极大的订单=')
print(df[df.order_products>50])
Pic.1.4
  • 总量:共有69659条订单数据,有23570名客户,总销量为167881件,总销售额为2500315.63
  • 均值:每笔订单平均购买2.41件商品,笔单价35.89
  • 最大、最小值:每笔订单最多购买99件商品、最少购买1件;每笔订单最多金额为1286.01、最少金额为0.00
  • 分位数:对于订单数量,75%分位数、25%分位数分别是3、1,对比极值,说明绝大多数订单都是小额订单;对于订单金额,大体上也集中在小额订单
  • 异常值:金额为0的订单,共80笔,每笔1件,可能是优惠促销活动的结果;数量极大的订单有5笔,数量分别为55/56/63/70/99

1.2.2 用户数据概述

user_grouped=df.groupby('user_id').agg({'order_products':'sum','order_amount':'sum','order_dt':'count'})
user_grouped.describe()
Pic.1.5
  • 均值:每人平均消费2.96次,平均购买7.12件商品,客单价106.08
  • 最大、最小值:
    消费次数上,最少的用户买了1次,最多的买了217次
    消费金额上,最少的消费了0(可能是促销活动白嫖党),最多的消费了13990.93
    商品数量上,最少的买了1件,最多的买了1033件

2. 数据整体描述

2.1 订单与用户的构成

通过以下几个方面观察订单与用户质量

  • 订单、用户的消费金额和购买量分布(散点图)
  • 订单、用户的消费金额分布(柱状图)
  • 订单、用户的消费金额累计图(分别考虑消费金额与次数的累计图)
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(12,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
df.plot.scatter(x='order_products',y='order_amount',ax=ax[0],title='订单分布')
df.groupby(by='user_id').sum().plot.scatter(x='order_products',y='order_amount',ax=ax[1],title='用户分布')
Pic.2.1
  • 可以看到两张图规律性比较强,消费金额与商品数量呈线性关系,单品均价大概在10左右,这是因为CD网站商品种类单一。
  • 绝大多数的订单都在40件以下,大部分用户购买总数在200件以下,只有少量的大单交易。
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(12,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
df.order_amount.hist(bins=20,ax=ax[0])
ax[0].set_title('订单分布');ax[0].set_xlabel('订单金额')
df.groupby(by='user_id').sum().order_amount.hist(bins=20,ax=ax[1])
ax[1].set_title('用户分布');ax[1].set_xlabel('消费金额')
Pic.2.2
  • 与散点图第二条结论相近,大部分订单购买商品较少,毕竟CD属于可选消费,一次性需求量不大;用户的消费能力普遍较低,高消费档次的用户在图上几乎不可见。
fig,ax=plt.subplots(nrows=1,ncols=3,figsize=(16,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
#订单累计金额
cumsum_order=df.sort_values(by='order_amount',ascending=True).order_amount.cumsum().reset_index(drop=True)
cumsum_order=cumsum_order/cumsum_order.max()
cumsum_order.plot(ax=ax[0])
ax[0].set_title('订单分布');ax[0].set_xlabel('订单金额排名')
#用户累计金额
cumsum_user=df.groupby(by='user_id').sum().sort_values(by='order_products',ascending=True).order_amount.cumsum().reset_index(drop=True)
cumsum_user=cumsum_user/cumsum_user.max()
cumsum_user.plot(ax=ax[1])
ax[1].set_title('用户分布');ax[1].set_xlabel('用户消费金额排名')
#订单累计销售额,按照消费次数
cumsum_user_freq=df.groupby(by='user_id').agg({'order_dt':'count','order_amount':'sum'}).sort_values(by='order_dt',ascending=True).reset_index(drop=True).order_amount.cumsum()
cumsum_user_freq=cumsum_user_freq/cumsum_user_freq.max()
cumsum_user_freq.plot(ax=ax[2])
ax[2].set_title('用户分布');ax[2].set_xlabel('用户消费次数排名')
Pic.2.3

按订单、用户金额升序排列,做出累计图,可以看到:

  • 订单上,消费金额排名前20000(前28%)的订单构成了60%的销售额,排名前33000(前47%)的订单构成了80%的销售额,说明大额订单对消费金额贡献较大,需要重点关注
  • 用户上,消费金额排名前5000(前25%)的用户构成了60%的销售额,排名前10000(前40%)的用户构成了80%的销售额,说明在运营过程中,重点关注前25%-40%的客户即可完成60%-80%的KPI,能够保证产出投入比

按消费次数升序排列,做出累计图,同样可以看到:

  • 消费次数高的用户销售额贡献大,消费次数排名前5000(前25%)的用户贡献了约55%的销售额,消费次数排名前15000(前40%)的用户贡献了约75%的销售额,说明老客/回头客的销售额贡献比较大

综上,可以得出初步结论,网站主要营收来自于大额订单、高消费额度、高消费频次的用户,这类订单/用户需要重点维护。

2.2 销量/销售额/消费人数/订单数随时间变化

这里主要观察销量、销售额、消费人数、订单数随时间变化的趋势,下面以月为时间窗口进行聚合

#分别按照年月日观察消费总金额,销量,消费人数,订单数
freq='month'
TotalAmount=df.groupby(by=freq).order_amount.sum() #总销售额
ProductNum=df.groupby(by=freq).order_products.sum() #商品销量
UserNum=df.groupby(by=freq).user_id.unique().apply(len) #消费人数
OrderNum=df.groupby(by=freq).user_id.count() #订单数
def plotUserAmount(TotalAmount,ProductNum,UserNum,OrderNum,freq):
    fig,ax=plt.subplots(nrows=2,ncols=2,figsize=(12,9))
    plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
    #更改默认字体
    plt.rcParams['font.sans-serif']=['SimHei']
    plt.rcParams['axes.unicode_minus']=False
    #绘图
    TotalAmount.plot(ax=ax[0,0],title='销售额')#,kind='bar')
    ProductNum.plot(ax=ax[0,1],title='销量')#,kind='bar')
    UserNum.plot(ax=ax[1,0],title='消费人数')#,kind='bar')
    OrderNum.plot(ax=ax[1,1],title='订单数')#,kind='bar')
plotUserAmount(TotalAmount,ProductNum,UserNum,OrderNum,freq)
Pic.2.4

可以看到,销售额、销量、消费人数、订单数量变化趋势几乎一致,在前三个月,四者数量相当高,随后趋于稳定,可能原因是:

  • 用户异常数据集中在前三个月
  • 前三个月有拉新/促销优惠活动,增加了用户数,消费总量提升

剔除单笔订单在20件以上的数据,再次作图,可得下图

threhold=20
TotalAmount1=df[df.order_products<threhold].groupby(by=freq).order_amount.sum() #总销售额
ProductNum1=df[df.order_products<threhold].groupby(by=freq).order_products.sum() #商品销量
UserNum1=df[df.order_products<threhold].groupby(by=freq).user_id.unique().apply(len) #消费人数
OrderNum1=df[df.order_products<threhold].groupby(by=freq).user_id.count() #订单数
plotUserAmount(TotalAmount1,ProductNum1,UserNum1,OrderNum1,freq)
Pic.2.5
  • 剔除单笔订单在20件以上的数据后,结果并无太大变化,说明大笔订单数量较小,对于整体销量变化趋势影响小,前三个月的异常数据并不是由大笔订单引起的,更可能是用户拉新/优惠促销导致的结果

3. 消费行为分析

消费行为分析主要从以下几点考量:

  • 用户生命周期
  • 复购率、回购率、留存率
  • 用户分层:新客、老客、流失客户、回流客户
  • 用户价值评估:RFM模型

3.1 用户生命周期

为了计算生命周期,首先采用min,max函数计算每名用户第一次消费与最后一次消费的时间点,再根据第一次/最后一次消费的时间点做出柱状图

#用户数据
df_consumer=df.groupby(by='user_id').agg({'order_products':'sum','order_amount':'sum','order_dt':'count'})
df_consumer['first_consume']=df.groupby(by='user_id').day.min()
df_consumer['last_consume']=df.groupby(by='user_id').day.max()
df_consumer['life_time']=df_consumer['last_consume']-df_consumer['first_consume']
#作图
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(12,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
#第一次消费
df_first_consume_num=df_consumer.first_consume.astype(str).map(lambda x: x[0:7]).value_counts().sort_index()
df_first_consume_num.index=df_first_consume_num.index.astype(str).map(lambda x: x[0:7])
df_first_consume_num.plot.bar(ax=ax[0],title='第一次消费')
#最后一次消费
df_last_consume_num=df_consumer.last_consume.astype(str).map(lambda x: x[0:7]).value_counts().sort_index()
df_last_consume_num.index=df_last_consume_num.index.astype(str).map(lambda x: x[0:7])
df_last_consume_num.plot.bar(ax=ax[1],title='最后一次消费')
Pic.3.1
  • 数据比较集中,用户第一次消费集中在前三个月(1997-01,02,03),最后一次消费时间点比较多,但在前三个月有大量用户流失。
df_consumer['life_time'].describe()
Pic.3.2
  • 用户平均生命周期为134天,最长生命周期达到544天,最短生命周期为0天,但中位数只有0天,这说明存在大量用户只消费了一次
#作图
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(12,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
#用户生命周期
df_lifetime=(df_consumer.life_time/np.timedelta64(1,'D'))
df_lifetime.hist(bins=50,ax=ax[0])
ax[0].set_title('用户生命周期分布');ax[0].set_xlabel('用户生命周期/天')
#用户生命周期去除0
df_lifetime[df_lifetime>0].hist(bins=50,ax=ax[1])
ax[1].set_title('用户生命周期分布(除只购买了一次的)');ax[1].set_xlabel('用户生命周期/天')
Pic.3.3
  • 作出用户生命周期柱状图,可以看到,有大量用户生命周期为0,仅仅消费了了一次。
  • 排除这些质量较低用户,再次作图,可以发现,生命周期呈现双峰趋势,第一个峰趋近于0,说明大部分用户只消费了2、3次。另一个峰出现在400-500天以内,这些用户的忠诚度较高。
df_lifetime[df_lifetime>0].describe()
  • 剔除生命周期为0的用户后,平均生命周期由134天达到了276天,有了极大的提升。

3.2 不同生命周期的用户占比

按照bin=[0,1,100,200,400,1000],采用cut函数,对用户生命周期进行划分,作出各生命周期用户人数与消费总额占比

df_consumer['life_time_float']=df_consumer['life_time']/np.timedelta64(1,'D')
#按照用户生命周期进行划分
bins=[0,1,100,200,400,1000]
df_consumer['life_time_label']=pd.cut(df_consumer.life_time_float,bins=bins,right=False)
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(16,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
df_lifetime_group=df_consumer.groupby(by='life_time_label').agg({'life_time_label':'count','order_amount':'sum'})
df_lifetime_group.life_time_label.plot.pie(ax=ax[0],autopct='%1.1f%%',startangle=0)
ax[0].set_ylabel('生命周期/天');ax[0].set_title('用户生命周期占比')

df_lifetime_group.order_amount.plot.pie(ax=ax[1],autopct='%1.1f%%',startangle=0)
ax[1].set_ylabel('生命周期/天');ax[1].set_title('消费总额占比')
Pic.3.4
  • 生命周期为0,即只消费了一次的用户占51.1%,但销售额仅贡献了14.7%,这部分用户具有较大的发展价值,可以通过一定引导手段将其发展成老客,这将带来近1.8倍(9.1/11.1/(14.7/51.1))的价值提升。
  • 生命周期>=400的用户,即忠诚用户,占总用户数量15.7%,占老客数量32.1%,但贡献了45.3%的消费总额,需要重点维护。

3.3 复购率

计算复购率,需要明确定义:在某时间窗口内消费两次及以上的用户在总消费用户中占比。这里以月为时间窗口进行计算,采用pivot_table建立数据透视表,以用户id为index,时间为columns,有几点需要注意的:1.applymap函数对每一个元素应用,而apply函数对列/行应用;2.count函数计数时,会计及所有非空值;3.sum函数求和时自动忽略空值nan

fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(16,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
#数据透视,index为用户,columns为月份
df_Retention=pd.pivot_table(index='user_id',columns='month',data=df,values='order_dt',aggfunc='count')
df_Retention.columns=df_Retention.columns.astype(str).map(lambda x: x[0:7])
def countNum(x):
    if(x==1):
        return 0
    elif(x>1):
        return 1
    else:
        return np.nan
df_Retention=df_Retention.applymap(countNum)
df_Retention_True=df_Retention.apply('sum',axis=0) #复购的人数
df_Retention_All=df_Retention.apply('count',axis=0) #总消费的人数
df_Retention_Ratio=df_Retention_True/df_Retention_All
df_Retention_Ratio.index=df_Retention_Ratio.index.astype('datetime64[M]')
df_Retention_Ratio.plot(ax=ax[0])
ax[0].set_title('复购率')

#除去只消费了一次的用户index
consumer0=list(df_consumer[df_consumer.life_time_float!=0].index)
df_Retention0=df_Retention.loc[consumer0,:]
df_Retention_True=df_Retention0.apply('sum',axis=0) #复购的人数
df_Retention_All=df_Retention0.apply('count',axis=0) #总消费的人数
df_Retention_Ratio=df_Retention_True/df_Retention_All
df_Retention_Ratio.index=df_Retention_Ratio.index.astype('datetime64[M]')
df_Retention_Ratio.plot(ax=ax[1])
ax[1].set_title('复购率(剔除只消费一次的用户)')
Pic.3.5
  • 在前期三个月,复购率呈现上升趋势,最初只有6%左右,随后稳定在21%左右。
  • 最初的上涨可以解释为:新增用户主要集中在前三个月,其中大部分人只消费了一次,基数变大复购率因此降低。剔除只消费一次的用户后,老客的复购率稳定大约在0.21左右。

3.4 回购率

同样需要明确回购率的定义:某一个时间窗口内消费的用户,在下一个时间窗口仍旧消费的占比。这里需要说明的是,对透视表每列运用apply函数时,需要保证函数返回的是Series对象,而不是List对象。

fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(16,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
df_BuyBack=df_Retention.replace(to_replace=0,value=1.0).fillna(0)
#计算复购率函数
def BuyBack(x):
    x_new=x.copy()#保证函数返回的是Series对象,而不是List对象。
    for i in range(len(x)-1):
        if(x[i]==1):
            if(x[i+1]==1):
                x_new[i]=1
            elif(x[i+1]==0):
                x_new[i]=0
        else:
            x_new[i]=np.nan
    x_new[len(x)-1]=np.nan
    return x_new
df_Return=df_BuyBack.apply(BuyBack,axis=1)
Return_Ratio=df_Return.sum(axis=0)/df_Return.count(axis=0)
Return_Ratio.index=Return_Ratio.index.astype('datetime64[M]')
Return_Ratio.plot(ax=ax[0])
ax[0].set_title('回购率')

#除去只消费了一次的用户index
df_Return0=df_Return.loc[consumer0,:]
Return_Ratio0=df_Return0.sum(axis=0)/df_Return0.count(axis=0)
Return_Ratio0.index=Return_Ratio0.index.astype('datetime64[M]')
Return_Ratio0.plot(ax=ax[1])
ax[1].set_title('回购率(剔除只消费一次的用户)')
Pic.3.6
  • 在前期三个月,回购率呈现上升趋势,最初只有15%左右,随后稳定在30%左右。
  • 与复购率相同,回购率的上涨也是因为前期新用户的增加。剔除只消费一次的用户后,老客的回购率稳定大约在0.30左右。

复购率与回购率的计算结果说明老客的忠诚度与消费频次优于新客。如果剔除只消费一次的新客,用户的整体质量将有较大提升

3.5 用户分层

计算用户分层的函数比较复杂,基本逻辑是:判断本月是否有消费;若有,根据上月用户消费状况,判别用户是新用户/活跃用户/回流用户;若无,根据上月用户状态,判断用户是未激活用户/不活跃用户。

def active_status(data):
    data_new=[]
    for i in range(len(data)):
        #本月无消费
        if(data[i]==0):
            if(len(data_new)==0):
                data_new.append('unreg')
            else:
                if(data_new[i-1]=='unreg'):
                    data_new.append('unreg')
                else:
                    data_new.append('unactive')
        #本月有消费
        if(data[i]!=0):
            if(len(data_new)==0):
                data_new.append('new')
            else:
                if(data_new[i-1]=='unreg'):
                    data_new.append('new')
                elif(data_new[i-1]=='unactive'):
                    data_new.append('return')
                else:
                    data_new.append('active')
    data_Series=pd.Series(data_new)
    data_Series.index=data.index
    return data_Series
BuyerStatus=df_BuyBack.apply(active_status,axis=1)
BuyerStatus.head()
Pic.3.7
BuyerStatus1=BuyerStatus.replace('unreg',np.nan)
BuyerStatus1.columns=BuyerStatus1.columns.astype('datetime64[M]')
BuyerStatus1.apply(lambda x:pd.value_counts(x),axis=0).T.plot.area()
Pic.3.8
  • 只有前三个月有新客,因为数据集跟踪的是前三个月新客在之后一段时间内的消费行为
  • 三个月以后,用户构成比较稳定,主要是活跃用户与回流用户,分别对应红色与紫色区域。

进一步计算活跃用户与回流用户占比,可得

#活跃用户与回流用户占比
fig,ax=plt.subplots(nrows=1,ncols=2,figsize=(16,4))
plt.subplots_adjust(left=None, bottom=None, right=None, top=None,
                    wspace=None, hspace=0.3)
#活跃用户
BuyerComponentRatio=BuyerComponent.apply(lambda x: x/x.sum(),axis=1)
ax[0].set_title('活跃用户占比')
BuyerComponentRatio.loc[:,'active'].plot(ax=ax[0])
#回流用户
ax[1].set_title('回流用户占比')
BuyerComponentRatio.loc[:,'return'].plot(ax=ax[1])
Pic.3.9
  • 活跃用户占比逐步降低,最后稳定在2%左右,这部分用户质量较高,能够保持连续购买。
  • 回流用户占比大约在5%上下波动。

3.6 留存率

留存的概念相比复购、回购更简单,只要在某段时间内消费过,就意味着该名用户留存。计算时,先计算出用户消费距离第一次消费的间隔,再采用透视表pivot_table的形式,记录用户在某个时间区间内是否消费。

#计算用户消费距离第一次间隔时间
df_consumer_retention=pd.merge(df_consumer[['first_consume']],df[['user_id','order_date','order_products','order_amount']]\
                               ,left_index=True,right_on='user_id')
df_consumer_retention['order_date_diff']=df_consumer_retention.order_date-df_consumer_retention.first_consume
df_consumer_retention['date_diff']=df_consumer_retention['order_date_diff']/np.timedelta64(1,'D')
#划分间隔时间区间
bins=[0,3,7,15,30,60,90,180,365]
df_consumer_retention['date_diff_bin']=pd.cut(df_consumer_retention['date_diff'],bins=bins)
df_consumer_retention.head()
pivoted_retention=pd.pivot_table(df_consumer_retention,values='order_amount',index='user_id',\
                                 columns='date_diff_bin',aggfunc='sum',dropna=False)
pivoted_retention.mean()
#建立透视表,记录用户在哪个时间区间内有消费
pivoted_retention_trans=pivoted_retention.applymap(lambda x:1 if x>0 else 0)
pivoted_retention_trans.head()
Pic.3.10

进一步计算每个区间内留存用户的占比,可以发现

(pivoted_retention_trans.sum(axis=0)/pivoted_retention_trans.count(axis=0)).plot.bar(color='r')
Pic.3.11
  • 只有2.5%的用户在第一次消费的次日至3天内有过消费,3%的用户在3~7天内有过消费。短期回购的用户较少,因为CD购买并不是高频消费行为。
  • 在第一次消费后的3个月到半年之间,有20%的用户再次消费,27%的用户在半年后至1年内再次消费。

3.8 用户购买周期

购买周期需要计算相邻两次消费的时间,采用shift(-1)形成位错,对用户进行分组聚合即可得到时间差分即每名用户两次购买的时间间隔。

def diff(group):
    d=group.date_diff-group.date_diff.shift(-1)
    return d
last_diff=df_consumer_retention.groupby(by='user_id').apply(diff)
last_diff.mean()
Pic.3.12
last_diff.hist(bins=20)
Pic.3.13
  • 用户平均消费间隔时间是68天。想要召回用户,在60天左右的消费间隔是比较好的。
  • 用户的消费间隔呈现长尾分布,大部分用户的消费间隔确实比较短。可以将时间召回点设为消费后立即赠送优惠券,消费后10天询问用户CD怎么样,消费后30天提醒优惠券到期,消费后60天短信推送。

4. 采用RFM模型分析用户价值

RFM模型从三个角度分析用户价值,R代表用户最近一次消费时间,F代表用户消费频率,M代表用户的消费总额

df_RFM=df_consumer.reset_index()[['user_id','last_consume','order_dt','order_amount']]
df_RFM['last_consume']=(df_RFM['last_consume'].max()-df_RFM['last_consume'])/np.timedelta64(1,'D') #将最近一天作为当前的天数
#R:last_consume F:order_dt M:order_amount
from sklearn import preprocessing
#归一化
X=preprocessing.minmax_scale(df_RFM[['last_consume','order_dt','order_amount']])
df_RFM[['R_value','F_value','M_value']]=pd.DataFrame(X,columns=['R_value','F_value','M_value'])
#评分
df_RFM['RFM_Total']=(-df_RFM['R_value']+df_RFM['F_value']+df_RFM['M_value'])*100+100
  • 将R、F、M三个数值进行min-max归一化,投影到[0,1]内。
  • 计算用户价值评分时,采用以下公式: RFM总值 = [R值 *(-1)+F值+M值] *100+100
#df_RFM.sort_values(by='RFM_Total',ascending=False)
#计算1/4分位数
R_median=df_RFM.sort_values(by='RFM_Total',ascending=False).describe().loc['25%','R_value'] #R median
F_median=df_RFM.sort_values(by='RFM_Total',ascending=False).describe().loc['75%','F_value'] #F median
M_median=df_RFM.sort_values(by='RFM_Total',ascending=False).describe().loc['75%','M_value'] #M median
#以上四分位数定义R/F/M的高、低,从而判断群体类型
df_RFM['R_label']=df_RFM['R_value'].apply(lambda x:'1' if x<R_median else '0')
df_RFM['F_label']=df_RFM['F_value'].apply(lambda x:'1' if x>F_median else '0')
df_RFM['M_label']=df_RFM['M_value'].apply(lambda x:'1' if x>M_median else '0')
df_RFM['RFM_label']=df_RFM['R_label']+df_RFM['F_label']+df_RFM['M_label']
#将标签映射成中文
dictValue={'111':'重要价值用户','011':'重要保持用户','101':'重要发展用户',
        '001':'重要挽留用户','110':'一般价值用户','010':'一般保持用户',
        '100':'一般发展用户', '000':'一般挽留用户'}
dictValue1={'111':'A','011':'B','101':'A',
        '001':'B','110':'B','010':'C',
        '100':'B', '000':'C'} #成交客户等级
df_RFM['RFM_label_chi']=df_RFM['RFM_label'].map(dictValue)
df_RFM['RFM_Deal_Rank']=df_RFM['RFM_label'].map(dictValue1)
  • 在划分用户等级时,需要界定R、F、M值的高低。由于数据大量集中于低频、低额区域,采用平均数、中位数难以反映数据分布特点。在此采用四分位数进行界定,F、M以上四分位数为界,R值以下四分位数为界,这是因为R值越小用户价值越高。
  • 根据RFM的高低,将用户划分为8类:重要价值用户(111),重要保持用户(011),重要发展用户(101),重要挽留用户(001),一般价值用户(110),一般保持用户(010),一般发展用户(100),一般挽留用户(000)。
df_RFM.groupby('RFM_label_chi').count()['user_id'].plot.pie(autopct='%1.1f%%',colors=['#FF00FF','b','#A9A9A9','r',\
                                                                    '#FFC0CB','#FFFF00','#87CEEB','#E9967A'])
Pic.4.1
df_RFM[['user_id','last_consume','order_dt','order_amount','RFM_label_chi']].groupby('RFM_label_chi').\
    agg({'user_id':'count','last_consume':'mean','order_dt':'mean','order_amount':'mean'})
Pic.4.2

做出饼图,并通过聚合求出用户距离上次消费时间均值,平均下单量以及平均消费金额,可以发现:

  • 最典型的是两类用户是:一般挽留用户(000)与重要价值用户(111)用户,
    • 前者(000)占比最高,反映出CD销售行业的特点,大部分用户消费频率低、金额较小以及最近一次消费比较久远;
    • 后者(111)占比次高,这些用户是CD发烧友,消费频率高、金额大且最近一次消费比较近。
  • 在运营时,针对不同用户,需要采取不同的措施。如针对重要挽留用户,可以通过短信/邮件形式发放大额度电子优惠券,对用户进行召回;针对重要发展用户,可以让他们关注公众号,及时获取一些促销活动信息,从而提升用户消费次数,培养用户粘性。
#再划分成交用户等级,重要价值/重要发展用户等级为A,一般保持/一般挽留用户等级为C,重要保持/重要挽留/一般发展/一般价值用户等级为B
df_value=df_RFM.groupby('RFM_Deal_Rank').\
    agg({'user_id':'count','last_consume':'mean','order_dt':'sum','order_amount':'sum'})
df_value
Pic.4.3
  • 再划分成交用户等级,重要价值/重要发展用户等级为A,一般保持/一般挽留用户等级为C,重要保持/重要挽留/一般发展/一般价值用户等级为B。A、B、C分别对应的是用户价值高、中、低三档。
fig,ax=plt.subplots(1,2)
df_value.user_id.plot.pie(autopct='%1.1f%%',ax=ax[0])
ax[0].set_title('用户人数占比')
df_value.order_amount.plot.pie(autopct='%1.1f%%',ax=ax[1])
ax[1].set_title('用户消费额贡献占比')
Pic.4.4

可以看到:

  • A类用户,人数上只占16.2%,但贡献了55.6%的销售额
  • B类用户,在人数上与A类相当约17.5%,在销售额上,与C类用户贡献相当,约21.3%
  • 在用户价值上,可以近似认为,A类用户=2.6倍B类用户=10.7倍C类用户

5. 总结

本次分析得出以下结论:
1. 数据整体描述

  • 总量:共有69659条订单数据,有23570名客户,总销量为167881件,总销售额为2500315.63
  • 均值:每笔订单平均购买2.41件商品,笔单价35.89;每位用户平均消费2.96次,平均购买7.12件商品,客单价106.08
  • 极值:每笔订单最多购买99件商品、最少购买1件,最多金额为1286.01、最少金额为0.00;每位用户最少的买了1次,最多的买了217次,最少消费了0,最多的消费了13990.93
  • 特点:绝大多数订单为小额订单,有个别超大额度订单。部分订单金额为0,可能是拉新促销的原因

2. 订单与用户整体质量

  • a.订单消费金额与商品数量呈线性关系,单品均价大概在10左右,CD网站商品种类单一
  • b.绝大多数的订单都在40件以下,大部分用户购买总数在200件以下,只有少量的大单交易
  • c.数据呈现二八分布特点,消费金额较高、消费次数多的用户虽然人数少(占25%-40%),但贡献了较大的营收(占总额60%-80%),网站主要营收来自于大额订单、高消费额度、高消费频次的用户,这类订单/用户需要重点维护
  • d.月度数据分析
    • 1).销量、销售额、消费人数、订单数在前三个月显著增加,之后随时间逐渐减少,趋于稳定。这是因为数据观察的是前三个月新增用户的后续消费情况。前三个月很可能有拉新/促销优惠活动,增加了用户数,消费总量提升,随后统计量趋于稳定是因为用户经过筛选,只留存了忠诚度较高的
    • 2).剔除大额订单后,四种统计量无明显变化。这是因为大额订单占比极少,对月度时间序列影响较小

3. 消费行为分析

  • a.用户生命周期
    • 1).第一次消费:用户第一次消费集中在前三个月(1997-01,02,03)
    • 2).最后一次:时间点比较多,但在前三个月有大量用户流失
    • 3).用户平均生命周期为134天,最长生命周期达到544天,最短生命周期为0天,但中位数只有0天,这说明存在大量用户只消费了一次
    • 4).剔除只消费一次的用户,生命周期均值可达276天
    • 5).用户生命周期呈现双峰趋势,第一个峰趋近于0,说明大部分用户只消费了2、3次。另一个峰出现在400-500天以内,这些用户的忠诚度较高
    • 6).生命周期>=400的用户,即忠诚用户,占总用户数量15.7%,占老客数量32.1%,但贡献了45.3%的消费总额
  • b.复购率
    • 1).在前期三个月,复购率呈现上升趋势,最初只有6%左右,随后稳定在21%左右
    • 2).复购率最初的上涨可以解释为:新增用户主要集中在前三个月,其中大部分人只消费了一次,基数变大复购率因此降低。剔除只消费一次的用户后,老客的复购率稳定大约21%左右
  • c.回购率
    • 1).在前期三个月,回购率呈现上升趋势,最初只有15%左右,随后稳定在30%左右
    • 2).与复购率相同,回购率的上涨也是因为前期新用户的增加。剔除只消费一次的用户后,老客的回购率稳定大约在0.30左右
  • d.留存率
    • 1).只有2.5%的用户在第一次消费的次日至3天内有过消费,3%的用户在3~7天内有过消费。短期回购的用户较少,因为CD购买并不是高频消费行为。
    • 2). 在第一次消费后的3个月到半年之间,有20%的用户再次消费,27%的用户在半年后至1年内再次消费。
  • e.用户消费间隔
    • 1).用户平均消费间隔时间是68天。想要召回用户,在60天左右的消费间隔是比较好的
    • 2).用户的消费间隔呈现长尾分布,大部分用户的消费间隔确实比较短。可以将时间召回点设为消费后立即赠送优惠券,消费后10天询问用户CD怎么样,消费后30天提醒优惠券到期,消费后60天短信推送
  • f.用户分层:新客、老客、流失客户、回流客户
    • 1).只有前三个月有新客,因为数据集跟踪的是前三个月新客在之后一段时间内的消费行为
    • 2).三个月以后,用户构成比较稳定,主要是活跃用户与回流用户

4.用户价值评估:RFM模型

  • a.按照最近一次消费R,消费频次F,消费总额M,以四分位数为界,对用户进行分层
  • b.用户中最典型、数量最多的是两类用户是:一般挽留用户(000)与重要价值用户(111)用户,前者(000)占比最高,反映出CD销售行业的特点,大部分用户消费频率低、金额较小以及最近一次消费比较久远;后者(111)占比次高,这些用户是CD发烧友,消费频率高、金额大且最近一次消费比较近。
  • c.在运营时,针对不同用户,需要采取不同的措施。如针对重要挽留用户,可以通过短信/邮件形式发放大额度电子优惠券,对用户进行召回;针对重要发展用户,可以让他们关注公众号,及时获取一些促销活动信息,从而提升用户消费次数,培养用户粘性。
  • d.再划分成交用户等级,重要价值/重要发展用户等级为A,一般保持/一般挽留用户等级为C,重要保持/重要挽留/一般发展/一般价值用户等级为B,可得:用户价值上,近似有,A类用户=2.6倍B类用户=10.7倍C类用户
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352