本文对CD案例进行简单分析与总结。
目录:
项目背景
分析目标
分析过程
总结
一丶项目背景
CDNOW是美国的一家网上唱片公司,成立于1994年,后来被贝塔斯曼音乐集团收购。
二丶分析目标
根据CD网站上的消费者消费记录数据,针对分析消费者行为,建立RFM模型,及复购率;回购率;留存率等关键指标。
三丶分析过程
1,数据观察与清洗
2,用户整体数据趋势分析——每月的消费总金额,消费次数,产品购买量,消费人数,用户平均消费金额,用户平均消费次数
3,用户个体消费数据分析——用户消费金额,用户消费金额与用户购买量分布,用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)
4,用户消费行为分析——用户第一次消费时间分布,用户最后一次消费时间分布,新老用户占比,用户分层(RFM模型),用户分类(新用户,回归用户,活跃用户,流失用户),用户购买周期,用户生命周期
5,用户复购率,回购率,留存率分析——复购率,回购率,留存率
1,数据观察与清洗
(1)导入相关包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['KaiTi']
plt.style.use('ggplot']
%matplotlib inline
(2)
导入数据,数据集为TXT文件,以多个不等空格符分隔,没有列名,导入时需要相关处理
columns = ['user_id','order_dt','product','amount']
data = pd.read_csv(r'D:\WeGame\CDNOW_master.txt',sep='\s+',names=columns)
(3)查看数据
data.head()
(4)检查数据信息,查看缺失值,数据类型等
data.info()
order_dt数据类型为int64,需要转化为日期格式,且无缺失数据
data['order_dt'] = pd.to_datetime(data['order_dt'],format = '%Y%m%d')
接下来需要按月分析,这里新增order_month列(以月份为单位的时间列)
data['order_month'] = data.order_dt.values.astype('datetime64[M]') #astype转化格式
(5)数据初步了解
根据上图可以看出:
订单金额(order_amount):平均每笔订单35元,中位数为26,标准差为36,说明存在极值
订单产品量(order_product):平均每笔订单购买产品数为2.4件,中位数为2件,75%的订单产品数为3件,说明大多数用户单笔订单都是2或3件,存在极值干扰。
2丶用户整体数据趋势分析
(1)
data_month = data.groupby('order_month')
fig,axes = plt.subplots(2,2,figsize = (20,10))
axes1,axes2,axes3,axes4 = axes.flatten()
#每月消费总金额曲线
data_month.order_amount.sum().plot(marker = 'o',ax = axes1)
axes1.set_title('每月消费金额')
axes1.set_ylabel('消费金额')
#每月产品购买数量
data_month.order_product.sum().plot(marker = 'o',ax = axes2)
axes2.set_title('每月产品数量')
axes2.set_ylabel('产品数量')
#每月消费人数
data_month.user_id.apply(lambda x:len(x.drop_duplicates())).plot(marker = 'o',ax = axes3)
axes3.set_title('每月消费客户数')
axes3.set_ylabel('客户数')
#每月订单数
data_month.user_id.count().plot(marker = 'o',ax = axes4)
axes4.set_title('每月订单数')
axes4.set_ylabel('订单数')
plt.tight_layout()
由上图表可得,每月销售金额,每月产品数量,每月消费人数,每月订单数前三个月达到最高峰,后续消费平稳,但有明显的下降,产品购买量峰值,达到20000甚至以上,后续月份平均7000左右,订单数最高达到1.2万单、购买客户数在2月份左右就达到峰值9500人左右,但是四月份之后,各项指标出现严重下滑,顾客流失严重。
(2)用户每月平均消费金额与平均消费次数
fig,axes = plt.subplots(1,2,figsize=(12,4))
ax5,ax6 = axes.flatten()
#每月用户平均消费金额
amount = data_month.order_amount.sum() #每月消费总金额
num = data_month.user_id.apply(lambda x:len(x.drop_duplicates())) #每月消费总人数
avg_amount = amount/num
ax5.plot(avg_amount,marker='o')
#avg_amount.plot(marker='o',ax5)
ax5.set_title('用户月均消费')
ax5.set_ylabel('金额')
#每月用户平均消费次数
cons = data_month.user_id.count() #每月消费总次数
avg_con = cons/num
ax6.plot(avg_con,marker = 'o')
#avg_con.plot(marker = 'o',ax6)
ax6.set_title('用户月均消费次数')
ax6.set_ylabel('次数')
plt.tight_layout()
由上图可知,每月用户平均消费金额都在37.5元以上,1997年1月份最低,1998年11月最高,最高值为57元左右,每月用户平均消费次数在1次以上,1997年1月份最低,1997年10月份最低,峰值为1.4次左右
3丶用户个体分析
(1)用户消费金额,产品购买量描述性统计
用户消费平均金额为106,中位数为43,说明存在极值干扰
用户产品平均购买量为7,中位数为3,说明小部分人大量购买了产品,存在极值
(2)用户消费金额与购买量分析
data_user = data.groupby('user_id')
fig,axes = plt.subplots(1,3,figsize = (16,4))
ax1,ax2,ax3 = axes.flatten()
#用户消费金额分布图
data_user.sum().order_amount.plot.hist(bins=20,ax = ax1)
ax1.set_title('用户消费金额分布')
ax1.set_xlabel('金额')
ax1.set_ylabel('人数')
#用户产品购买量分布图
data_user.sum().order_product.plot.hist(bins=20,ax = ax2)
ax2.set_title('用户产品购买量分布')
ax2.set_xlabel('购买量')
ax2.set_ylabel('人数')
#用户消费金额与购买量散点图
data_user.sum().plot.scatter(x = 'order_product',y = 'order_amount',ax = ax3)
ax3.set_title('用户消费金额与购买量散点图')
plt.tight_layout()
由上图可以看出,绝大部分呈现集中趋势,小部分异常值干扰了判断,可以使用过滤操作排除异常。
根据切比雪夫不等式,平均数五个标准差之内,包含了96%的数据,排除4%极值。
#用户消费金额分布图
data_user.sum().query('order_amount<1200').order_amount.plot.hist(bins=20,ax = ax1)
ax1.set_title('用户消费金额分布')
ax1.set_xlabel('金额')
ax1.set_ylabel('人数')
#用户产品购买量分布图
data_user.sum().query('order_product<100').order_product.plot.hist(bins=20,ax = ax2)
ax2.set_title('用户产品购买量分布')
ax2.set_xlabel('购买量')
ax2.set_ylabel('人数')
#用户消费金额与购买量散点图
data_user.sum().query('order_amount<4000').plot.scatter(x = 'order_product',y = 'order_amount',ax = ax3)
ax3.set_title('用户消费金额与购买量散点图')
plt.tight_layout()
大部分用户购买产品数量在05,消费金额在0100之间,散点图可以看出,消费金额与产品购买量呈线性关系,购买的产品数量越多,金额越大。
(3)个体累计消费金额占比
#使用cumsum函数
user_cum = data_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
user_cum.reset_index().order_amount.plot()
plt.xlabel('客户数量')
plt.ylabel('比例')
由图可以看出,极少数一部分人占了高比例,符合市场二八定理,应把营运战略侧重与这一部分人上。
4丶用户消费行为分析
(1)第一次消费时间分布
data_user.min().order_dt.value_counts().plot()
plt.ylabel('客户数量')
plt.xlabel('date')
新用户主要集中在1997-01至1997-03月份,二月份新用户量达到最高,3月之后没有新用户
(2)最后一次消费时间分布
data_user.max().order_dt.value_counts().plot()
plt.ylabel('客户数量')
plt.xlabel('date')
1997-01至1997-03月份客户流失严重,后续流失在40左右,从前面用户第一次消费时间分布图得出的结论,三月份出现断崖式分层,且没有新用户,不断流失,建议改变营运策略。
(3)新老用户占比
a.仅消费一次的用户数量
data_user_onece = data_user.order_dt.agg(['max','min'])
#第一次消费跟最后一次消费时间相同,则表示仅消费一次。
(data_user_onece['max'] == data_user_onece['min']).value_counts()
可以看出,新用户达到50%。
b.每月新用户占比
#以月份跟用户进行分组,获取每月不同用户的第一次消费跟最后一次消费日期
user_news = data.groupby(['order_month','user_id']).order_dt.agg(['max','min']).reset_index()
#添加每月新客户字段is_new
user_news['is_new'] = (user_news['max'] == user_news['min'])
#获取占比
new = user_news.groupby('order_month').is_new.apply(lambda x:x.value_counts()/x.count()).reset_index()
#进行可视化
new[new['level_1']].plot(x='order_month',y = 'is_new')
plt.ylabel('比例')
1月份新用户占比高达90%以上,后续出现下降,但趋势比较平稳,维持在81%左右。
5丶用户分层
(1)RFM模型
rfm = data.pivot_table(index = 'user_id',
values = ['order_amount','order_product','order_dt'],
aggfunc = {'order_amount':'sum',
'order_product':'count',
'order_dt':'max'})
#这里取order_dt的最大值作为rencency的参照
rfm['R'] = -(rfm['order_dt'] - rfm['order_dt'].max())/np.timedelta64(1,'D')
#F:消费频率
#M:消费金额
rfm.rename(columns = {'order_amount':'M','order_product':'F'},inplace= True)
rfm.head()
#创建函数
def rfm_fun(x):
a = x.apply(lambda x:'1' if x>0 else '0')
#字符串拼接
concat = a.R + a.F + a.M
#设立标签
b = {
'111':'重要价值客户',
'011':'重要保持客户',
'101':'重要发展客户',
'001':'重要挽留客户',
'110':'一般价值客户',
'100':'一般发展客户',
'010':'一般保持客户',
'000':'一般挽留客户'
}
result = b[concat]
return result
rfm['label'] = rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_fun,axis=1)
使用饼图进行可视化,查看不同类型客户占比情况
#不同客户类型消费金额,频率
rfm.groupby('label').sum()
8种类型中,011(重要保持客户)消费金额最高,其次为100(一般发展客户)。
#不同客户类型的人数
rfm.groupby('label').count()
8种客户中,一般发展客户(100)的人数最多,其次是重要保持客户(011)。
从RFM分层可知,大部分用户为一般发展客户,但是这是由于极值的影响,所以RFM的划分标准应该以业务为准,也可以通过切比雪夫定理去除极值后求均值,并且 RFM 的各个划分标准可以都不一样.
a.尽量用小部分的用户覆盖大部分的额度
b.不要为了数据好看划分等级
(2)用户分类
#用户分类
user_type = data.pivot_table(index = 'user_id',
columns = 'order_month',
values = 'order_product',
aggfunc = 'count')
数据整理之后,开始分类
#建立函数
def type_states(x):
#创建一个空列表接受值
states = []
#有18个月份,遍历每个用户每个月的消费情况,根据不同情况返回不同值即可
for i in range(18):
#这个月没有消费的情况
if x[i] == 0:
#判断之前月份有无消费,若有,则
if len(states)>0:
#在判断之前是否已经注册了帐号,若没有。则
if states[i-1] == 'unreg':
states.append('unreg')
#若已经注册,则为不活跃客户
else:
states.append('unactive')
#之前没有消费,这个月也没有,则为没有注册
else:
states.append('unreg')
#这个月有消费,则看之前有无消费
else:
#之前没有消费,则为新用户
if len(states) == 0:
states.append('new')
#之前也有消费,则为活跃用户
else:
#之前有消费,但上个月没有,这个月有,(即上个月是不活跃用户)则为回归用户
if states[i-1] == 'unactive':
states.append('return')
#之前没有注册,则为新用户
elif states[i-1] == 'unreg':
states.append('new')
#否则为活跃用户
else:
states.append('active')
return states
u_types = user_type_zero.apply(type_states,axis=1)
u_types.head()
统计不同类型用户每月的数量
#把未注册的数据转换成NaN,这样统计数据时便不会计算在内
user_statu = u_types.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x))
user_statu
将NULL填充,进行可视化
#根据统计结果作面积图
user_statu.fillna(0).T.plot.area()
蓝色是新用户,前3个月大量涌入,后面没有新增。
红色是活跃用户,前几个月较多,后面有所下降。
紫色是回流用户,数量趋于稳定,每月1000多。
灰色是流失/不活跃用户,数量非常多,基本上每月都在20000以上。
6丶用户生命周期
(1)用户购买周期统计描述
使用shift()函数进行日期错位计算,目的是计算出用户两次购买的时间间隔天数(周期)
#用户购买周期分析
user_diff = data_user.apply(lambda x:x.order_dt-x.order_dt.shift())
user_diff.head(10)
(user_diff/np.timedelta64(1,'D')).hist(bins=20)
plt.xlabel('购买周期')
用户平均购买间隔为68天
绝大部分购买周期在100内
订单周期呈指数分布
7丶用户生命周期
(1)用户生命周期统计描述
根据用户第一次购买与最后一次购买时间进行分析
data_user_onece = data_user.order_dt.agg(['max','min'])
data_user_onece.head()
#用户生命周期分布
(data_user_onece['max']-data_user_onece['min']).describe()
((data_user_onece['max']-data_user_onece['min'])/np.timedelta64(1,'D')).hist(bins=20)
用户均消费134天,中位数0天,(0天表示用户只进行一次购买),需要排除这一部分的影响
user_life = ((data_user_onece['max']-data_user_onece['min'])/np.timedelta64(1,'D'))
user_life[user_life>0].hist(bins=20)
对排除只购买一次用户结果进行统计描述
user_life[user_life>0].describe()
用户平均生命周期276天,中位数302天。
8丶复购率,回购率,留存率
(1)复购率
#用户分类,数据中有NaN值,整理数据
user_type_zero = data.pivot_table(index = 'user_id',
columns = 'order_month',
values = 'order_product',
aggfunc = 'count').fillna(0)
#区分一次跟多次,为了方便统计,多次购买为1,一次购买为0,没有购买为NaN(不统计在内)
user_r = user_type_zero.applymap(lambda x:1 if x>1 else np.NaN if x ==0 else 0)
user_r.head()
#复购人数/总购买人数
user_r.apply(lambda x:x.sum()/x.count()).plot(figsize=(10,4))#NaN不统计在内
plt.ylabel('复购率')
plt.xlabel('月份')
前三月复购率低,是因为大部分为只购买一次的新用户
后续每月复购率在21%左右波动
(2)回购率
#回购率
#为了方便统计,将大于等于1为1,其余为0
user_b = user_type_zero.applymap(lambda x:1 if x>=1 else 0)
user_b.head()
#建立函数,判断是否为回购
def back_buy(x):
back = []
#遍历每一个月分,最后一个月没有回购,所以这里一共为17个月
for i in range(17):
#这个月买了
if x[i] == 1:
#下个月没买
if x[i+1] == 0:
#则为没有回购
back.append(0)
if x[i+1] == 1:
#下个月也买了,则为回购
back.append(1)
else:
#本月没有消费,则赋予空值,不参加计算
back.append(np.NaN)
#滴18个月没有回购,赋予空值
back.append(np.NaN)
return back
back_b = user_b.apply(back_buy,axis=1)
back_b.head()
(back_b.sum()/back_b.count()).plot(figsize=(10,4))
回购率稳定在30%左右,前3个月因为有大量新用户涌入,而这批用户只购买了一次,所以导致回购率较低。
(3)留存率
a.数据整合
#获取用户第一次购买时间
df = data.groupby('user_id').order_dt.min().reset_index()
#连接两个DataFrame
df_lc = pd.merge(data,df,on = 'user_id',how = 'inner')
#查看数据
df_lc.head()
#获取时间差
df_lc['cha'] = df_lc['order_dt_x']-df_lc['order_dt_y']
#使用列表推导式,将时间差转化成int形式
df_lc['days'] = [x.days for x in df_lc['cha']]
#使用cut函数进行天数分段
bins = [3,7,30,60,90,120,160,180,365,544]
df_lc['lc_days'] = pd.cut(df_lc['days'],bins=bins)
df_lc.head()
#使用数据透视表进行不同分段数值
df2 = pd.pivot_table(df_lc,
index = 'user_id',
columns = 'lc_days',
values = 'order_amount',
aggfunc = {'order_amount':'sum'})
df2.head()
#对不同分段留存天数的消费金额进行可视化
plt.figure(figsize = (12,6))
df2.sum().plot.bar(x = 'lc_days')
plt.ylabel('总金额')
plt.xlabel('留存天数')
plt.title('各留存时间段内的消费总金额')
#各留存时间段内的消费平均金额
df2.mean().plot.bar(x = 'lc_days')
plt.ylabel('总金额')
plt.xlabel('留存天数')
plt.title('各留存时间段内的消费平均金额')
b.计算留存率
#为了方便接下来计算,将数值转为1和0
df3 = df_lc.pivot_table(index = 'user_id',columns = 'lc_days',values = 'order_amount',aggfunc = sum).applymap(lambda x:1 if x>0 else 0)
#计算留存率
(df3.sum()/df3.count()).plot()
8丶结论与总结