项目背景:
对一家公司18个月的销售记录进行了用户消费行为的相关分析。
分析流程如下:
0.数据预处理
import pandas as pd
import numpy as np
columns=['user_id','order_dt','order_products','order_amount']
df=pd.read_table(r'Data\单车项目\bicycle_master.txt',names=columns, sep='\s+')
# 数据清洗
df['order_dt']=pd.to_datetime(df.order_dt,format="%Y%m%d")
df['month']=df.order_dt.astype('datetime64[M]') #转换类型为datetime64[M],默认就会是每月的第一天了
数据结果如下:
- user_id:用户ID
- order_dt:购买日期
- order_products:购买产品数
- order_amount:购买金额
1.进行用户消费趋势的分析(按月)
- 以下维度展开
- 每月的消费总金额
- 每月的消费次数
- 每月的产品购买量
- 每月的消费人数
grouped_month = df.groupby('month')
grouped_month_info = grouped_month[['order_amount','user_id','order_products']].agg({'order_amount':'sum', 'user_id': 'count', 'order_products':sum})
grouped_month_info.rename(columns = {'order_amount':'消费金额', 'user_id': '消费次数', 'order_products': '产品购买量'}, inplace=True)
grouped_month_info['消费人数'] = grouped_month['user_id'].unique().map(lambda x:len(x)) #nunique()也可,去重统计user_id
grouped_month_info = grouped_month_info.reset_index()
grouped_month_info['month'] = grouped_month_info['month'].astype(str)
利用tableau构造dashboard,观看整体趋势。
从上图可看出:
- 消费额、产品销量、消费人次和消费次数折线图基本类似,都是前三个月较高,到底三个月达到顶峰,随后骤降在较低值趋于稳定,但整体仍有轻微下降趋势。
- 初步猜测前三个月存在某种推广营销活动,使大量流量涌入,但后续并没有进行有效的后续活动导致用户粘性降低,留存率不高。
2.用户个体消费分析
- 用户消费金额和产品购买量的散点图
- 用户消费金额的分布图
- 用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)
# 用户消费金额和产品购买量的散点图
grouped_user=df.groupby('user_id').sum()
grouped_user_sum_order_amount = grouped_user.sum().order_amount
# 用户消费金额的分布图
grouped_user_sum_order_amount_lst = [i for i in range(0,int(grouped_user_sum_order_amount.max())+50,50)]
grouped_user_sum_order_amount = pd.cut(grouped_user_sum_order_amount, bins=grouped_user_sum_order_amount_lst,labels = grouped_user_sum_order_amount_lst[1:])
# 累计消费金额表
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
user_cumsum.reset_index().order_amount
- 销售额与消费产品散点图有极值影响,所以我们去除了总金额大于4000的散点图,由修正后的散点图可知,用户消费金额与产品购买量几乎成线性关系,购买的商品越多,消费金额越大。
- 从直方图可知,用户消费金额,绝大部分呈现集中趋势,小部分异常值干扰了判断,可以使用过滤操作排除异常
使用切比雪夫定理过滤掉异常值,因为切比雪夫定理说明,95%的数据都分布在5个标准差之内,剩下5%的极值就不要了
order_amount (mean = 106 ,std = 241) mean+5std = 1311 - 按照用户消费金额进行升序排序,由图可以知道50%的用户仅贡献了11%的消费额度,而排名前5000的用户就贡献了60%的消费额度
3.用户消费行为
- 3.1 用户第一次消费(首购)
- 3.1 用户最后一次消费
- 3.2 新老客户消费比
- 多少用户仅消费一次
- 每月新客占比
- 3.3 用户分层
- 3.3.1 RFM模型
- 3.3.2 新、老、活跃、回流、流失
- 3.4 用户购买周期(按订单)
- 用户消费周期分布
- 3.5 用户生命周期(按第一次和最后一次消费)
- 用户生命周期分布
3.1 首购分布&最后一次购买分布
# 首购分布
grouped_user_min = grouped_user.min().order_dt.value_counts().reset_index().rename(columns={'index':'first_date'})
grouped_user_min['first_date'] =grouped_user_min['first_date'].astype(str)
# 最后一次购买分布
grouped_user_max = grouped_user.max().order_dt.value_counts().reset_index().rename(columns={'index':'last_date'})
grouped_user_max['last_date'] =grouped_user_max['last_date'].astype(str)
- 首购
- 用户第一次购买的分布详情,主要集中在前三个月。其中,在2月有剧烈的波动。
- 最后一次购买
- 断崖式下跌:可以理解用户流失比例基本一致,一开始用户迅猛增长数量比较多流失的也比较多,后面没有用户大面积流失,处于平衡状态
- 用户最后一次购买的分布比首购分布广
- 大部分最后一次购买,集中在前三个月,说明很多用户购买了一次后就不再进行购买
- 随着时间的递增,最后一次购买数量也在递增,消费呈现流失上升的状况(随着时间的增长,可能运营没有跟上用户的变化,或者用户被别的竞争公司吸引走了)
3.2 新老客户消费比
3.2.1多少用户仅消费一次
# 得到第一次和最后一次消费情况,如果 min、max 日期相同,说明只消费了一次
user_life=grouped_user.order_dt.agg(['min','max'])
user_life_1 = (user_life['min']==user_life['max']).value_counts()
将上表进行可视化展示:
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
plt.figure(figsize=(4,4),dpi=100)
labels = user_life_1.index
x = user_life_1.values
plt.pie(x,labels=labels,autopct='%.0f%%',colors = 'br', startangle = 90 )
plt.legend()
plt.show()
有一半左右的用户,只消费了一次。
3.2.2 每月新客占比
grouped_um = df.groupby(['month','user_id']).order_dt.agg(["min","max"]) #按月分组下的userid分组,求每月的最早购买日期和最晚消费日期
grouped_um["new"] = (grouped_um["min"] == grouped_um["max"] ) # 新增列 True 为 新用户
a = grouped_um.reset_index().groupby("month").new.value_counts()
a = a.unstack() # 多重索引进行unstack()顺时针转化
# 并列柱状图可视化
a.columns = ['老用户','新用户']
plt.figure(figsize=(8,8),dpi=100)
x1 = np.arange(0,18,1)
x = a.index.date
y1 = a.新用户
y2 = a.老用户
plt.bar(x1, y1, color='g', width=0.25, label='新客户')
plt.bar(x1+0.25, y2, color='b', width=0.25, label='老客户')
plt.xticks(x1, x, rotation=-45)
plt.legend()
由上图可以看出前三个月的新客户增长迅速,接下来的13个月趋于稳定,老用户也是在接下来的13个月内稳定不增,基本可以阐述多数用户只购买了一次该产品。
3.3 用户分层
3.3.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':'sum'}) # 总产品数
# 得到最近一次消费,一般是计算距离当天最近一次消费的时间间隔,这里因为时间太久远,就用表格中order_dt的max值
rfm['R']= (rfm.order_dt - rfm.order_dt.max())/np.timedelta64(1,'D')
# 重命名,也就是 R:最后一次消费距今天数,M:消费总金额 ,F:消费总产品数
rfm.rename(columns={'order_products':"F",'order_amount':'M'},inplace=True)
def rfm_func(x):
level=x.apply(lambda x:'1' if x>=0 else '0')
#level=x.map(lambda x:'1' if x>=0 else '0')也可以
# level 的类型是 pd.Series,index 是 R、F、M
label=level.R + level.F + level.M
d={
# R 为1 表示比均值大,购买时间近,F为1 表示 消费金额比较多,M 为1 表示消费频次比较多,所以是重要价值客户
'111':'重要价值客户',
'011':'重要保持客户',
'101':'重要发展客户',
'001':'重要挽留客户',
'110':'一般价值客户',
'010':'一般保持客户',
'100':'一般发展客户',
'000':'一般挽留客户',
}
result=d[label]
return result
# axis=1,传递一行,然后匹配返回一个值
rfm['label']=rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
# 为了简化用户分层,将8分层简化为2分层
rfm.loc[rfm.label == '重要价值客户','color'] = '重要价值客户'
rfm.loc[rfm.label != '重要价值客户','color'] = '非重要价值客户'
rfm表如下:
图表看出重要价值客户和非重要价值客户的分布:
# 精确分层下的用户总数
rfm.groupby('label').count()
- 从RFM 分层可知,大部分用户是一般挽留客户,但是这是由于极值的影响,所以 RFM 的划分标准应该以业务为准,也可以通过切比雪夫去除极值后求均值,并且 RFM 的各个划分标准可以都不一样
- 尽量用小部分的数据覆盖大部分的区间。
- 靠具体的业务去划分用户等级,不要为了数据优异去做反常态的划分
3.3.2 新、老、活跃、回流、流失
pivoted_counts=df.pivot_table(index='user_id',
columns='month',
values='order_dt',
aggfunc='count').fillna(0)
# 有的用户消费了两次,有的用户消费了一次,我们将消费了的设置为1,未消费的设置为0
df_purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0)
df_purchase[:5]
如下表所示:
判断用户分布状况
新用户(new):某一周期前从未发生过购买行为(eg:0,0,1,0)
活跃用户(active):本周期和上周期都发生购买行为,且不属于新客户(eg:0,1,1,0,1,1)
回流用户(return):上周期没买,本周期买了,且不属于新客户(eg:0,1,0,1)
流失用户(unactive):沉寂几个周期未发生购买行为(eg:1,1,0,0,0,)
未注册(unreg):从未购买过(eg:0,0,0,0)
def active_status(data):
status=[]
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
df_purchase.apply(lambda x:pd.Series(active_status(x),index = df_purchase.columns),axis=1)
# 传递一行数据给x后active_status(x)输出列表,再把它包装成Series构成二维表
# 这里把未注册的替换为空值,这样 count 计算时不会计算,最终得到每个月的用户分布
purchase_stats_ct=purchase_stats.replace('unreg',np.nan).apply(lambda x:pd.value_counts(x))
purchase_stats_ct
purchase_stats_ct_info = purchase_stats_ct.fillna(0).T
purchase_stats_ct_info['total_num'] = purchase_stats_ct_info['active'] + purchase_stats_ct_info['new'] + purchase_stats_ct_info['return']+purchase_stats_ct_info['unactive']
purchase_stats_ct_info
- 前三个月有大量新用户涌入,新用户占比很高,而后面几个月不活跃用户占比非常高,普遍在90%以上。
- 持续消费的用户,一直维持在5%左右,对应的是消费运营质量一般。
- 回流用户在5%到8%之间,唤回运营这方面做的是很不错的。
- 流失率是最高的,所以可以加强对活跃用户的福利待遇,提高换回用户的手段。
3.4 用户购买周期
# 计算相邻两个订单的时间间隔,shift 函数是对数据进行错位,所有数据会往下平移一下,所以可以
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
# 将后缀单位去除
order_diff_info = (order_diff/np.timedelta64(1,'D'))
order_diff_cut_lst = [i for i in range(0,int(order_diff_info.max())+1,10)]
order_diff_info_hist = pd.cut(order_diff_info,bins=order_diff_cut_lst,labels=order_diff_cut_lst[1:]).fillna(10)
- 订单周期呈指数分布
- 用户的平均购买周期是68天
- 绝大部分用户的购买周期都低于100天
3.5 用户生命周期(第一次购买和最后一次购买的差值)
user_life_info = ((user_life['max']-user_life['min'])/np.timedelta64(1,"D"))
user_life_lst = [i for i in range(0,int(user_life_info.max())+1,10)]
user_life_info_hist1 = pd.cut(user_life_info,bins=user_life_lst,labels=user_life_lst[1:]).fillna(10)
user_life_info.describe()
# mean 134
# 50% 0
- 生命周期的均值为134,中位数为0。
- 由图可看出,用户的生命周期受只购买一次的用户影响非常厉害因此考虑把这一部分数据用户排除掉再次分区间统计。
user_life_info_hist1 = pd.cut(user_life_info,bins=user_life_lst,labels=user_life_lst[1:])
user_life_info[user_life_info>0].describe()
# mean 276.044807
# 50% 302.000000
- 去除极值之后,用户平均购买周期为276天,中位数是302天。
- 可以多进行促销互动,减小购买周期的间隔。
4.复购率和回购率
# 区分一个,和一个以上的情况,以便于计算复购率,大于1为1,等于0为np.nan,等于1为0。因为复购统计时sum函数不会计算np.nan,count函数会计为1。
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x == 0 else 0)
purchase_r_reshop = (purchase_r.sum()/purchase_r.count()).reset_index(name = 'reshape')
# 需要使用函数来判断是否回购:当月消费过的用户下个月也消费了叫做回购,时间可以改变
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
indexs=df_purchase.columns
purchase_b = df_purchase.apply(lambda x :pd.Series(purchase_back(x),index = indexs),axis =1)
purchase_b_backshop = (purchase_b.sum()/purchase_b.count()).reset_index(name = 'huigou_rate')
本周期(在此为月)有购买且下个周期也有购买时,则为回购:1
本周期有购买下个周期没有购买,没有回购:0
本周期没有购买则为空值:np.nan
最后一个月补充为np.nan
复购率和回购率方面,复购率稳定在20%左右,回购率稳定在30%左右,前3个月因为有大量新用户涌入,而这批用户仅购买了一次,所以导致前三个月的复购率和回购率都比较低。
结论:
-
1.用户消费趋势分析(按月)
- 前3个月有大量新用户涌入,消费金额、消费次数、消费人数以及产品购买量均达到高峰,后续每月较为稳定。前3个月消费次数都在10000笔左右, 后续月份平均在2500上下波动;
- 前3个月产品购买量达到20000,后续月份平均7000;前3个月消费人数在8000-10000之间,后续月份平均2000不到;
- 推断可能是举办了促销降价活动,但是活动过去后,后续新用户量并没有显著提升,可见活动并没有给产品带来实质性的流量。在活动举办期间,全部指标都达到了顶峰,所以建议加大活动力度,并保持一定的时间维度,给买家充分了解产品的时间,才能持续提高用户消费。
-
2.用户个体消费
- 个别用户购买了大量的产品,升高了平均消费水平,绝大部分的用户购买金额在200元以下;
- 用户累计消费金额曲线得出50%的用户仅贡献了11%的消费额度,而排名前5000的用户就贡献了60%的消费额度,满足二八法则;
-
3.用户消费行为
- 首购和最后一次购买时间集中在前三个月,说明很多用户只购买了一次,而且用户最后一次的购买时间逐步上升,流失的客户在逐步增加。
- 有一半的用户仅仅消费了一次,而且每个月的新客在第四个月起逐步稳定在80%左右。
- 利用RFM模型分出八种用户,其中一般挽留用户最多,可以多注意这些用户。该模型实现了对用户的精细化运营。
- 针对用户生命周期,前三个月有大量新用户涌入,新用户占比很高,而后面几个月不活跃用户占比非常高,普遍在90%以上。持续消费的用户,一直维持在5%左右,对应的消费运营质量一般。回流用户在5%到8%之间,唤回运营这方面做的是很不错的。流失率是最高的,所以可以采取加强对活跃用户的福利待遇,提高换回用户的手段。排除50%的仅消费一次的用户影响后,平均购买周期为276天,中位数是302天,可采取降价手段,降低购买周期。
- 用户购买周期呈现指数分布,绝大部分用户的购买周期都低于100天,高于100天的呈指数形下降。
4.复购率和回购率方面,复购率稳定在20%左右,回购率稳定在30%左右,前3个月因为有大量新用户涌入,而这批用户仅购买了一次,所以导致前三个月的复购率和回购率都比较低。