数据来源于CDNow的一份用户购买CD明细,包含了用户ID、购买日期、购买数量、购买金额四个字段。
分析目的
- 按月度进行消费趋势分析
- 用户个体消费分析
- 用户消费行为分析
- 复购率和回购率分析
数据导入
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
# 使用R语言绘图风格
plt.style.use('ggplot')
读取数据
df = pd.read_table('CDNOW_master.txt', sep='\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')
df['month'] = df.order_dt.values.astype('datetime64[M]')
df.head()
描述性统计
df.describe()
平均每笔订单购买2.4张cd,标准差为2.33,稍有波动,每笔订单平均消费35.89元,购买量上四分位数为3张,说明大部分用户订单购买量不大,消费金额呈现类似趋势,单笔订单最大值为99,说明有少部分狂热打榜粉丝,一般消费类的数据都是呈现长尾形态,即大部分用户都是小额消费者,小部分用户贡献了主要的消费收益,俗称二八理论。
按月度进行消费趋势分析
每月的消费总金额
grouped_month = df.groupby('month')
order_month_amount = grouped_month.order_amount.sum()
order_month_amount
month
1997-01-01 299048.40
1997-02-01 379590.03
1997-03-01 393155.27
1997-04-01 142824.49
1997-05-01 107933.30
1997-06-01 108395.87
1997-07-01 122078.88
1997-08-01 88367.69
1997-09-01 81948.80
1997-10-01 89780.77
1997-11-01 115448.64
1997-12-01 95577.35
1998-01-01 76756.78
1998-02-01 77096.96
1998-03-01 108970.15
1998-04-01 66231.52
1998-05-01 70989.66
1998-06-01 76109.30
Name: order_amount, dtype: float64
可视化
plt.figure(figsize=(10, 5))
order_month_amount.plot()
plt.title('每月消费总额趋势图')
前3个月为消费金额的高峰,随后在第4个月下跌,后趋于平稳,总体上呈现下降趋势。
每月订单数量
order_month_count = grouped_month.user_id.count()
order_month_count
month
1997-01-01 8927
1997-02-01 11272
1997-03-01 11598
1997-04-01 3781
1997-05-01 2895
1997-06-01 3054
1997-07-01 2942
1997-08-01 2320
1997-09-01 2296
1997-10-01 2562
1997-11-01 2750
1997-12-01 2504
1998-01-01 2032
1998-02-01 2026
1998-03-01 2793
1998-04-01 1878
1998-05-01 1985
1998-06-01 2043
Name: user_id, dtype: int64
可视化
plt.figure(figsize=(10, 5))
order_month_count.plot()
plt.title('每月订单数量趋势图')
前3个月为订单量的高峰,都在1万左右,第四个月急剧下跌,后趋于平稳,总体趋势和每月消费总额趋势一致。猜测前三个月应为新专辑发布或促销活动。
每月用户量
user_month_count = grouped_month.user_id.apply(lambda x:len(x.drop_duplicates()))
user_month_count
可视化
plt.figure(figsize=(10, 5))
user_month_count.plot()
plt.title('每月用户数量趋势图')
前3个月用户量大约在9000左右浮动,第4个月开始用户量急剧下跌,最终每月用户量维持在2000左右。
每月用户消费平均金额
# 每月总消费额 / 每月用户数量
mean_order_amount = order_month_amount / user_month_count
mean_order_amount
month
1997-01-01 38.119618
1997-02-01 39.405173
1997-03-01 41.280478
1997-04-01 50.611088
1997-05-01 48.750361
1997-06-01 46.342826
1997-07-01 55.999486
1997-08-01 49.868900
1997-09-01 47.124094
1997-10-01 48.820430
1997-11-01 56.927337
1997-12-01 51.275402
1998-01-01 49.939349
1998-02-01 49.707905
1998-03-01 52.898131
1998-04-01 46.090132
1998-05-01 47.708105
1998-06-01 50.537384
dtype: float64
可视化
plt.figure(figsize=(10, 5))
mean_order_amount.plot()
plt.title('每月用户平均消费金额趋势图')
前3个月用户平均消费金额在40元左右,后续有所上升,最终在46-57范围内波动。
用户平均消费次数
# 总消费次数 / 每月用户数量
mean_order_count = grouped_month.user_id.count() / user_month_count
mean_order_count
1997-01-01 1.137922
1997-02-01 1.170144
1997-03-01 1.217766
1997-04-01 1.339830
1997-05-01 1.307588
1997-06-01 1.305686
1997-07-01 1.349541
1997-08-01 1.309255
1997-09-01 1.320299
1997-10-01 1.393148
1997-11-01 1.356016
1997-12-01 1.343348
1998-01-01 1.322056
1998-02-01 1.306254
1998-03-01 1.355825
1998-04-01 1.306889
1998-05-01 1.334005
1998-06-01 1.356574
可视化
plt.figure(figsize=(10, 5))
mean_order_count.plot()
plt.title('每月用户平均消费次数趋势图')
平均消费次数总体呈上升趋势,前3个月在1.25次以下,后续几个月在1.35次附近波动。
用户个体消费分析
消费商品数与消费总金额的描述性统计
grouped_user = df.groupby('user_id')
grouped_user.sum().describe()
从用户角度看,单个用户平均购买cd数量为7个,中位数为3个,标准差为16.98;平均消费金额为106.8,中位数为43.4,标准差为240.9。明显存在极大值干扰,存在一小部分高消费人群。总体上符合二八法则。
消费金额与消费商品数的散点图
plt.figure(figsize=(10, 5))
grouped_user.sum().query('order_amount < 4000').plot.scatter(x='order_amount', y='order_products')
plt.title('消费金额与消费产品数散点图')
可以看出,消费金额与消费产品数量呈线性关系。
用户消费金额分布图
plt.figure(figsize=(10, 5))
grouped_user.sum().order_amount.plot.hist(bins=20)
plt.title('用户消费金额分布')
极大部分用户集中在2000元以下,整体消费能力不高,高消费用户数量极少,符合消费市场行业规律。
过滤掉消费数量大于100的订单,减小极大值影响。
plt.figure(figsize=(10, 5))
grouped_user.sum().query('order_products < 100').order_amount.hist(bins=20)
plt.title('用户消费金额分布(过滤后)')
可以看出,过滤掉消费数量大于100的订单后,大部分用户的消费金额都小于250,高消费用户不多,符合市场规律。
用户消费次数的分布图
plt.figure(figsize=(10, 5))
grouped_user.count().query('order_products < 100').order_dt.hist(bins=40)
plt.title('用户消费次数分布图')
从图上看大部分用户只消费1次两次,高频消费者很少这也满足cd消费市场行业规律。
累计消费金额占比
plt.figure(figsize=(10, 5))
user_cumsum = grouped_user.sum().sort_values(by='order_amount', ascending=False).apply(lambda x: x.cumsum() / x.sum())
user_cumsum.reset_index().order_amount.plot()
plt.title('消费金额累计百分比')
按照用户消费金额进行降序排序,由图可知前30-40%的用户贡献了80%的消费额。只需要重点维护这写用户,就可以把业绩kpi完成80%。
用户消费行为分析
用户首次消费
grouped_user.order_dt.min().value_counts().plot(figsize=(10, 5))
plt.title('用户首次消费')
用户首次消费集中在前3个月,2月10日至2月25日有较为强烈的波动。
用户最后一次消费
grouped_user.order_dt.max().value_counts().plot(figsize=(10, 5))
plt.title('用户最后一次消费')
用户最后一次消费比第一次消费分布广,大部分最后一次消费集中在前三个月,说明很多客户购买一次就不再进行购买。随着时间的增长,最后一次购买数也在递增,消费呈现流失上升的情况,用户忠诚度在慢慢下降。
新老客户消费比
计算只买了一次的消费群体
grouped_user.count().query('order_dt == 1').order_dt.count()
11907
总消费人数
grouped_user.count().reset_index().user_id.count()
23569
大部分人只消费一次。
grouped_month_user = df.groupby(['month', 'user_id'])
tmp = grouped_month_user.order_dt.agg(['min']).join(grouped_user.order_dt.min())
tmp['new'] = (tmp['min'] == tmp.order_dt)
tmp.reset_index().groupby('month').new.apply(lambda x: x.sum() / x.count()).plot(figsize=(10, 5))
plt.title('新用户占比')
可以看出,只有前3个月有新客户,后续都是老客户进行消费。
用户分层
使用RFM模型对用户分层。
构建rfm透视表。
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.head()
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.head()
用户分层
def rfm_func(x):
level = x.apply(lambda x: '1' if x >= 0 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()
计算每层客户的R、F、M的和
rfm.groupby('label').sum()
绝大部分消费是由重要保持客户产生的,维持好这部分客户,完成kpi有极大的帮助。
rfm.groupby('label').count()
一般发展客户基数较大,可对这部分客户进行挽留以保持高额增长。
rfm.loc[rfm.label == '重要保持客户', 'color'] = 'r'
rfm.loc[~(rfm.label == '重要保持客户'), 'color'] = 'g'
rfm.plot.scatter('F', 'R', c=rfm.color, figsize=(10, 5))
plt.title('F-R')
从RFM分层可知,大部分用户为重要保持客户,但是这是由于极值的影响,所以RFM的划分标准应该以业务为准。尽量用小部分的用户覆盖大部分的份额,不要为了数据好看划分等级。
用户生命周期
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()
于尾部数据,从实际的业务场景上说,他们一月和二月都没有注册三月份才是他们第一次消费。透视会把他们一月和二月的数据补上为0,这里面需要进行判断将第一次消费作为生命周期的起始,不能从一月份开始就粗略的计算。
columns_month = df.groupby('month').sum().reset_index().month
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('new')
return pd.Series(status, index=columns_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))
purchase_status_ct
plt.figure(figsize=(10, 5))
purchase_status_ct.fillna(0).T.plot.area()
plt.title('各类用户面积分布图')
用户购买周期
order_diff = grouped_user.apply(lambda x: x.order_dt - x.order_dt.shift()).reset_index()
order_diff
user_id
2 0 NaT
1 0 days
3 2 NaT
3 87 days
4 3 days
5 227 days
6 10 days
7 184 days
4 8 NaT
9 17 days
Name: order_dt, dtype: timedelta64[ns]
plt.figure(figsize=(10, 5))
(order_diff / np.timedelta64(1, 'D')).hist(bins=20)
plt.title('用户消费周期分布')
order_diff.describe()
count 46089
mean 68 days 23:22:13.567662
std 91 days 00:47:33.924168
min 0 days 00:00:00
25% 10 days 00:00:00
50% 31 days 00:00:00
75% 89 days 00:00:00
max 533 days 00:00:00
Name: order_dt, dtype: object
平均消费周期为68天,绝大部分用户购买周期在100天以内。
用户生命周期
user_life = grouped_user.order_dt.agg(['min', 'max'])
user_life.head()
plt.figure(figsize=(10, 5))
((user_life['max'] - user_life['min']) / np.timedelta64(1, 'D')).hist(bins=40)
plt.title('用户生命周期分布')
用户生命周期受只购买一次的客户影响较大,此处应该排除掉。
plt.figure(figsize=(10, 5))
u_1 = (user_life['max'] - user_life['min']) / np.timedelta64(1, 'D')
u_1[u_1 > 0].hist(bins=40)
plt.title('用户生命周期分布(删除之后买一次用户后)')
这是双峰趋势图。部分质量差的用户,虽然消费了两次,但是仍旧无法持续,在用户首次消费30天内应该尽量引导。少部分用户集中在50天~300天,属于普通型的生命周期,高质量用户的生命周期,集中在400天以后,这已经属于忠诚用户了。
复购率和回购率分析
复购率
所谓复购率就是在一个自然月内,购买多次的用户占比。
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()).plot(figsize=(10, 5))
plt.title('复购率')
回购率
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)
status.append(np.NaN)
return pd.Series(status, index=columns_month)
purchase_b = df_purchase.apply(purchase_back, axis=1)
purchase_b.head()
(purchase_b.sum() / purchase_b.count()).plot(figsize=(10, 5))
plt.title('回购率')
回购率在30%左右,新客的整体质量低于老客,老客的忠诚度(回购率)表现较好。