电商网站用户购买CD消费分析
用户消费行为分析
一、项目介绍
用户消费行为贯穿于生活中的各行各业,例如:淘宝、拼多多、叮当快药(医疗行业)、金融理财产品以及物流行业等等。
每天都会产生很多的消费行为,数据分析中经常需要对用户的消费行为进行分析。为了达到企业盈利、多卖产品的目的,除了保证产品质量,首要任务就是分析用户的消费行为。通过分析,可以更好地针对性地加大营销力度,进而提升企业的营业额。
本项目以某电商网站用户购买CD的明细作为样本数据,总共有6万条数据。
项目整体内容分为8大部分,从项目的介绍到数据处理再到各种维度的分析。比如用户整体和个体的分析、消费时间的分析、用户分层模型、复购率和回购率、生命周期和购买周期等等。通过分析,最后总结得出最终的结论。
二、需求分析
业务化分析思路:
用户通过自然渠道或付费渠道成为新客访问网站,从网站的营销落地页/通过导航页/通过搜索/个性化推荐访问商品详情页,用户收藏商品/加入购物车后,产生订单进行支付,获取物流信息,最终收到货物。收货后会产生退换货/用户评价分享/注册会员的行为,最后,用户选择流失/只购买一次或进行二次购买转换为回头客、忠诚客。
需求点分析如下:(分析维度)
三、数据预处理
- user_id:用户ID
- order_dt:购买日期
- order_products:购买产品数
- order_amount:购买金额
数据时间:1977年1月-1998年6月用户行为数据,约6万条
1.导入数据,导入常用库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
%matplotlib inline
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.style.use('ggplot') # 更改绘图风格,R语言绘图库的风格
2.导入源数据
# 导入数据
columns = ['user_id','order_dt','order_products','order_amount'] # 设置列名
df = pd.read_table('CDNOW_master.txt',names=columns,sep='\s+') # sep:'\s+':匹配任意个空格
df.head()
由上表:
- 日期格式需要转换
- 存在同一个用户一天内购买多次的行为
3.查看数据基本信息
df.describe()
由上表:
- 用户平均每笔订单购买2.4个商品,标准差2.3,有轻微波动,但属于正常现象;
- 75%分位数也为3,说明绝大多数订单的购买量都不多,数量范围在2-3个左右;
- 从购买金额列,反映出大部分订单消费金额集中在中小额,30-45左右。
df.info()
由上表:
- 日期类型为int64,需要转换
4.数据预处理
df['order_date'] = pd.to_datetime(df['order_dt'],format='%Y%m%d')
# order_date: datetime64[ns]
# format参数:按照指定的格式去匹配要转换的数据列
# %Y:四位数的年份-0000 %m:两位数月份-00 %d:两位数日期-00
# %y:两位是月份-00 %h:两位小时-00 %M:两位数分钟-00 %s:两位秒-00
# 将order_date转换成精度为月份的数据列
df['month'] = df['order_date'].astype('datetime64[M]') # [M]:控制转换后的精度
# df['month'] = df.order_dt.values.astype('datetime64[M]') #用values先将数据转换为数组格式
df.head()
df.info()
四、用户整体消费趋势分析(按月)
- 产品购买数量
- 消费金额
- 消费次数
- 消费人数
plt.figure(figsize=(20,15)) # 单位是英寸
# 每月的产品购买数量
plt.subplot(221) # 两行两列,占据第一个位置
df.groupby(by='month')['order_products'].sum().plot() # 默认折线图
plt.title('每月的产品购买数量')
# 每月的消费金额
plt.subplot(222)
df.groupby(by='month')['order_amount'].sum().plot()
plt.title('每月的消费金额')
# 每月的消费次数
plt.subplot(223)
df.groupby(by='month')['user_id'].count().plot()
plt.title('每月的消费次数')
# 每月的消费人数(先对user_id去重,再统计个数)
plt.subplot(224)
df.groupby(by='month')['user_id'].apply(lambda x: len(x.drop_duplicates())).plot() # 去重函数drop_duplicates()、计数len()(也可以用count())
plt.title('每月的消费人数')
分析结果:
-
图一可以看出,前三个月销量非常高,而后的销量较为稳定,并且稍微呈现下降趋势;
-
图二可以看出,前三个月消费金额依然较高,与消费数量成正比例关系,三月份后下降严重,并呈现下降趋势,猜测原因:
- 与月份有关,我国的1-3月份处于春节前后;
- 网站在1-3月份的时候加大了促销力度
-
图三可以看出,前三个月订单数在10000左右,后续月份的订单数在2500左右
-
图四可以看出,前三个月平均消费人数在8000-10000,后续月份的平均消费人数在2000不到
-
总结:所有数据显示,97年前三月消费事态异常,后续趋于常态化
五、用户个体消费分析
1.用户消费金额,消费次数(产品数量)的描述统计(用户个体user_id)
user_grouped = df.groupby(by='user_id').sum()
print(user_grouped.describe())
print('用户数量:',len(user_grouped))
用户数量: 23570
- 从用户的角度,用户数量23570个,每个用户平均购买7个CD,但是中位数只有3,并且最大购买量为1033,平均值大于中位数,属于典型的有偏分布(替购买量小于7的用户背锅);
- 从消费金额的角度,用户平均消费106,中位数43,并且存在土豪用户消费金额为13990,结合分位数和最大值来看,平均数与75%分位数几乎相等,属于典型的右偏分布,说明存在小部分用户(25%)高额消费(这些用户需要给消费金额小于106的用户背锅)
# 绘制每个用户的产品购买量与消费金额的散点图
df.plot(kind='scatter',x='order_products',y='order_amount')
- 从图中可知,用户的消费金额与购买了存在线性关系,每个商品均价15左右;
- 订单的极值点比较少(消费金额>1000/购买量>60),对于样本来说影响不大,可以忽略不计
2.用户消费分布图
plt.figure(figsize=(12,4))
plt.subplot(121)
plt.xlabel('每个订单的消费金额') # 对每条记录
df['order_amount'].plot(kind='hist',bins=50) # 区间分数,影响柱子的宽度,值越大柱子越细;宽度=(列最大值-最小值)/bins
plt.subplot(122)
plt.xlabel('每个用户购买的数量')
df.groupby(by='user_id')['order_products'].sum().plot(kind='hist',bins=50)
- 绝大部分的订单消费金额在100以内
- 每个用户的购买数量较少,集中在50以内
- 综上,该网站的大部分用户是消费金额低,并且购买量小于50的类型(是正常的电商领域现象)
3.用户累计消费金额的占比(用户的贡献度)
# 进行用户分组,取出消费金额,进行求和,排序,重置索引
user_cumsum = df.groupby(by='user_id')['order_amount'].sum().sort_values().reset_index()
# 每个用户消费金额累加
user_cumsum['amount_cumsum'] = user_cumsum['order_amount'].cumsum()
# 消费金额总值
amount_total = user_cumsum['amount_cumsum'].max()
# 前x名用户的贡献率
user_cumsum['prop'] = user_cumsum.apply(lambda x:x['amount_cumsum']/amount_total,axis=1)
user_cumsum.tail()
user_cumsum['prop'].plot()
plt.xlabel('用户数量')
由图分析,前20000名用户贡献总金额的40%,剩余3500名用户贡献了60%(二八法则)
六、用户消费行为
1.用户首购时间
# 用户分组,取最小值,即为首购时间,并计数
df.groupby(by='user_id')['order_date'].min().value_counts().plot()
由图可知,首次购买的用户量在1月1号~2月10号呈明显上升趋势,后续开始逐步下降
猜测原因:公司产品的推广力度或价格的调整
2.用户最近一次消费时间
# 用户分组,取最小值,即为首购时间,并计数
df.groupby(by='user_id')['order_date'].max().value_counts().plot()
- 大多数用户最后一次购买时间集中在前3个月,说明缺少忠诚用户;
- 随着时间的推移,最后一次购买商品的用户量呈现上升趋势;
- 猜测这份数据是针对前三个月消费的用户在之后18个月消费的跟踪记录
3.用户分层
3.1 构建RFM模型(透视图的使用)
- 为了进行精细化运营,可以利用RFM模型对用户价值指数(衡量历史到当前用户贡献的收益)进行计算,其中:
- R:(Recency,最近一次消费)R值越大,表示客户交易发生的日期越久远,反之则最近一次交易发生的日期越近;
- F:(Frequency,消费频率),F值越大,表示客户交越频繁,反之则表示客户交易不够活跃(此处我们选择购买产品数量作F);
- M:(Monetary,消费金额),M值越大,表示客户价值越高,反之则表示客户价值越低
根据上述三个维度,对客户做细分
透视表是一种可以对数据动态排布并且分类汇总的表格格式。在pandas中称为pivot_table
- 灵活性高,可以随意定制分析计算要求;
- 脉络清晰易于理解数据;
- 操作性强,报表神器
# 透视表的使用
rfm = df.pivot_table(index = 'user_id',
values = ['order_products','order_amount','order_date'],
aggfunc={
'order_date':'max', # 最近一次购买
'order_products':'sum', # 购买产品的总数量
'order_amount':'sum' # 最消费总金额
})
rfm.head()
- index:相当于groupby
- values:取出的数据列
- aggfunc:key值必须存在values列中,并且必须跟随有效的聚合函数
# 用每个用户的最后一次购买时间-日期列中的最大值(当前日期),最后转换成天数,小数保留一位
rfm['R'] = -(rfm['order_date'] - rfm['order_date'].max()) / np.timedelta64(1,'D') # 取相差的天数
rfm.rename(columns={'order_products':'F','order_amount':'M'},inplace=True)
rfm.head()
# RFM计算方式:每一列数据减去数据所在列的平均值,有正有负,将结果只与1作比较,如果大于等于1,设置标签为1,否则为0
def rfm_func(x): # x分别代表每一列数据
level = x.apply(lambda x:'1' if x>=1 else '0')
label = level['R'] + level['F'] + level['M']
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()
# 客户分层可视化
for label,grouped in rfm.groupby(by='label'):
x = grouped['F'] # 单个用户的购买数量
y = grouped['R'] # 最近一次购买时间与98年7月的相差天数
# 可以随意选择
plt.scatter(x,y,label = label)
plt.legend()
plt.xlabel('F')
plt.ylabel('R')
3.2 新、老、活跃、回流、流失分层用户
- 新用户的定义是第一次消费
- 活跃用户即老客,在某个时间窗口内有过消费
- 不活跃用户则是时间窗口内没有消费过的老客
- 回流用户:即回头客
- 用户回流的动作可以分为自主回流和人工回流
pivoted_counts = df.pivot_table(index='user_id',
columns='month',
values='order_dt',
aggfunc='count'
).fillna(0)
pivoted_counts
# 由于浮点数不直观,需要转成是否消费过,用0、1表示
df_purchase = pivoted_counts.applymap(lambda x:1 if x>0 else 0)
df_purchase.head()
- apply:作用于dataFrame数据中的一行或一列数据
- applymap:作用于dataFrame数据中的每一个元素
- map:本身是一个series的函数,在DataFrame结构中无法使用,作用于series中的每一个元素
# 用户分群:新用户、活跃用户、不活跃用户、回流用户
# 由于用户第一次消费的时间不同,定义一个新状态‘未注册(unreg)’来表示未开始第一次消费的新用户
# df_purchase.apply(func,axis=1)
def active_status(data): # data:整行数据,共18列
status = [] # 负责存储18个月的状态:unreg|new|active|unactive|return
for i in range(18):
# 本月没有消费
if data[i] == 0:
if len(status) == 0:
status.append('unreg')
else:
if status[i-1] == 'unreg':
status.append('unreg')
else:# new|active|unactive|return
status.append('unactive')
pass
# 本月有消费
else: # data[i] == 1
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:# new|active|return
status.append('active')
return pd.Series(status,df_purchase.columns) #值status,列名为df_purchase中的列名
purchase_status = df_purchase.apply(active_status,axis=1) #得到用户分层结果
purchase_status.head()
# 用NaN替换unreg
purchase_status_ct = purchase_status.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
purchase_status_ct.head()
purchase_status_ct.T.fillna(0).plot.area() # 行列变换后绘制面积图
- 由图可知,前三个月的活跃用户(红)和新用户(蓝)占比较大
- 四月份过后,新用户和活跃用户开始减少,并且呈现稳定趋势
- 回流用户主要产生在4月过后,呈现稳定趋势,是网站的重要客户(忠诚客户)
3.3 回流用户占比
# 用户的占比:status/总消费人数
rate = purchase_status_ct.T.fillna(0).apply(lambda x:x/x.sum(),axis=1)
# 回流、活跃用户的占比
plt.plot(rate['return'],label='return')
plt.plot(rate['active'],label='active')
plt.legend()
- 回流用户:前五个月回流用户上涨,之后呈现下降趋势,平均维持在5%比例
- 活跃用户:前三个月活跃用户大量增长(活动引来新用户),4月份过后开始下降,平均维持在2.5%左右
- 网站运营稳定后,回流用户(5%)占比大于活跃用户(2.5%)
4.用户购买周期
shift函数的使用
# shift函数:将数据移动到指定的位置
data1 = pd.DataFrame({
'a':[0,1,2,3,4,5],
'b':[5,4,3,2,1,0]
})
data1.shift(axis=0) # 整体向下移动一个位置(默认值:axis=0)
data1.shift(axis=1) # 整体向右移动一个位置
order_diff = df.groupby(by='user_id').apply(lambda x:x['order_date']-x['order_date'].shift()) # 当前订单日期-上次订单日期
order_diff.head()
order_diff.describe()
# 将dtype: timedelta64[ns]类型转换为天数,绘制直方图
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
plt.xlabel('购买周期')
- 用户的平均消费周期为68天,且大多数用户的消费周期低于100天
- 呈现典型的长尾分布,只有小部分用户消费周期在200天以上(不积极消费的用户),可以在这批用户消费后3天左右进行电话回访或者短信赠送优惠券等活动,增大消费频率
5.用户生命周期(新老客户消费比)
# 计算方式:用户最后一购买日期(max)-第一次购买的日期(min)(如果差值等于0,说明用户仅仅购买了一次)
user_life = df.groupby('user_id')['order_date'].agg(['min','max'])
(user_life['max'] == user_life['min']).value_counts().plot.pie(autopct='%1.1f%%') # 格式化为1位小数
plt.legend(['仅消费一次','多次消费'])
一半以上的用户仅消费了一次,说明运营不利,留存率不好
(user_life['max'] - user_life['min']).describe() # 生命周期描述统计分析
- 用户平均生命周期为134天,但是中位数为0,再次说明大多数用户只消费了一次,是低质量用户;
- 75%分位数以后的用户,生命周期大于294,属于核心用户,需要着重维护
# 绘制所有用户的生命周期直方图
plt.figure(figsize=(12,6))
plt.subplot(121)
((user_life['max'] - user_life['min'])/np.timedelta64(1,'D')).hist(bins=15)
plt.title('所有用户的生命周期直方图')
plt.xlabel('生命周期天数')
plt.ylabel('用户人数')
# 绘制多次消费的用户生命周期直方图
plt.subplot(122)
u_1 = (user_life['max'] - user_life['min']).reset_index()[0]/np.timedelta64(1,'D')
u_1[u_1>0].hist(bins=15 )
plt.title('多次消费的用户生命周期直方图')
plt.xlabel('生命周期天数')
plt.ylabel('用户人数')
- 通过两图对比可知,第二幅图过滤掉了生命周期为0的用户,呈现双峰结构
- 虽然过滤后仍有一部分用户的生命周期趋于0,但状况明显比第一幅图好了很多;这部分用户虽然进行了多次消费但是生命周期仍然很短,属于普通用户,可针对性进行营销推广活动
- 少部分用户生命周期集中在300-500天,属于忠诚用户,需要大力度维护此类用户
七、复购率和回购率分析
1.复购率分析
# 计算方式:在自然月内,购买多次的用户在总消费人数中的占比(包括在同一天消费多次)
# 某月内的消费者:复购用户(1)、非复购用户(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():表示总消费人数(NaN不参与计数)
(purchase_r.sum() / purchase_r.count()).plot(figsize=(12,6))
- 由图:前三个月复购率开始上升,后续趋于平稳维持在20%-22%之间
- 前三个月复购率较低的原因:大部分新用户仅仅购买一次
2.回购率分析
# 计算方式:在一个时间窗口内进行了消费,在下一个窗口内又进行了消费
# 1:回购用户 0:非回购用户 NaN:当前月份未消费
# df_purchase.apply(func,axis=1)
def purchase_back(data):
status = [] # 存储用户回购率状态
for i in range(17):
# 当前月份消费了
if data[i] == 1:
if data[i+1] == 1:
status.append(1)
elif data[i+1] == 0:
status.append(0)
else:
# 由于for循环只需要执行17次,返回17个结果,但原表有18列,需要填充最后一列数据
status.append(np.NAN)
return pd.Series(status,df_purchase.columns)
purchase_b = df_purchase.apply(purchase_back,axis=1)
purchase_b
# 用户回购率与复购率的对比
plt.figure(figsize=(10,12))
plt.subplot(211)
# 回购率
(purchase_b.sum()/purchase_b.count()).plot(label = '回购率')
# 复购率
(purchase_b.sum()/purchase_b.count()).plot(label = '回购率')
plt.legend()
plt.ylabel('百分比%')
plt.title('用户回购率与复购率的对比图')
# 回购人数与购物总人数对比
plt.subplot(212)
plt.plot(purchase_b.sum(),label='回购人数')
plt.plot(purchase_b.count(),label='购物总人数')
plt.legend()
plt.ylabel('人数')
plt.title('回购人数与购物总人数的对比图')
由图一:
- 回购率:平稳后在30%左右,波动性相对较大
- 复购率低于回购率,平稳后在20%左右,波动性较小
- 前三个月的回购率和复购率都呈现上升趋势,说明新用户需要一定的时间转换成复购/回购用户
- 结合新老用户分析,新客户的忠诚度远低于老客户
由图二:
- 前三个月的购物总人数远远大于回购人数,主要是因为很多新用户在1月份进行了首次购买
- 三个月后,回购人数和购物总人数开始稳定,其中,回购人数稳定在1000左右,购物总人数在2000左右。
八、结论
- 1.整体趋势:按年的月份趋势销量和销售额在1-3月份相对较高,然后骤降,原因可能跟这段时间的大力促销与商品的季度属性有关;
- 2.用户个体特征:每笔订单的金额和商品购买量都集中在区间的低段水平,都是小金额小批量进行购买,此类交易群体,可在丰富产品线和增加促销活动提高转换率和购买率;
- 3.大部分用户的消费总额和购买总量都集中在区间的低段,长尾分布,这个主要跟用户的需求有关,可以对商品进行多元文化价值的赋予,增强其社交价值属性,提高用户的价值需求;
- 4.用户的消费周期:有两次消费以上的用户,平均消费周期为68天,所以在50-60天期间,应该对这批用户进行刺激召回。例如10天咨询满意度,30天发放优惠券,60天提醒使用优惠券;
- 5.用户的生命周期:有两次消费以上的用户,平均生命周期为276天,针对生命周期分布在20天内或400-500天间的用户,应该在20天内对用户进行引导,促进其再次消费形成消费习惯,延长生命周期;生命周期在100-400天的用户,也要根据其特点推出有针对性的营销活动,引导其持续消费;
- 6.新客户的复购率约为12%,老客户的复购率在20%左右;新客户的回购率在15%左右,老客户的回购率在30%左右;需要营销策略积极引导其再次消费以及持续消费;
- 7.用户质量:用户个体消费有一定规律性,大部分用户的消费金额集中在2000以下,用户消费反映了二八法则。消费排名前20%的用户贡献了80%的消费额。所以,狠抓高质量用户是亘古不变的道理,这些高质量客户都是“会员”类型,需要专门为会员优化购物体验,比如专线接听、特殊优惠等等。
九、方法总结
- 1.针对用户按照月份进行整体和个体分析,主要分析维度为人数,消费金额和购买量;
- 2.消费分析:首购时间、最近一次购买时间、购买周期(相邻两个购物时间的间隔)、用户分层(RFM模型+数据透视表),分析维度主要是新用户、活跃用户、不活跃用户流失分析、回流用户占比;
- 3.复购率和回购率的分析