目标:针对用户消费行为进行分析,获取一定的数据理解
数据:产品订单数据,含user_id,order_dt,order_products,order_amount,month五个字段
实现方式:数据可视化,描述性分析,指标分析,RFM模型
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
plt.style.use('ggplot') # 更改绘图风格,R语言绘图库的风格
plt.rcParams['font.sans-serif']='SimHei' ##设置中文显示
导入数据 数据预处理
df = pd.read_table('data\CDNOW_master.txt',sep='\s+') #sep: '\s+'以任意空格作为不同属性的分隔符
df.columns=['user_id','order_dt','order_products','order_amount']
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.info()
user_id | order_dt | order_products | order_amount | month | |
---|---|---|---|---|---|
0 | 2 | 1997-01-12 | 1 | 12.00 | 1997-01-01 |
1 | 2 | 1997-01-12 | 5 | 77.00 | 1997-01-01 |
2 | 3 | 1997-01-02 | 2 | 20.76 | 1997-01-01 |
3 | 3 | 1997-03-30 | 2 | 20.76 | 1997-03-01 |
4 | 3 | 1997-04-02 | 2 | 19.54 | 1997-04-01 |
数据分析之用户整体消费分析(按月)
##每月产品购买数量
plt.figure(figsize=(12,5))
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('每月的消费次数')
##每月的消费人数
plt.subplot(224)
df.groupby(by='month')['user_id'].apply(lambda x:len(x.drop_duplicates())).plot()
plt.title('每月的消费人数')
Text(0.5, 1.0, '每月的消费人数')
数据分析之用户个体消费分析
#df['u_id']=int(df['user_id'])
user_grouped=df.groupby(by='user_id').sum()
print(user_grouped.describe())
order_products order_amount
count 23569.000000 23569.000000
mean 7.122916 106.084427
std 16.983845 240.929523
min 1.000000 0.000000
25% 1.000000 19.970000
50% 3.000000 43.410000
75% 7.000000 106.490000
max 1033.000000 13990.930000
#绘制每个用户购买产品数与消费金额的散点图
df.plot(kind='scatter',x='order_products',y='order_amount')
# 订单极值点较少(消费金额>1000或购买量>60),对于样本来说影响不大,可以忽略不记
# 用户消费金额与购买量呈线性相关,均价约为15
<AxesSubplot: xlabel='order_products', ylabel='order_amount'>
用户消费分布图
plt.figure(figsize=(12,4))
plt.subplot(121)
plt.xlabel('每个订单的消费金额')
df['order_amount'].plot(kind='hist',bins=20) #bins:区间分数,影响柱子宽度---宽度=(列最大值-列最小值)/bins
# 消费金额在100以内占据绝大部分
plt.subplot(122)
plt.xlabel('每个uid的购买数量')
df.groupby(by='user_id')['order_products'].sum().plot(kind='hist',bins=50)
# 由两图可知我们的大部分用户消费金额低,且购买数量小于50(在电商领域是普遍现象)
<AxesSubplot: xlabel='每个uid的购买数量', ylabel='Frequency'>
用户累计消费金额占比分析(用户贡献度)
user_cumsum=df.groupby(by='user_id')['order_amount'].sum().sort_values().reset_index() # 用户分组,取消费金额,进行求和,排序,重置索引
user_cumsum
user_cumsum['amount_cumsum']=user_cumsum['order_amount'].cumsum()
user_cumsum.tail()
amount_total=user_cumsum['amount_cumsum'].max() #消费金额总值
user_cumsum['prop']= user_cumsum.apply(lambda x:x['amount_cumsum']/amount_total,axis=1) #axis=1:对列apply函数
user_cumsum.tail()
user_id | order_amount | amount_cumsum | prop | |
---|---|---|---|---|
23564 | 7931 | 6497.18 | 2463810.83 | 0.985405 |
23565 | 19339 | 6552.70 | 2470363.53 | 0.988025 |
23566 | 7983 | 6973.07 | 2477336.60 | 0.990814 |
23567 | 14048 | 8976.33 | 2486312.93 | 0.994404 |
23568 | 7592 | 13990.93 | 2500303.86 | 1.000000 |
user_cumsum['prop'].plot()
# 由图可知前2w名用户贡献40%的消费金额,后3k+名用户贡献60%的消费金额(2/8原则)
<AxesSubplot: >
plt.figure(figsize=(12,4))
plt.subplot(121)
df.groupby(by='user_id')['order_dt'].min().value_counts().plot() #用户首购时间
# 由图一可知,首购的用户量在1月1号~2月10号呈上升趋势,后续逐渐下降,可能跟商家促销活动有关
plt.subplot(122)
df.groupby(by='user_id')['order_dt'].max().value_counts().plot() #用户最后一次购买时间
# 由图二可知,大多数用户最后一次购买时间集中在前3个月,说明缺少忠诚用户
# 随着时间的推移,最后一次购买商品的用户量呈上升趋势,猜测:这份数据选择的是前3个月的消费 用户在后面18个月的跟踪记录
<AxesSubplot: >
用户分层分析
构建RFM模型
# 指标:距离最近时间 (Recency),消费频率次数 (Frequency),消费金额 (Monetary)
rfm= df.pivot_table(index='user_id',
values=['order_products','order_amount','order_dt'],
aggfunc={
'order_dt':'max', #最后一次购买
'order_products':'sum', #购买产品总数
'order_amount':'sum' # 消费总金额
})
rfm['R']=(rfm['order_dt'].max()-rfm['order_dt'])/np.timedelta64(1,'D') #取相差天数,保留一位小数
rfm.rename(columns={'order_products':'F','order_amount':'M'},inplace=True)
# RMF计算方式:每一列对应值减去所在列的均值,若结果>0,设置为1,否则为0
def rfm_func(x):
level=x.apply(lambda x:'1' if x>=0 else '0')
label=level['R']+level['F']+level['M']
d={
'111':'重要价值客户',
'101':'重要发展客户',
'001':'重要挽留客户',
'011':'重要保持客户',
'110':'一般价值客户',
'100':'一般发展客户',
'000':'一般挽留客户',
'010':'一般保持客户',
}
result =d[label]
return result
rfm['label']=rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1) #根据业务需求看是用平均值还是中位数啥的
rfm.head()
M | order_dt | F | R | label | |
---|---|---|---|---|---|
user_id | |||||
2 | 89.00 | 1997-01-12 | 6 | 534.0 | 一般发展客户 |
3 | 156.46 | 1998-05-28 | 16 | 33.0 | 重要保持客户 |
4 | 100.50 | 1997-12-12 | 7 | 200.0 | 一般挽留客户 |
5 | 385.61 | 1998-01-03 | 29 | 178.0 | 重要保持客户 |
6 | 20.99 | 1997-01-01 | 1 | 545.0 | 一般发展客户 |
客户分层之RMF可视化
for label,grouped in rfm.groupby(by='label'):
#print(label,grouped)
x=grouped['F'] # 消费频率次数 单个用户购买的数量
y=grouped['R'] # 最后一次购买时间与最近:df['order_dt'].max()的距离
plt.scatter(x,y,label=label)
plt.legend() #显示图例
plt.xlabel('F')
plt.ylabel('R')
Text(0, 0.5, 'R')
新老 活跃 回流用户分析
# 通过透视表得到想要的数据
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)
# apply 作用于df中的一行或一列
# applymap 作用于df中每一个元素
# map 本身是一个series函数,无法在df结构中使用,作用于series中每一个元素
df_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 | ||||||||||||||||||
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 |
6 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
判断是否是为用户 活跃用户 不活跃用户 回流用户
# 自定义函数 实现以上的判断
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
#本月有消费==1
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: #new/active/return=1
status.append('active')
return pd.Series(status,df_purchase.columns) # 值status ,列名df_purchase中的列名
#apply自定义函数 得到用户分层结果
purchase_states=df_purchase.apply(active_status,axis=1)
purchase_states.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 | ||||||||||||||||||
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 |
6 | new | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive | unactive |
# 统计每个月各种状态用户的数量
purchase_states_ct=purchase_states.replace('unreg',np.NaN).apply(lambda x: pd.value_counts(x)) #用NaN替换unreg 未注册用户
purchase_states_ct.head(60)
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 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
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 | 7845.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 | 6688.0 | 14045 | 20747.0 | 21355.0 | 21230.0 | 21389.0 | 21797.0 | 21830.0 | 21730.0 | 21541.0 | 21705.0 | 22032.0 | 22018.0 | 21509.0 | 22132.0 | 22081.0 | 22063.0 |
purchase_states_ct.T.fillna(0).plot.area() # 行列变换
# 前3个月可知,红色和蓝色用户占比大 前3个月活跃用户与新用户比较多
<AxesSubplot: xlabel='month'>
回流用户的占比
rate=purchase_states_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()
<matplotlib.legend.Legend at 0x1d55c72fe90>
用户消费周期与生命周期
order_diff=df.groupby('user_id').apply(lambda x: x['order_dt']-x['order_dt'].shift(axis=0)) #默认axis=0 按行遍历 向下
order_diff.head()
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
<AxesSubplot: >
# 计算用户生命周期 最后一次购买日期-第一次购买日期,若两者相等,说明该用户仅购买一次
user_life=df.groupby('user_id')['order_dt'].agg(['max','min'])
(user_life['max']==user_life['min']).value_counts().plot.pie(autopct='%1.1f%%') #格式化成1位小数
plt.legend(['仅消费一次','消费多次'])
# 由图可知,一半以上的用户仅仅消费一次
<matplotlib.legend.Legend at 0x1d55c727d50>
(user_life['max']-user_life['min']).describe() #对消费用户生命周期描述性分析
count 23569
mean 134 days 21:03:51.405659978
std 180 days 13:49:09.372703982
min 0 days 00:00:00
25% 0 days 00:00:00
50% 0 days 00:00:00
75% 294 days 00:00:00
max 544 days 00:00:00
dtype: object
plt.figure(figsize=(12,4))
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') #[0]刚刚好是重置索引后需要字段的列名
u_1[u_1>0].hist(bins=15)
plt.title('多次消费用户生命周期直方图')
plt.xlabel('生命周期天数')
plt.ylabel('用户人数')
# 由以下两图可知,第二幅图过滤掉了生命周期==0的用户,呈现双峰结构
# 虽然图二还有一部分生命周期趋于0天,但比图一情况好了很多,对这部分可以针对性进行营销活动,以拉长其生命周期
# 少部分用户生命周期集中在300~500天,属于我们的忠诚客户,
Text(0, 0.5, '用户人数')
复购率分析
# 在自然月内,购买多次的用户在总消费人数中的占比(若同一客户在一天内多次复购,也属于复购)
# 消费者有三种:复购:消费记录>=2,无消费:消费记录==0,有消费非复购:消费记录==1
# 对应表示复购 1 ,无消费 NaN ,有消费非复购 0
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>=2 else np.NaN if x==0 else 0)
purchase_r.head()
(purchase_r.sum()/purchase_r.count()).plot(figsize=(12,5))
<AxesSubplot: xlabel='month'>
回购率分析
# 在同一个时间窗口进行了消费,在下一个时间窗口又进行消费
def purchase_back(data):
status =[]
# 1:回购用户 0:非回购用户(当月消费,下个月未消费) NaN:当前月份未消费
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:
status.append(np.NaN)
status.append(np.NaN)
return pd.Series(status,df_purchase.columns)
purchase_b=df_purchase.apply(purchase_back,axis=1)
purchase_b.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 | ||||||||||||||||||
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 |
6 | 0.0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
plt.figure(figsize=(20,4))
plt.subplot(211)
# 回购率
(purchase_b.sum()/purchase_b.count()).plot(label='回购率')
# 复购率
(purchase_r.sum()/purchase_r.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.xlabel('month')
plt.ylabel('人数')
plt.legend()
<matplotlib.legend.Legend at 0x1d55cd57c50>