CDNow曾经是一家在线音乐零售平台,后被德国波泰尔斯曼娱乐集团公司出资收购,其资产总价值在最辉煌时曾超过10亿美元。本文主要通过分析CDNow网站的用户购买明细来分析该网站的用户消费行为,使运营部门在营销时更加具有针对性,从而节省成本,提升效率。
数据来源
本次分析数据来源CDNow网站的用户在1997年1月1日至1998年6月30日期间内购买CD交易明细。
数据下载地址
——提取码: va7h
数据集一共有用户ID,购买日期,订单数,订单金额四个字段。
import numpy as np
import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
columns=['user_id','order_dt','order_products','order_amount']
data=pd.read_table('./CDNOW_master.txt',names=columns,sep='\s+')
data.head()
|
user_id |
order_dt |
order_products |
order_amount |
0 |
1 |
19970101 |
1 |
11.77 |
1 |
2 |
19970112 |
1 |
12.00 |
2 |
2 |
19970112 |
5 |
77.00 |
3 |
3 |
19970102 |
2 |
20.76 |
4 |
3 |
19970330 |
2 |
20.76 |
data.describe()
|
user_id |
order_dt |
order_products |
order_amount |
count |
69659.000000 |
6.965900e+04 |
69659.000000 |
69659.000000 |
mean |
11470.854592 |
1.997228e+07 |
2.410040 |
35.893648 |
std |
6819.904848 |
3.837735e+03 |
2.333924 |
36.281942 |
min |
1.000000 |
1.997010e+07 |
1.000000 |
0.000000 |
25% |
5506.000000 |
1.997022e+07 |
1.000000 |
14.490000 |
50% |
11410.000000 |
1.997042e+07 |
2.000000 |
25.980000 |
75% |
17273.000000 |
1.997111e+07 |
3.000000 |
43.700000 |
max |
23570.000000 |
1.998063e+07 |
99.000000 |
1286.010000 |
从上可知,看数据6关键指标:样本数:69659,最小值:0,最大值:1286,平均值:36,方差36,中位数:26。平均数大于中位数,右偏分布。没有空缺数据,不用清理直接分析,注意到日期数据是整数型,需要转换成时间格式。
data['order_date']=pd.to_datetime(data.order_dt,format='%Y%m%d')
data['month']=data.order_date.values.astype('datetime64[M]')
user_grouped=data.groupby('user_id').sum()
user_grouped
|
order_dt |
order_products |
order_amount |
user_id |
|
|
|
1 |
19970101 |
1 |
11.77 |
2 |
39940224 |
6 |
89.00 |
3 |
119833602 |
16 |
156.46 |
4 |
79882233 |
7 |
100.50 |
5 |
219686137 |
29 |
385.61 |
6 |
19970101 |
1 |
20.99 |
7 |
59921434 |
18 |
264.67 |
8 |
159775420 |
18 |
197.66 |
9 |
59921222 |
6 |
95.85 |
10 |
19970121 |
3 |
39.31 |
11 |
79890766 |
4 |
58.55 |
12 |
19970101 |
4 |
57.06 |
13 |
19970101 |
4 |
72.94 |
14 |
19970101 |
2 |
29.92 |
15 |
19970101 |
4 |
52.87 |
16 |
79882450 |
8 |
79.87 |
17 |
19970101 |
5 |
73.22 |
18 |
19970104 |
1 |
14.96 |
19 |
39940711 |
11 |
175.12 |
20 |
39940219 |
46 |
653.01 |
21 |
39940214 |
4 |
75.11 |
22 |
19970101 |
1 |
14.37 |
23 |
19970101 |
2 |
24.74 |
24 |
39950221 |
4 |
57.77 |
25 |
159804813 |
12 |
137.53 |
26 |
39940227 |
6 |
102.69 |
27 |
39940219 |
10 |
135.87 |
28 |
59910536 |
7 |
90.99 |
29 |
239656335 |
28 |
435.81 |
30 |
39940315 |
2 |
28.34 |
... |
... |
... |
... |
23541 |
39940727 |
2 |
57.34 |
23542 |
19970325 |
5 |
77.43 |
23543 |
19970325 |
2 |
50.76 |
23544 |
59920964 |
12 |
134.63 |
23545 |
19970325 |
1 |
24.99 |
23546 |
19970325 |
1 |
13.97 |
23547 |
39940732 |
2 |
23.54 |
23548 |
19970325 |
2 |
23.54 |
23549 |
19970325 |
2 |
27.13 |
23550 |
19970325 |
2 |
25.28 |
23551 |
119824382 |
12 |
264.63 |
23552 |
39940728 |
4 |
49.38 |
23553 |
39940653 |
8 |
98.58 |
23554 |
39950526 |
3 |
36.37 |
23555 |
99873685 |
14 |
189.18 |
23556 |
139814017 |
15 |
203.00 |
23557 |
19970325 |
1 |
14.37 |
23558 |
79891692 |
11 |
145.60 |
23559 |
59911470 |
8 |
111.65 |
23560 |
19970325 |
1 |
18.36 |
23561 |
59930982 |
6 |
83.46 |
23562 |
19970325 |
2 |
29.33 |
23563 |
39941329 |
3 |
58.75 |
23564 |
59911976 |
5 |
70.01 |
23565 |
19970325 |
1 |
11.77 |
23566 |
19970325 |
2 |
36.00 |
23567 |
19970325 |
1 |
20.97 |
23568 |
59911152 |
6 |
121.70 |
23569 |
19970325 |
2 |
25.74 |
23570 |
39940651 |
5 |
94.08 |
23570 rows × 3 columns
user_grouped.describe()
|
order_dt |
order_products |
order_amount |
count |
2.357000e+04 |
23570.000000 |
23570.000000 |
mean |
5.902627e+07 |
7.122656 |
106.080426 |
std |
9.460684e+07 |
16.983531 |
240.925195 |
min |
1.997010e+07 |
1.000000 |
0.000000 |
25% |
1.997021e+07 |
1.000000 |
19.970000 |
50% |
1.997032e+07 |
3.000000 |
43.395000 |
75% |
5.992125e+07 |
7.000000 |
106.475000 |
max |
4.334408e+09 |
1033.000000 |
13990.930000 |
原表只按订单来记录,现按用户分组看数据集概述。
从购买数量(单位:PC)角度看,用户数量:23570,平均值购买7张,最多购买1033张,属于狂热用户,中位数3张,平均值大于中位数,是右偏分布,存在小部分购买多张碟的用户。
从消费金额(单位:美元)角度看,平均数106,最高值:13990,属于土豪用户,中位数43,平均值大于中位数,是右偏分布,存在小部分高消费用户。
plt.style.use('ggplot')
plt.rcParams['font.sans-serif']=['SimHei']
ax=data.groupby('month').order_products.sum().plot()
ax.set_xlabel('月份')
ax.set_ylabel('数量(张)')
ax.set_title('不同月份的用户购买数量')
plt.show()
ax=data.groupby('month').order_amount.sum().plot()
ax.set_xlabel('月份')
ax.set_ylabel('消费金额')
ax.set_title('不同月份的用户消费金额')
plt.show()
由图片可知,无论是消费金额还是CD碟数都呈现相同的趋势,而且前三月数据都呈现出异常状态,由于我们不知道原数据到底从何获得,只能做出这三个月有促销活动,抑或是这是新开的店之类的,前三个月大多都是新人之类的假设,我们不得而知,继续往下看。
ax=data.groupby('user_id').sum().plot.scatter('order_amount','order_products')
ax.set_xlabel('消费金额')
ax.set_ylabel('数量')
ax.set_title('每个用户消费金额与购买个数的关系')
plt.show()
每个用户的消费金额跟购买数量呈现一定的线性关系。
plt.figure(figsize=(12,4))
ax=plt.subplot(121)
ax=data.groupby('user_id').order_products.sum().plot.hist(bins=50)
ax.set_xlabel('数量')
ax.set_ylabel('用户数')
ax.set_xlim(0,150)
ax.set_title('用户购买数量分布直方图')
ax1=plt.subplot(122)
ax1=data.groupby('user_id').order_amount.sum().plot.hist(bins=50)
ax1.set_xlabel('消费金额')
ax1.set_ylabel('用户人数')
ax1.set_xlim(0,2000)
ax1.set_title('用户消费金额分布直方图')
plt.show(ax,ax1)
我们可以从直方图看到,大部分用户的消费能力确实不高,整个计算周期里的购买碟数在20张以内,消费金额在250美金以内,再图上几乎看不到高消费用户。
data.groupby('user_id').month.min().value_counts()
1997-02-01 8476
1997-01-01 7846
1997-03-01 7248
Name: month, dtype: int64
data.groupby('user_id').month.max().value_counts()
1997-02-01 4912
1997-03-01 4478
1997-01-01 4192
1998-06-01 1506
1998-05-01 1042
1998-03-01 993
1998-04-01 769
1997-04-01 677
1997-12-01 620
1997-11-01 609
1998-02-01 550
1998-01-01 514
1997-06-01 499
1997-07-01 493
1997-05-01 480
1997-10-01 455
1997-09-01 397
1997-08-01 384
Name: month, dtype: int64
我们可以看到,貌似每个用户的第一次购买记录都是前三个月,最后次购买记录也是前三个月居前三。由此我们可以推测,这份数据集大概是选择了这三个月时间段消费的用户在后面的18个月的追踪记录数据。也能很好的解释我们之前看到的前三个月无论是消费金额还是购买数量的异常。
pivoted_count=data.pivot_table(index='user_id',columns='month',values='order_dt',aggfunc='count').fillna(0)
pivoted_count.head()
month |
1997-01-01 |
1997-02-01 |
1997-03-01 |
1997-04-01 |
1997-05-01 |
1997-06-01 |
1997-07-01 |
1997-08-01 |
1997-09-01 |
1997-10-01 |
1997-11-01 |
1997-12-01 |
1998-01-01 |
1998-02-01 |
1998-03-01 |
1998-04-01 |
1998-05-01 |
1998-06-01 |
user_id |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
1.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
2 |
2.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
3 |
1.0 |
0.0 |
1.0 |
1.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
2.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
1.0 |
0.0 |
4 |
2.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
1.0 |
0.0 |
0.0 |
0.0 |
1.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
5 |
2.0 |
1.0 |
0.0 |
1.0 |
1.0 |
1.0 |
1.0 |
0.0 |
1.0 |
0.0 |
0.0 |
2.0 |
1.0 |
0.0 |
0.0 |
0.0 |
0.0 |
0.0 |
pivoted_count_transf =pivoted_count.applymap(lambda x:1 if x>1 else np.NAN if x ==0 else 0)
pivoted_count_transf.head()
month |
1997-01-01 |
1997-02-01 |
1997-03-01 |
1997-04-01 |
1997-05-01 |
1997-06-01 |
1997-07-01 |
1997-08-01 |
1997-09-01 |
1997-10-01 |
1997-11-01 |
1997-12-01 |
1998-01-01 |
1998-02-01 |
1998-03-01 |
1998-04-01 |
1998-05-01 |
1998-06-01 |
user_id |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
2 |
1.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
3 |
0.0 |
NaN |
0.0 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
1.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
0.0 |
NaN |
4 |
1.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
0.0 |
NaN |
NaN |
NaN |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
5 |
1.0 |
0.0 |
NaN |
0.0 |
0.0 |
0.0 |
0.0 |
NaN |
0.0 |
NaN |
NaN |
1.0 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
消费行为中的复购率和回购率
复购率:复购率的定义是在某时间窗口内消费两次及以上的用户在总消费用户中占比。这里的时间窗口是月,如果一个用户在同一天下了两笔订单,这里也将他算作复购用户。
db=pivoted_count_transf.sum()/pivoted_count_transf.count()
ax=db.plot(figsize=(10,4))
ax.set_xlabel('时间(月)')
ax.set_ylabel('百分比(%)')
ax.set_title('每月用户复购率图')
plt.show()
从图中我们可以看出复购率因为大量新用户加入的关系,不断扩大分母,导致初期的复购率不怎么高,譬如刚开始的97年一月份复购率只有6%左右,而后直线上升,到第四个月后到达最高点,因为这次数据集是前三个月新客跟踪数据,三个月后,没有新客的加入,可以认定是前三个月的客户大浪淘沙剩下的还有购买欲望的老客,这时候的复购率比较稳定,在20%左右徘徊。
fig,ax=plt.subplots(figsize=(10,4))
ax.plot(pivoted_count_transf.sum())
ax.plot(pivoted_count_transf.count())
ax.set_xlabel('时间(月)')
ax.set_ylabel('用户数(人)')
ax.set_title('每月消费和二次消费以上用户数')
legends=['二次消费以上人数','消费人数']
ax.legend(legends,loc='upper right')
plt.show()
如图所示,三个月后,用户迅速沉淀,前三个月的新客大概在1891正负387左右人数继续消费波动,而二次消费的客户则很稳定,曲线也趋近直线,大概在400人左右波动。这部分的客户放到现实中应该是重点维护的优质客户。
回购率:回购率是某一个时间窗口内消费的用户,在下一个时间窗口仍旧消费的占比。我前一月消费用户1000,后一个月他们中仍有200个消费,则回购率是20%。由于牵扯两个时间窗口的计算,所以较之复购率稍稍复杂点。
pivoted_amount=data.pivot_table(index='user_id',columns='month',values='order_dt',aggfunc='count').fillna(0)
pivoted_purchase=pivoted_amount.applymap(lambda x:1 if x>0 else 0)
pivoted_purchase.head()
month |
1997-01-01 |
1997-02-01 |
1997-03-01 |
1997-04-01 |
1997-05-01 |
1997-06-01 |
1997-07-01 |
1997-08-01 |
1997-09-01 |
1997-10-01 |
1997-11-01 |
1997-12-01 |
1998-01-01 |
1998-02-01 |
1998-03-01 |
1998-04-01 |
1998-05-01 |
1998-06-01 |
user_id |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3 |
1 |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
4 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
5 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
def purchase_return(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)
status.append(np.NaN)
return pd.Series(status)
pivoted_purchase_return=pivoted_purchase.apply(purchase_return,axis=1)
pivoted_purchase_return.head()
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
user_id |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
2 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
3 |
0.0 |
NaN |
1.0 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
0.0 |
NaN |
4 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
0.0 |
NaN |
NaN |
NaN |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
5 |
1.0 |
0.0 |
NaN |
1.0 |
1.0 |
1.0 |
0.0 |
NaN |
0.0 |
NaN |
NaN |
1.0 |
0.0 |
NaN |
NaN |
NaN |
NaN |
NaN |
columns=data.month.sort_values().unique()
pivoted_purchase_return.columns = columns
ax=(pivoted_purchase_return.sum()/pivoted_purchase_return.count()).plot(figsize=(10,4))
ax.set_xlabel('时间(月)')
ax.set_ylabel('百分比(%)')
ax.set_title('十八个月内用户回购率图')
plt.show()
fig,ax=plt.subplots(figsize=(10,4))
ax.plot(pivoted_purchase_return.sum())
ax.plot(pivoted_purchase_return.count())
ax.set_xlabel('时间(月)')
ax.set_ylabel('用户数(人)')
legends = ['每月回购人数','每月消费人数']
ax.legend(legends,loc = 'upper right')
plt.show()
由图可知,用户的回购率大于复购率,约在30%左右正负五个点波动,看人数分布表则发现回购人数趋近稳定,那么波动则是偶尔月份间的消费人数基数的变动,可能有营销者淡旺季,但是这部分回购用户的消费行为大抵稳定,应该跟之前每月复购的用户有一定重合,是属于优质用户。对回购率和复购率进行综合分析,可以得出,新客的整体质量低于老客,老客的忠诚度(回购率)表现较好,消费频次稍次,这是这个网站的用户消费特征
(3)消费行为中的用户分层
我们按照用户的消费行为,简单划分成几个维度:新用户、活跃用户、不活跃用户、回流用户。
新用户(new):新用户的定义是第一次消费的用户。
活跃用户(active):即连续两个时间窗口都消费过的用户。
不活跃用户(unactive):不活跃用户则是时间窗口内没有消费过的活跃用户,即一二月份都消费过,三月份没消费过。
回流用户(return):回流用户是在上一个窗口中没有消费,而在当前时间窗口内有过消费。
def active_status(date):
status=[]
for i in range(18):
if date[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)
pivoted_purchase_status=pivoted_purchase.apply(active_status,axis=1)
pivoted_purchase_status.columns=columns
pivoted_purchase_status.head()
|
1997-01-01 |
1997-02-01 |
1997-03-01 |
1997-04-01 |
1997-05-01 |
1997-06-01 |
1997-07-01 |
1997-08-01 |
1997-09-01 |
1997-10-01 |
1997-11-01 |
1997-12-01 |
1998-01-01 |
1998-02-01 |
1998-03-01 |
1998-04-01 |
1998-05-01 |
1998-06-01 |
user_id |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
new |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
2 |
new |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
3 |
new |
unactive |
return |
active |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
return |
unactive |
unactive |
unactive |
unactive |
unactive |
return |
unactive |
4 |
new |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
return |
unactive |
unactive |
unactive |
return |
unactive |
unactive |
unactive |
unactive |
unactive |
unactive |
5 |
new |
active |
unactive |
return |
active |
active |
active |
unactive |
return |
unactive |
unactive |
return |
active |
unactive |
unactive |
unactive |
unactive |
unactive |
这个函数主要分为两部分的判断,以本月是否消费为界。本月若没有消费,则判断是不是第一个月,是的话直接返回unreg(不辨别),不是第一个月的话,我们就可以找出前一月标签,前一个月还是不辨别的话,就返回unreg(不辨别),因为这个月还是没消费,如果前一个月是新课还是活跃用户,还是回流用户,因为这个月都没消费,重新定义为unactive(不活跃用户)。
本月若有消费,则需要判断是不是第一次消费,若是第一月的消费,因为没有前一个月数据,所以直接是new(新客)。接下来就是不是第一个月又有消费的情况了,此时可以找出前一个月的标签,如果前一个月是不活跃,则返回return(回流用户),如果是unreg,则是第一次消费,返回new(新客),剩下的就是前一个月是新客或者是活跃用户了,此时都返回active(活跃用户)。
pivoted_status_counts=pivoted_purchase_status.replace('unreg',np.NaN).apply(pd.value_counts)
pivoted_status_counts.head()
|
1997-01-01 |
1997-02-01 |
1997-03-01 |
1997-04-01 |
1997-05-01 |
1997-06-01 |
1997-07-01 |
1997-08-01 |
1997-09-01 |
1997-10-01 |
1997-11-01 |
1997-12-01 |
1998-01-01 |
1998-02-01 |
1998-03-01 |
1998-04-01 |
1998-05-01 |
1998-06-01 |
active |
NaN |
1157.0 |
1681 |
1773.0 |
852.0 |
747.0 |
746.0 |
604.0 |
528.0 |
532.0 |
624.0 |
632.0 |
512.0 |
472.0 |
571.0 |
518.0 |
459.0 |
446.0 |
new |
7846.0 |
8476.0 |
7248 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
return |
NaN |
NaN |
595 |
1049.0 |
1362.0 |
1592.0 |
1434.0 |
1168.0 |
1211.0 |
1307.0 |
1404.0 |
1232.0 |
1025.0 |
1079.0 |
1489.0 |
919.0 |
1029.0 |
1060.0 |
unactive |
NaN |
6689.0 |
14046 |
20748.0 |
21356.0 |
21231.0 |
21390.0 |
21798.0 |
21831.0 |
21731.0 |
21542.0 |
21706.0 |
22033.0 |
22019.0 |
21510.0 |
22133.0 |
22082.0 |
22064.0 |
ax=pivoted_status_counts.fillna(0).T.plot.area(figsize=(12,6))
ax.set_xlabel('时间(月)')
ax.set_ylabel('用户数(人)')
ax.set_title('每月各类用户类型占比面积图')
ax.legend(loc='upper left')
plt.show()
由图可知,黑色的不活跃用户始终是占据大头的,这也跟我们之前的图表结果相符,其次红色代表的活跃用户非常稳定,是属于核心用户,以及紫色的回流用户,这两个分层相加,大抵是当月的消费用户人数的占比,与我们之前的复购率和回购率的表结果一致,这两层的用户大抵接近两千人左右。
回流占比:某个时间窗口内回流用户在总用户中的占比。
活跃占比:某个时间窗口内活跃用户在总用户中的占比。
return_rate=pivoted_status_counts.apply(lambda x:x/x.sum())
ax=return_rate.loc['return'].plot(figsize=(12,6))
ax.set_xlabel('时间(月)')
ax.set_ylabel('百分数(%)')
ax.set_title('每月回流用户占比')
plt.show()
ax = return_rate.loc['active'].plot(figsize = (12,6))
ax.set_xlabel('时间(月)')
ax.set_ylabel('百分数(%)')
ax.set_title('每月活跃用户占比')
plt.show()
由图可知,用户回流占比在5%~8%之间波动,趋势向下,有客户流失的预警。用户活跃占比在3%~5%间,作为连续消费用户,质量在一定程度上高于回流用户。结合回流用户和活跃用户看,在后期的消费用户中,60%是回流用户,40%是活跃用户,整体质量还好,但是针对这两个分层依旧有改进的空间,可以继续细化数据。
(4)用户质量
因为消费行为有明显的二八倾向,我们需要知道高质量用户为消费贡献了多少份额。
user_amount=data.groupby('user_id').order_amount.sum().sort_values().reset_index()
user_amount['amount_cumsum']=user_amount.order_amount.cumsum()
user_amount.tail()
|
user_id |
order_amount |
amount_cumsum |
23565 |
7931 |
6497.18 |
2463822.60 |
23566 |
19339 |
6552.70 |
2470375.30 |
23567 |
7983 |
6973.07 |
2477348.37 |
23568 |
14048 |
8976.33 |
2486324.70 |
23569 |
7592 |
13990.93 |
2500315.63 |
amount_total = user_amount.amount_cumsum.max()
user_amount['prop'] = user_amount.amount_cumsum.apply(lambda x: x / amount_total)
ax = user_amount.prop.plot()
ax.set_xlabel('人数(人)')
ax.set_ylabel('百分数(%)')
ax.set_title('用户累计贡献金额百分比')
plt.show()
由图我们可以清晰的看到,金额排名靠后的15000名用户才贡献不到20%的销售量,而此次数据集的总用户人数为23569人,我们可以进一步再看,排名靠后的20000名用户,贡献了接近40%的消费金额,也就是说后面的3569人接近贡献了60%的消费金额,如果我们换算成金额/人单位,这后面的3569名客户相当于签名20000名客户的8.4倍!这也反应了在消费领域中,狠抓高质量用户是万古不变的道理。
(5)用户生命周期
这里我们定义第一次消费至最后一次消费为整个用户生命。我们需要找出每个用户的第一次消费和最后次消费
user_purchase = data[['user_id','order_products','order_amount','order_date']]
order_date_min=user_purchase.groupby('user_id').order_date.min()
order_date_max=user_purchase.groupby('user_id').order_date.max()
life_time = (order_date_max-order_date_min).reset_index()
display(life_time.head(),life_time.describe())
|
user_id |
order_date |
0 |
1 |
0 days |
1 |
2 |
0 days |
2 |
3 |
511 days |
3 |
4 |
345 days |
4 |
5 |
367 days |
|
user_id |
order_date |
count |
23570.000000 |
23570 |
mean |
11785.500000 |
134 days 20:55:36.987696 |
std |
6804.217258 |
180 days 13:46:43.039788 |
min |
1.000000 |
0 days 00:00:00 |
25% |
5893.250000 |
0 days 00:00:00 |
50% |
11785.500000 |
0 days 00:00:00 |
75% |
17677.750000 |
294 days 00:00:00 |
max |
23570.000000 |
544 days 00:00:00 |
由描述可知,所有用户的平均生命周期是134天,中位数是0天,也就是存在一半的用户是第一次消费就是最后次消费,也就是我们前面所说的低质量客户。而最大是544天,相当于我们这个数据集的总天数,说明这用户是从开始到最后都有消费意愿的高质量用户。
因为数据中的用户都是前三个月第一次消费,所以这里的生命周期代表的是1月~3月用户的生命周期。因为这里数据只截取到了18个月为止,这时间过后,用户仍然会持续消费,所以理论上,用户的平均生命周期还会增长。接下来我们看下直方图的分布,更加直观。
life_time['life_time'] = life_time.order_date/np.timedelta64(1,'D')
ax=life_time.life_time.plot.hist(bins =100,figsize = (12,6))
ax.set_xlabel('天数(天)')
ax.set_ylabel('人数(人)')
ax.set_title('二次消费以上用户的生命周期直方图')
plt.show()
life_time['life_time'] = life_time.order_date/np.timedelta64(1,'D')
ax=life_time[life_time.life_time>0].life_time.plot.hist(bins =100,figsize = (12,6))
ax.set_xlabel('天数(天)')
ax.set_ylabel('人数(人)')
ax.set_title('二次消费以上用户的生命周期直方图')
plt.show()
看图我们可以出图像呈双峰结构,部分质量差的用户,虽然消费了两次,但是仍旧无法持续,此时若想提高点用户转化率,应在用户首次消费30天内应该尽量引导,不然就会流失。少部分用户集中在50天~300天,属于普通型的生命周期,有一定忠诚度。高质量用户的生命周期,集中在400天以后,这时候途中人数又逐渐上升,这已经属于高忠诚用户了,尽量维护好这批高质量用户。
消费两次以上用户的生命周期接近消费只有一次用户的两倍,所以如何在用户首次消费后进行有效的引导促使其多次消费,可以有效的提高用户的生命周期和用户质量
len(life_time[life_time.life_time > 400])
3651
我们同时计算了用户生命周期大于400天的人数,为3651人,跟我们之前的高贡献率人数非常接近,暗示我们这批人应该接近同一批人,也就是说消费金额高的用户往往他们的生命周期也越长,这也符合我们的常规认识。
user_purchase_retention = pd.merge(left = user_purchase, right = order_date_min.reset_index(), how = 'inner', on = 'user_id', suffixes=('', '_min'))
user_purchase_retention['order_date_diff'] = user_purchase_retention.order_date-user_purchase_retention.order_date_min
user_purchase_retention['date_diff'] = user_purchase_retention.order_date_diff.apply(lambda x: x/np.timedelta64(1,'D'))
这里用到merge函数,它和SQL中的join差不多,用来将两个DataFrame进行合并。我们选择了inner 的方式,对标inner join。即只合并能对应得上的数据。这里以on=user_id为对应标准。这里merge的目的是将用户消费行为和第一次消费时间对应上,形成一个新的DataFrame。suffxes参数是如果合并的内容中有重名column,加上后缀。
然后将order_date和order_date_min相减。获得一个新的列,为用户每一次消费距第一次消费的时间差值,然后在整除np.timedelta64(1,‘D’)来获得日期差数值。
user_purchase_retention.head()
|
user_id |
order_products |
order_amount |
order_date |
order_date_min |
order_date_diff |
date_diff |
0 |
1 |
1 |
11.77 |
1997-01-01 |
1997-01-01 |
0 days |
0.0 |
1 |
2 |
1 |
12.00 |
1997-01-12 |
1997-01-12 |
0 days |
0.0 |
2 |
2 |
5 |
77.00 |
1997-01-12 |
1997-01-12 |
0 days |
0.0 |
3 |
3 |
2 |
20.76 |
1997-01-02 |
1997-01-02 |
0 days |
0.0 |
4 |
3 |
2 |
20.76 |
1997-03-30 |
1997-01-02 |
87 days |
87.0 |
def convert(date):
if date==0.0:
return np.NaN
elif 0<date<=30:
return '(0,30]'
elif 30<date<=60:
return '(30,60]'
elif 60<date<=90:
return '(60,90]'
else:
return date
user_purchase_retention['date_diff_bin']=user_purchase_retention['date_diff'].apply(convert)
user_purchase_retention.dropna(inplace=True)
然后将时间差值分桶。我这里分成0~30天内,30~60天内,60~90天等,代表用户当前消费时间距第一次消费属于哪个时间段呢。这里date_diff=0并没有被划分入0~30天,因为计算的是留存率,如果用户仅消费了一次,留存率应该是0。另外一方面,如果用户第一天内消费了多次,但是往后没有消费,也算作留存率0。
bin = [0,30,60,90,120,150,180,365]
user_purchase_retention['date_diff_bin'] = pd.cut(user_purchase_retention.date_diff, bins = bin)
pivoted_retention= user_purchase_retention.groupby(['user_id','date_diff_bin']).order_amount.sum().unstack()
pivoted_retention_trans = pivoted_retention.fillna(0).applymap(lambda x: 1 if x >0 else 0)
pivoted_retention_trans.head()
date_diff_bin |
(0, 30] |
(30, 60] |
(60, 90] |
(90, 120] |
(120, 150] |
(150, 180] |
(180, 365] |
user_id |
|
|
|
|
|
|
|
3 |
0 |
0 |
1 |
0 |
0 |
0 |
1 |
4 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
5 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
7 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
8 |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
直接用金额表达不出留存率,我们还是按照之前的算法,把消费过的转换成1,未消费过的转换成0,毕竟留存率看的是是否有消费。
ax = (pivoted_retention_trans.sum()/pivoted_retention_trans.count()).plot.bar()
ax.set_xlabel('时间跨度(天)')
ax.set_ylabel('百分数(%)')
ax.set_title('各时间段的用户留存率')
plt.show()
如图,第一个月的留存率高达46%,第二个月就下降到35%左右,之后几个月逐渐趋近稳定在25%左右,说明后面的用户逐渐开始稳定下来,说明通过用户在前三个月的使用中,逐渐开始喜爱本店铺的业务或者转换别家的店铺,所以这时候流失率会增大。从运营的角度来看,留存的玩家都是至少消费两次以上的玩家,比起拉新用户来讲,如何提高这些已消费玩家的持续消费是运营的重点,有些活动还是营销,最好放在前三个月,特别是第一个月来进行比较好。为了更好的确定营销的最好时机,我们来看看用户的平均购买周期。
(6)平均购买周期
平均购买周期:用户的两次消费行为的时间间隔。
def diff(group):
d= abs(group.date_diff - group.date_diff.shift(-1))
return d
last_diff = user_purchase_retention.groupby('user_id').apply(diff)
ax = last_diff.hist(bins = 20)
ax.set_xlabel('时间跨度(天)')
ax.set_ylabel('人数(人)')
ax.set_title('用户平均购买周期直方图')
plt.show()
如图,典型的长尾分布,大部分用户的消费间隔确实比较短。不妨将时间召回点设为消费后立即赠送优惠券,消费后10天询问用户CD怎么样,消费后30天提醒优惠券到期,消费后60天短信推送。