一、项目背景:
这是一份单车网站用户消费行为的分析报告,通过对用户消费记录,深入分析用户消费行为,建立 RFM 模型,进行用户分层,发掘高价值用户并进行针对性管理和维护,实现用户运营精细化。
二、分析思路:
image.png三、分析工具:
使用Python,PowerBI对数据进行了可视化分析
四、分析目的:
结合数据集,分析用户消费趋势和行为特征,了解业务和用户现状,进一步挖掘用户行为特征信息,为决策提供数据支持。
五、分析过程:
#导入数据
import pandas as pd
import numpy as np
columns=['user_id','order_dt','order_products','order_amount']
df=pd.read_table('bicycle_master.txt',names=columns, sep='\s+')
- user_id:用户ID
- order_dt:购买日期
- border_products:购买产品数
- order_amount:购买金额
df.info()
image.png
df.head()
image.png
df.describe()
image.png
大部分订单只消费了少量商品(平均2.4),存在一定极值干扰
用户的消费金额比较稳定,平均消费35元,中位数在25元,有一定极值的干扰
df['order_dt']=pd.to_datetime(df.order_dt,format="%Y%m%d")
df['month']=df.order_dt.values.astype('datetime64[M]')
df['month']
image.png
对时间进行解析,一开始默认的格式是 int64的,默认会是 datetime64[ns] 类型,后面中括号表示 时间间隔是 ns
下面是对 order_dt 列(取values),转换类型为datetime64[M],默认就会是每月的第一天了,同理设置为[Y]就是每年的1月1日,然后生成新的一列 month
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
image.png
grouped_month_info.rename(columns = {'order_amount':'消费金额', 'user_id': '消费次数', 'order_products': '产品购买量'}, inplace=True)
grouped_month_info.head()
grouped_month_info['消费人次'] = grouped_month['user_id'].unique().map(len)
#也可以用 grouped_month_info['消费人次'] = grouped_month['user_id'].unique().apply(lambda x:len(x))
grouped_month_info.head()
image.png
#重置索引, 将month字段转换为字符型,
grouped_month_info = grouped_month_info.reset_index()
grouped_month_info['month'] = grouped_month_info['month'].astype(str)
将处理过的数据,导入到Excel表格中
grouped_month_info.to_excel(r'.\临时表\月销售额、销售次数、产品购买量、消费人数.xlsx')
上面进行的汇总分析,其实可以用数据透视的方法更快实现,一次性求出结果
df.pivot_table(index='month',
values=['order_products','order_amount','user_id'],
aggfunc={'order_products':'sum',
'order_amount':'sum',
'user_id':'count'}).head()
将Excel数据导入到powerBI,可视化数据
image.png
由上图可知,消费金额在前三个月达到最高峰,后续消费较为稳定,有轻微下降趋势
image.png由上图可知,产品购买量在前三个月达到最高峰,后续消费较为稳定,有轻微下降趋势
image.png
前三个月消费订单人数在10000笔左右,后续月份的平均消费人数则在2500人
每月消费人数低于每月消费次数,但差异不大
前三个月每月的消费人数在8000-10000之间,后续月份,平均消费人数在2000不到
去重的方法有多种,这里也可以使用数据库思想,df.groupby(['month','user_id']).count().reset_index()
2.用户个体消费分析
- 用户消费金额,消费次数的描述统计
- 用户消费金额和消费次数的散点图
- 用户消费金额的分布图
- 用户消费次数的分布图
- 用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)
grouped_user=df.groupby('user_id')
grouped_user_info = grouped_user.sum()
grouped_user_info.head()
image.png
#用户消费金额,消费次数的描述统计
grouped_user.sum().describe()
image.png
用户平均购买了7张,但是中位值只有3,说明小部分用户购买了大量货物
用户平均消费106元,中位值有43,判断同上,有极值干扰
将处理过的数据,导入到Excel表格中
grouped_user_info.to_excel(r'.\临时表\用户个体消费行为分析.xlsx')
image.png
- pd.cut() 利用进行切片分组
grouped_user_sum_order_amount = grouped_user.order_amount.sum()
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:])
grouped_user_sum_order_amount
image.png
将处理过的数据,导入到Excel表格中
grouped_user_sum_order_amount.to_excel(r'.\临时表\消费金额分布直方图.xlsx')
从直方图可知,用户消费金额,绝大部分呈现集中趋势,小部分异常值干扰了判断,可以使用过滤操作排除异常
使用切比雪夫定理过滤掉异常值,因为切比雪夫定理说明,95%的数据都分布在5个标准差之内,剩下5%的极值就不要了
order_amount (mean = 106 ,std = 241) mean+5std = 1311
image.png
利用cumsum 求金额累加值
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
user_cumsum.tail(10)
image.png
将处理过的数据,导入到Excel表格中
# 这里 reset_index() 是为了得到一个自然数的行标签,表示的就是人数,下面的图就可以看出来多少个少占多少百分比
user_cumsum.reset_index().order_amount.to_excel(r'.\临时表\累积销售.xlsx')
image.png
按照用户消费金额进行升序排序,由图可以知道50%的用户仅贡献了11%的消费额度,而排名前5000的用户就贡献了60%的消费额度
3.用户消费行为
-
用户第一次消费(首购)
-
用户最后一次消费
-
新老客户消费比
- 多少用户仅消费一次
- 每月新客占比
-
用户分层
- RFM模型
- 新、老、活跃、回流、流失
-
用户购买周期(按订单)
- 用户消费周期描述
- 用户消费周期分布
-
用户生命周期(按第一次和最后一次消费) -用户生命周期描述
- 用户生命周期分布
-
用户第一次消费(首购)
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_min.head()
image.png
将处理过的数据,导入到Excel表格中
grouped_user_min.to_excel(r'.\临时表\用户首购.xlsx')
image.png
- 用户最后一次消费
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)
grouped_user_max.head()
将处理过的数据,导入到Excel表格中
grouped_user_max.to_excel(r'.\临时表\用户最后一次购买.xlsx')
image.png
- 新老客户消费比
#新老客消费比
# 统计只消费了一次的用户
(user_life['min']==user_life['max']).value_counts()
image.png
有一半的用户,只消费了一次
grouped_um = df.groupby(['month','user_id']).order_dt.agg(["min","max"])
grouped_um["new"] = (grouped_um["min"] == grouped_um["max"] ) # 新增列 True 为 新用户
grouped_um.reset_index().groupby("month").new.value_counts()
image.png
- 用户分层
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()
得到最近一次消费,一般是计算 today 距离最近一次消费,这里因为时间太久远,就随便用的max值
数值越大就越久远,分子得到的是一些天数类似 545 days(因为是时间格式相减),处以一个单位,就不会有单位了只留下数值
重命名,也就是 R :消费时间 F:消费金额 M:消费频次
rfm['R']= (rfm.order_dt - rfm.order_dt.max())/np.timedelta64(1,'D')
rfm.rename(columns={'order_products':"F",'order_amount':'M'},inplace=True)
rfm[['R','F','M']].apply(lambda x:x-x.mean())
image.png
用函数打标签
def rfm_func(x):
level=x.apply(lambda x:'1' if x>=0 else '0')
# level 的类型是 series,index 是 R、F、M
# print(type(level))
# print(level.index)
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,传递一行得到一个 111,然后匹配返回一个值
rfm['label']=rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
rfm.loc[rfm.label=='重要价值客户','color']='重要价值客户'
rfm.loc[~(rfm.label=='重要价值客户'),'color']='非重要价值客户'
rfm.head()
image.png
将处理过的数据,导入到Excel表格中
rfm.to_excel(r'.\临时表\RFM模型.xlsx')
image.png
rfm.groupby('label').sum()
image.png
rfm.groupby('label').sum()
image.png
从RFM 分层可知,大部分用户是重要保持客户,但是这是由于极值的影响,所以 RFM 的划分标准应该以业务为准,也可以通过切比雪夫去除极值后求均值,并且 RFM 的各个划分标准可以都不一样
尽量用小部分的用户覆盖大部分的额度
不要为了数据好看划分等级
- 用户生命周期
新客,活跃,回流,流失(一段时间不消费,或者不活跃)
# 数据透视, userid为索引,月为列,求每月的消费次数,这里填充了
pivoted_counts=df.pivot_table(index='user_id',
columns='month',
values='order_dt',
aggfunc='count').fillna(0)
pivoted_counts.head()
image.png
# 转变一下消费,有消费为1,没有消费为0
df_purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0)
df_purchase.tail()
image.png
# 这里由于进行数据透视,填充了一些 null 值为0,而实际可能用户在当月根本就没有注册,
#这样会误导第一次消费数据的统计,所以写一个函数来处理
def active_status(data):
status=[]
# 数据一共有18个月份,每次输入一行数据,这样进行逐月判断
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
若本月没有消费,这里只是和上个月判断是否注册,有缺陷,可以判断是否存在就可以了
若之前是未注册,则依旧为未注册
若之前有消费,则为流失/不活跃
其他情况,为未注册
若本月有消费
若是第一次消费,则为新用户
如果之前有过消费,则上个月为不活跃,则为回流
如果上个月为未注册,则为新用户
初次之外,为活跃
return:回流
new:新客
unreg:未注册
active:活跃
purchase_stats=df_purchase.apply(lambda x: pd.Series(active_status(x),index=df_purchase.columns),axis=1)
purchase_stats.head()
image.png
# 这里把未注册的替换为空值,这样 count 计算时不会计算到
# 得到每个月的用户分布
purchase_stats_ct=purchase_stats.replace('unreg',np.NaN).apply(lambda x: x.value_counts())
purchase_stats_ct
image.png
returnratee=purchase_stats_ct.apply(lambda x:x/x.sum(),axis=0)
returnratee
image.png
purchase_stats_ct_info = purchase_stats_ct.fillna(0).T
purchase_stats_ct_info
image.png
将处理过的数据,导入到Excel表格中
purchase_stats_ct_info.index = purchase_stats_ct_info.index.astype(str)
purchase_stats_ct_info.to_excel(r'.\临时表\用户分层-新、活跃、流失、回流.xlsx')
image.png
purchase_stats_ct_T = purchase_stats_ct.fillna(0).T.apply(lambda x:x/x.sum(),axis=1)
purchase_stats_ct_T
image.png
- 用户购买周期(按订单)
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
order_diff.head(10)
image.png
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:])
order_diff_info_hist
image.png
order_diff_info_hist = order_diff_info_hist.fillna(10)
order_diff_info_hist.to_excel(r'.\临时表\用户购买周期时间差频率直方图.xlsx')
image.png
订单周期呈指数分布
用户的平均购买周期是68天
绝大部分用户的购买周期都低于100天
- 用户生命周期(按第一次和最后一次消费)
user_life.head()
image.png
# 用户生命周期(按第一次和最后一次消费)
(user_life['max']-user_life['min']).describe()
image.png
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_hist = pd.cut(user_life_info,bins=user_life_lst,labels=user_life_lst[1:])
user_life_info_hist
image.png
user_life_info_hist_2 = user_life_info_hist.fillna(10)
user_life_info_hist_2.to_excel(r'.\临时表\用户生命周期频率直方图.xlsx')
image.png
用户的生命周期受只购买一次的用户影响比较厉害(可以排除)
用户均消费134天,中位数仅0天
user_life["差值"]=(user_life["max"] - user_life["min"])/np.timedelta64(1,"D")
user_life.head(5)
image.png
user_life_info_hist
image.png
user_life_info_hist.to_excel(r'.\临时表\用户生命周期频率直方图(忽略一次购买).xlsx')
image.png
4.复购率和回购率分析
- 复购率
- 自然月内,购买多次的用户占比(即,购买了两次以上)
- 回购率
- 曾经购买过的用户在某一时期的再次购买的占比(可能是在三个月内)
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
# 区分一个,和一个以上的情况,以便于计算复购率,大于1为1,等于0 为0,等于1为0
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
purchase_r.head()
image.png
purchase_r_reshop = (purchase_r.sum()/purchase_r.count()).reset_index(name = 'reshop')
purchase_r_reshop
image.png
purchase_r_reshop['month'] = purchase_r_reshop['month'].astype(str)
purchase_r_reshop.to_excel(r'.\临时表\复购人数与总消费人数比例.xlsx')
image.png
复购率稳定在20%所有,前一个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致复购率降低
- 回购率,知道是否消费就可以了
df_purchase.head()
image.png
# 需要使用函数来判断是否回购:当月消费过的用户下个月也消费了叫做回购,这个定义可以改变
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
indexs=df['month'].sort_values().astype('str').unique()
purchase_b = df_purchase.apply(lambda x :pd.Series(purchase_back(x),index = indexs),axis =1)
purchase_b.head()
purchase_b_backshop = purchase_b.sum()/purchase_b.count()
purchase_b_backshop.index = purchase_b_backshop.index.astype(str)
purchase_b_backshop.to_excel(r'.\临时表\回购率.xlsx')
image.png
网友评论