分析目标
本篇分析基于CDNow网站18个月的CD销售数据。数据来源于互联网(txt格式)一共有用户ID,购买日期,购买数量,购买金额四个字段。本次分析侧重于通过历史消费数据分析,总结用户流失原因;依据用户消费行为将其划分为不同类别的用户,从而进行个性化管理,以提高利润。
分析结论及建议导读
1、用户消费趋势的分析(按月)
- 结论汇总:在前三个月消费总金额、消费次数、产品购买量、消费人数都呈上升趋势,而在四月份骤然下降,四月份之后稳定在低水平状态。
- 原因分析:
1)定位用户:
【渠道】方面,可从用户数量、渠道收入(环比、下滑)两个维度来评判渠道质量,可从查看不同渠道的等数据指标。不同渠道如web端直接访问、搜索引擎渠道、第三方合作渠道等,进一步定位是否是渠道推广有问题;
【用户来源】方面,是否时活动促销吸引来的用户,从新老客构成、流失客户的特征、聚类与分类等角度;
2)针对不同用户的流失再分析内外部原因:
内部原因:(业务流程)
【获客渠道】质量低、活动获取非目标用户
【满足需求】新功能改动引发某类用户不满,针对客户的评价、网站评论及主动与用户沟通收集反馈意见问题,对产品体验进行分析,如是包装不流行、价格没优惠了等等进行优化。
【用户体验流程】方面,通过埋点数据查看流失客户在网站上的行为数据:启动次数、停留时长、流程不畅(如流失客户大部分在支付环节未完成支付,那么就需要回访部分客户、进行优化改进);
外部原因:(PEST分析)
【外部技术】技术的更新,使得CD的播放设备不易携带特点突出,销售必然下滑
【竞争对手】方面,近期是否出现其他竞争与替代产品; - 后续改进:确定原因后,可以针对性的更换获客渠道,优化购物流程,改善用户体验,并注意竞争对手,及时改进产品。
2、用户个体消费分析
- 结论汇总:
1)描述统计来看,平均每位用户购买量约为7,购买金额约为106;而用户最多的购买量为1033,购买金额为13990.93;至少有一半的用户购买量在3及3以下,购买金额在43.395及以下。
2)用户的消费金额主要集中在0-856元,产品购买量集中在1-30,消费最高的3500名用户贡献了约60%的消费额,消费额最高的8500名用户贡献了超过80%的消费额。 - 原因分析:
消费、金融和钱相关的数据,基本上都符合二八法则,小部分的用户占了消费的大头 - 后续改进:
1)可以分析最受用户欢迎的商品和其他一些相关的商品做一些捆绑销售,带动其他商品的销量,从而提高产品购买量;
2)对购买金额较大的用户做好售后,分析这些用户特点,在用户运营方面考虑侧重于相关用户。
3.用户消费行为分析
- 结论汇总:
1)大部分的用户在前3个月只消费一次就不消费了,且总共有12054个用户只消费了一次。
2)用RFM模型进行用户群体划分,重要价值客户占比1.9%,重要保持客户占19.59%,大部分客户都是购买金额比较小的一般客户。
3)用户在不同月份的状态以‘新用户’、‘活跃’、‘回流’、‘不活跃’来进行用户分层,新用户在前三个月不断增加,从四月份开始就没有新用户注册,活跃用户后期稳定在500人左右。 - 原因分析:
1)可能因网站体验确实不好而流失,也有可能是用户只是被活动吸引,活动结束后,用户流失,我们要做好后续用户的维护和促活。
2)可能与CD产品属性(消费频次较低 / 购买周期较长)有关,此时相对于此网站的用户流失,用户拉新与首购才是最核心的指标。 - 后续改进:
1)用户只消费了一次可能是被最开始的活动所吸引,建议多做一些促销活动,可以以节日为主题带动用户消费。
2)针对RFM用户分层的,可制定三大策略:
提高活跃度:注重提升一般客户、低价值客户的活跃度,将其转化为优质客户。
提高留存率:与重要价值、重要挽留客户互动,提高这部分用户的留存率。
提高付费率:维系重要保持客户、重要发展客户的忠诚度,保持网站的良好收入。具体手段包括搭建会员体系、会员分类管理与升级、积分兑换、发放折扣券等。
3)从第四个月后没有新用户加入是一个严重的问题,可以不断开拓营销渠道、举办活动以及用老带新的方式来开拓新用户;同时促活流失用户
详细内容阅读指导:
1、数据预处理与描述性统计
1.1观察数据及预处理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline # 魔法方法,让图形在每一个空格中呈现
plt.rcParams['font.sans-serif'] = ['SimHei'] # 字体设置 正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 坐标轴可以显示负数
plt.style.use('ggplot') # 设置图形风格,自带的比较丑,这里的用的是R语言的图形风格
数据读取
df = pd.read_table('CDNOW_master.txt',sep = '\s+') # 这份数据通过多个空格分隔,\s+ 表示匹配任意空白字符
df.head()
观察以上数据,发现没有字段名,需要手动添加
columns = ['user_id','order_dt','order_products','order_amount'] # 添加字段名
df.columns = columns
df.head()
df.info() 观察数据大体情况
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 69658 entries, 0 to 69657
Data columns (total 4 columns):
user_id 69658 non-null int64
order_dt 69658 non-null int64
order_products 69658 non-null int64
order_amount 69658 non-null float64
dtypes: float64(1), int64(3)
memory usage: 2.1 MB
通过上述信息可以知道数据十分干净,没有空行等,但是order_dt应该为日期时间类型,这里却是int类型,需要完成数据类型转换
df['order_dt'] = pd.to_datetime(df.order_dt,format = '%Y%m%d') # Y为四位数的日年份,y表示两位数的年份
# 为了按照月份分析,新增字段month,[M]表示订单日期所在的月份,类型是时间数据,显示为月份的第一天 [Y]则显示为日期所在年份的第一天
df['month'] = df.order_dt.values.astype('datetime64[M]')
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 69658 entries, 0 to 69657
Data columns (total 5 columns):
user_id 69658 non-null int64
order_dt 69658 non-null datetime64[ns]
order_products 69658 non-null int64
order_amount 69658 non-null float64
month 69658 non-null datetime64[ns]
dtypes: datetime64[ns](2), float64(1), int64(2)
memory usage: 2.7 MB
1.2 描述性统计
df.describe()
从销量方面看,平均每笔订单购买2.4张cd,标准差为2.33,稍有波动,购买量上四分位数为3张,说明大部分用户订单购买量不大,大部分集中在3张以下;消费金额呈现类似趋势,单笔订单最大值为99,说明有少部分狂热打榜粉丝,一般消费类的数据都是呈现长尾形态,即大部分用户都是小额消费者,小部分用户贡献了主要的消费收益,俗称二八理论
2 按月度进行消费趋势分析
- 每月的消费总金额与订单量
- 每月用户量
- 每月用户消费的平均金额与平均消费次数
2.1 每月的消费情况
先做每月消费总额和订单量的变化图
grouped_month = df.groupby(by=['month'])
order_month_amount = grouped_month['order_amount'].sum()
order_month_amount.plot()
plt.title('每月消费总额趋势图')
grouped_month.user_id.count().plot()
plt.title('每月订单数量')
前3个月消费金额数据为最高峰,后续较为平稳,有轻微下降趋势
前3个月订单量都在1万左右,后续订单量急剧下滑,订单量维持在2500左右,猜测前期应为粉丝打榜或者促销活动释放了顾客购买力
2.2每月用户量
每月用户量稍稍复杂,主要是计算每个月下过订单的用户量,所以需要去重
grouped_month.user_id.apply(lambda x:len(x.drop_duplicates())).plot() # 去重操作,使用匿名函数将grouped_month.user_id传入x,将每个月内重复id去除,输出的是每个月用户id去重后的数据长度 grouped_month.user_id等同于 grouped_month['user_id']
plt.title('每月用户量')
# 另一种去重方式:
df.groupby(['month','user_id']).count().reset_index() #实际上是分组去重,先按月份分分完在按id分组,这样就去掉了重复值,接着去对每个唯一值去计数,reset_index() 去掉索引
s.groupby(['month']).user_id.count().plot()
前三个月用户量维持在9000左右,后续购买用户数总体上呈现缓慢下降的趋势,用户量维持在2000左右
2.3 每月用户消费的平均金额与平均次数
((grouped_month.order_amount.sum())/(grouped_month.user_id.apply(lambda x:len(x.drop_duplicates())))).plot()
plt.title('每月用户平均消费金额')
((grouped_month.user_id.count()) / grouped_month.user_id.apply(lambda x:len(x.drop_duplicates()))).plot()
plt.title('用户平均消费次数')
前3个月用户平均消费金额在40元左右,后续有所上升,用户平均消费金额在[46,56]
平均消费次数总体上呈现上升趋势,前3个月平均消费次数在1.2次上下,后续在1.35上下波动,说明cd消费大部分是一锤子买卖,狂热粉丝消费极少,顾客消费较为理性
3 用户个体消费分析
- 消费商品数与消费总金额的描述性统计
- 消费金额与消费商品数的散点图
- 用户消费金额、次数的分布图
- 用户累计消费金额占比
3.1 消费商品数与消费总金额的描述性统计
grouped_user = df.groupby('user_id')
grouped_user.sum().describe()
从用户角度看,单个用户平均购买量为7,中位数为3,说明小部分用户购买了大量CD;
用户平均消费金额为106元,消费总额中位数为43,判断同上,存在小部分的高额消费人群。
基本上消费、金融等与钱相关的都符合二八法则
3.2 消费金额与消费商品数的散点图(plot.scatter)
grouped_user.sum().query('order_amount < 4000'). plot.scatter(x = 'order_amount' , y = 'order_products')
plt.title('消费金额与消费产品数散点图')
#query后面只支持string形式的值,相当于sql的where子句,不筛选的话,极值会把图标拉大,不易观察数据集中部分的情况
用户比较健康,且规律性比较强,cd网站产品单一,价格较统一,消费金额与消费产品数量呈现线性关系
3.3 用户消费金额与消费次数的分布图
# 过滤掉商品数大于100的订单,减小极值影响,方便观察
grouped_user.sum().query('order_products<100').order_amount.hist(bins=40)
plt.title('消费金额分布图')
plt.figure(figsize=(12,5))
grouped_user.count().query('order_products<100').order_dt.hist(bins=40)
plt.title('用户消费次数的分布图')
大部分用户集中在低价领域,消费能力不高,高档用户几乎没有出现,符合消费市场行业规律
可以用中心极限低定理计算出95%的消费者消费金额在[0,856]之间
从图上看大部分用户只消费1次两次,高频消费者很少这也满足cd消费市场行业规律
3.4 累计消费金额占比
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x: x.cumsum()/x.sum())
user_cumsum.reset_index().order_amount.plot()#去掉索引,方便作图
plt.title('消费金额累计百分比')
按用户消费金额进行升序排序,由图可知50%的用户仅贡献了15%的销售额度。而排名前5000的用户就贡献了60%的消费额。也就是说我们只要维护了这5000个用户就可以把业绩KPI完成60%,如果能把5000个用户运营的更好就可以占比70%—80%之间。
4 用户消费行为分析
- 用户第一次消费时间与最后一次消费时间
- 新老客户消费比
- 用户分层
4.1 用户第一次消费时间与最后一次消费时间
grouped_user.order_dt.min().value_counts().plot(figsize=(12,5))
plt.title('用户首次消费')
grouped_user.order_dt.max().value_counts().plot(figsize=(12,5))
plt.title('用户最后一次消费时间')
用户首次消费集中在前3个月,2月10日至2月25日有较为强烈的波动
观察用户的最后一次消费时间。用户最后一次消费比第一次消费分布广,大部分最后一次消费集中在前三个月,说明很多客户购买一次就不再进行购买。随着时间的增长,最后一次购买数也在递增,消费呈现流失上升的情况,用户忠诚度在慢慢下降
4.2 新老客户消费比
#计算只买了一次的消费群体
grouped_user.count().query('order_dt == 1').order_dt.count()
#总消费人群
grouped_user.count().reset_index().user_id.count()
11907
23569
大部分人值消费了一次,买一次就跑
# 按月份和用户ID分组
grouped_month_user=df.groupby(['month','user_id'])
# 用当月用户订单日期最小值与用户订单日期最小值联结
tmp=grouped_month_user.order_dt.agg(['min']).join(grouped_user.order_dt.min())
# 判断用户当月订单日期最小值是否与用户订单日期最小值相等,新建字段new
tmp['new']=(tmp['min']==tmp.order_dt)
# 作新客占比折线图
tmp.reset_index().groupby('month').new.apply(lambda x: x.sum()/x.count()).plot()
plt.title('每月新客占比百分比')
## 注意sum()是计算值的和,count()是计算统计非空值
只有前三个月有新客户,后续全是原有老客消费
4.3 用户分层
1)RFM模型
R:(Recency)最后一次消费时间的度量,数值越大越好(这里用距离所有用户最后一次消费时间来代替,越小越好)
F:(Frequency)消费的次数(本数据消费次数比较集中,用总商品数代替),数值越大越好
M:(Monetary)消费的总金额,数值越大越好
# 作透视表
RFM = df.pivot_table(index = 'user_id',
values = ['order_dt','order_amount','order_products'],
aggfunc = {'order_dt':'max','order_products':'sum','order_amount':'sum'})
# 计算每位用户最后一次消费时间与全部用户最后一次消费时间的差值
RFM['R'] = - (RFM.order_dt-RFM.order_dt.max())/np.timedelta64(1,'D') # 除以np.timedelta64(1,'D') 消除单位days
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')#把正负转换为10
label=level.R+level.F+level.M#三个1或0拼接起来
d={
'111':'重要价值客户',
'011':'重要保持客户',
'101':'重要挽留客户',
'001':'重要发展客户',
'110':'一般价值客户',
'010':'一般保持客户',
'100':'一般挽留客户',
'000':'一般发展客户'
}
result=d[label]
return result
RFM['label']=RFM[['R','F','M']].apply(lambda x: x-x.mean()).apply(RFM_func,axis=1)#把数值与平均数相减,得到正负值,逐行应用
但这里平均数容易受到极值影响,所以,可以换成中位数,或者其他自定义的数值
RFM.head()
# 计算每层客户R、F、M的和
RFM.groupby('label').sum()
绝大部分消费是由重要保持客户产生的,维持好这部分客户,完成kpi有极大的帮助
# 计算每层客户R、F、M的个数和
RFM.groupby('label').count()
一般挽留客户客户基数较大,可对这部分客户进行挽留以保持高额增长
2)用户分层:新客、回流、活跃、流失
pivoted_counts = df.pivot_table(index = 'user_id',
columns = 'month',
values = 'order_dt',
aggfunc = 'count').fillna(0)
pivoted_counts.head()
用户每个月的消费次数,对于生命周期的划分只需要知道用户本月是否消费,消费次数在这里并不重要,需要将模型进行简化
使用数据透视表,需要明确获得什么结果。有些用户在某月没有进行过消费,会用NaA表示,这里用fillna填充。
df_purchase = pivoted_counts.applymap(lambda x : 1 if x > 0 else 0)
df_purchase.tail()
对于尾部数据,user_id为2W+的数据是有问题的,因为从实际的业务场景上说,他们一月和二月都没有注册三月份才是他们第一次消费。透视会把他们一月和二月的数据补上为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 pd.Series (status,index=columns_month)
columns_month=df.groupby('month').sum().reset_index().month
pivoted_status = df_purchase.apply( active_status,axis = 1)
pivoted_status.head()
#每月不同活跃用户的计数
purchase_status_ct = pivoted_status.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)) #不希望再后面count()时被计算,所以替代一下
purchase_status_ct
purchase_status_ct.fillna(0).T.plot.area()#填充nan值
plt.title("各类用户面积分布图")
active:活跃用户越来越少,说明运营的质量在降低,可能是用户体验不好、也可能是竞争加剧;
new:新用户前三个月之后就显著降低,说明市场和渠道部门需要加大拉新;
return:回流客户多,说明唤回运营(促销)起效;
unactive:不活跃用户正在增加,说明存在用户流失(也可能因为CD购买周期较长的原因