import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
-
首先当然要把该import的都全部Import进来。
plt.rcParams['font.family'] = ['Arial Unicode MS']
-
为了待会儿画图正常显示中文。因为我是MAC系统,所以代码跟WIN的可能有些不同。
pd.__version__
-
我pd的版本,是最新安装的,0.24.0
columns=['user_id','order_dt','order_products','order_amount']
df = pd.read_csv('CDNOW_master.txt',names=columns,sep='\s+')
#如果数据本身以空格做分割的话,这里sep要用\s+
-
导入数据,并把列头分别命名为:'user_id','order_dt','order_products','order_amount'
df.order_dt=pd.to_datetime(df.order_dt,format='%Y%m%d')
-
由于原数据时间那一项是‘19970101’这样的形式的,因此要用pd.to_datetime()把格式换成yyyy-mm-dd的格式,进行后续的分析时才更加方便。
df['month']=df.order_dt.values.astype('datetime64[M]')
#后续要解决的问题都需要以月为单位来计算,因此要多加一条column:"month",以月为单位。
df.head(10)
-
看下表头前十行大概长啥样。
df.describe()
-
先给这些数据做一些简单的统计分析。可以看出:大部分订单只消费了少量商品(平均2.4),用户的消费金额比较稳定,平均消费35元,中位数25元。有一定的极值干扰。
接下来就开始进行进一步的分析。
首先:1.进行用户按月消费趋势的分析。分别有:
- (1)每月消费的总金额
- (2)每月的消费次数
- (3)每月的产品购买数
- (4)每月的消费人数
grouped_month=df.groupby('month')
-
由于是按月消费的分析,因此要按月分组。
grouped_month.order_amount.sum().plot()#(1)每月消费的总金额
-
利用.sum()函数把order_amount那一列按月分组的总和求出来,并且画图,得到下列图片:
grouped_month.order_products.count().plot()#(2)每月的消费次数
-
利用.count()函数把总消费次数按月分组的总和求出来(这里用order_products或者order_amount其实都一样)。画图,并且得到下列图片:
grouped_month.order_products.sum().plot()#(3)每月的产品购买数
-
每月产品总购买数的画图:
grouped_month.user_id.count().plot()#(4)每月消费人数
-
每月消费人数的画图:
-
由以上四图可知,每月消费的总金额、消费次数、产品购买数以及消费人数,都在前三个月上升达到高峰,随后在3到4月份时巨幅下降,4月份之后开始稳定上下波动,甚至有轻微下降趋势。
2.用户个体消费分析
- (1)用户消费金额、产品购买数的描述统计
- (2)用户消费金额和产品购买数的散点图
- (3)用户消费金额的分布图
- (4)用户产品购买数的分布图
- (5)用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额
grouped_user=df.groupby("user_id")
-
用户个体消费分析,就要根据user_id来分组了。
grouped_user.sum().describe() #(1)用户消费金额、产品购买数的描述统计。
-
由上表可知:
-
①用户平均每个人购买7张CD,但中位数是3,说明有小部分用户购买了大量CD。
-
②用户平均消费106元,中位值有43,判断同上,有极值干扰。
-
由于散点图对极值比较敏感,可以做简单的过滤,因此:
grouped_user.sum().plot.scatter(x='order_amount',y='order_products',c='b')
-
因此可得:
-
由上图可知,数据主要集中在order_amount小于3000,order_products小于200。因此超出这两个范围的都视为极值。
grouped_user.sum().query('order_amount<3000').query('order_products<200').plot.scatter(x='order_amount',y='order_products',c='b')
(2)用户消费金额和产品购买数的散点图
-
再缩小范围做一幅图,可以更加清晰地看到,绝大部分的数据集中在order_amount小于1000,order_products小于100。:
-
用户消费金额的分布图
grouped_user.sum().query('order_amount<1000').order_amount.plot.hist(bins=50)
#(3)用户消费金额的分布图(根据上面散点图,选择数据较为集中的部分进行分析。)
-
用户消费产品数的分布图
grouped_user.sum().query('order_products<100').order_products.plot.hist(bins=50)
#(4)用户消费产品数的分布图(根据上面散点图,选择数据较为集中的部分进行分析)
-
用户累计消费金额占比:利用cumsum() 计算轴向元素累加和,再除以sum,得到累计比例。
user_cumsum=grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum()/x.sum())
user_cumsum.reset_index().order_amount.plot()
-
按用户消费金额进行升序排列,由图可知,排名前50%的用户只贡献了15%左右的消费额度。而排名最后3570的用户就贡献了60%的消费额度。
3.用户消费行为:
- (1)用户第一次消费(首购)
- (2)用户最后一次消费
- (3)新老客户消费比:
- ① 多少用户仅消费了一次?② 每月新客占比。
- (4)用户分层:
- ① RFM。② 新、活跃、回流、流失。
- (5)用户购买周期(按订单)
- ① 用户消费周期描述。② 用户消费周期分布。
- (6)用户生命周期(按第一次&最后一次消费)
- ① 用户生命周期描述。② 用户生命周期分布。
-
(1)用户第一次消费(首购):
grouped_user.min().order_dt.value_counts().plot(figsize=(10,4))
#第一次购买的时间,就是时间最小,所以用.min()。然后用value_counts()汇总起来。
-
由图可知,所有用户的首购都集中在97年1月1日至97年3月29日,2月上旬以前首购客户数量一直稳定上升。在2月10日左右首购客户的数量产生剧烈波动,在2月下旬开始稳定,然后一直到3月下旬都呈现稳定下跌的状态。
-
(2)用户最后一次消费:
grouped_user.max().order_dt.value_counts().plot()
#最后一次消费,日期最大。
-
用户最后一次购买的分布比第一次分布广,大部分最后一次购买集中在前三个月,说明有很多用户购买了第一次之后就不再购买了。而且随着时间的递增,最后一次购买也在递增,说明消费呈现流失上升的状况。
-
(3)新老客户比:
- ① 有多少客户只消费了一次?
用count对grouped_user的order_dt进行统计,=1的就是只消费一次客户。
- ① 有多少客户只消费了一次?
(grouped_user.order_dt.count()==1).value_counts()
-
接近一半的客户是只消费了一次的。
- ② 每月新客占比。
user_life=grouped_user.order_dt.agg(['min']).sort_values('min')
user_life['month']=user_life['min'].values.astype('datetime64[M]')
-
新建一个dataframe,user_life,以min为首购日期。在user_life那里添加一列month,也是首购日期,不过是以月为单位。
user_life.groupby('month').month.describe()
-
由上表可知每个月份的首购客户数量:
1月份新客占比:7846/(7846+8476+7248)=33.29%
2月份新客占比:8476/(7846+8476+7248)=35.96%
3月份新客占比:7248/(7846+8476+7248)=30.75%
-
(4)用户分层:
①RFM模型分析(最近一次消费、消费频率、消费金额)。
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()
利用透视表功能,把user_id作为索引,根据order_amount和order_dt的值,算出每个用户最大购买日期(最后一次消费的日期)、总消费金额,以及总消费次数(这里用 'order_products':'count' 或者 'order_amout':'count'都一样,因为算的是次数而不是购买的产品数。每次消费买的产品数都可能不一样。)然后我们可以看下rfm表大概长啥样:
rfm["R"]=(rfm.order_dt.max()-rfm.order_dt)/np.timedelta64(1,'D')
-
R,Recency,计算最近一次消费距离至今多少天(因为数据比较久远,所以用order_dt里面最大的一项作为今天)
rfm=rfm.rename(columns={'order_products':'F','order_amount':'M'})
-
F,Frequency,消费频率,这里为总消费次数;M,Monetary,为总金额。
-
然后把按照r、f、m这三个维度取平均,每个维度都把高于平均值的标记为1,低于平均值得标记为0.
- 这样就有以下8种用户:
111 重要价值客户
011 重要保持客户
101 重要挽留客户
001 重要发展客户
110 一般价值客户
010 一般保持客户
100 一般挽留客户
000 一般发展客户
- 这样就有以下8种用户:
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' : '重要挽留客户',
'110' : '一般价值客户',
'001' : '重要发展客户',
'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添加一列客户标示。
rfm.head(10)
rfm_grouped=rfm.groupby('label')
rfm_grouped.sum()
-
可以看到不同维度客户的占比
pd.Series(rfm_grouped['label'].count(),name='series')
-
可以看到不同客户的总数。
pd.Series(rfm_grouped['label'].count(),name='series').plot.pie()
-
画成饼图更加直观。
-
从RFM分层可知,本数据集的大部分用户为一般挽留客户,其次是重要保持客户。
② 用户生命周期:新、活跃、回流、流失(不活跃)
- 新:首次消费在最新时间。
- 活跃:持续消费。
- 回流:曾今有过消费,隔了超过一个月后再次消费。
- 流失:曾今有过消费,到最新时间为止还没有消费过。
本例直接按一个月作为间隔。
pivoted_counts = df.pivot_table(index='user_id', # 使用数据透视,将每个用户每月消费次数计算出来
columns='month', # 如果没有数据的话(比如1号客户1997年2月1日没有数据),数据透视功能也会把它变成NaN。
values='order_dt',# 因此要价格fillna(0)把空值变成0。
aggfunc='count').fillna(0)
pivoted_counts.head()
-
透视表大概长这个样子。
-
由于本次案例是需要计算每个月是否有消费,具体消费多少次不重要,因此可以把大于0的都当做1:
purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0)
-
由于很多的首次消费是在2月或者3月,所以在写函数判断的时候需要考虑在内,并不是所有的首次消费都在一月:
def active_status(data):
status=[]
for i in range(18): #共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
函数的逻辑:
若本月没有消费:
- 若之前是未注册,则依旧为未注册。
- 若之前有消费,则为流失。
- 其他情况,视为未注册。
若本月有消费:
- 若是第一次消费,则是新用户。
- 如果之前有过消费,且上个月为不活跃,则为回流。
- 如果上个月为未注册,则为新用户。
- 除此之外,为活跃。
indexs=df['month'].sort_values().astype('str').unique()
purchase_sta=purchase.apply(lambda x:pd.Series(active_status(x),index=indexs),axis=1)
purchase_sta.head()
-
可得到一张不同用户在不同月份的不同状态(new=新、active=活跃、return=回流、unactive=流失),unreg相当于未注册,指这个用户在这个月及以前从未购买过产品,主要为了统计起来更加方便而加进去。
purchase_sta.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)).fillna(0).T
-
把unreg替换成NaN,再用fillna(0)把空值填为0。然后转置,把月份作为索引行,状态作为列,得到如下的表:
purchase_sta.replace('unreg',np.NaN).apply(lambda x:pd.value_counts(x)).fillna(0).T.plot.area(figsize=(10,4))
-
作出非堆积效果图:
-
由图可知,到了在前三个月,新用户增加的数量非常大。从三月一号开始,用户开始快速流失。到后面的几个月流失用户基本占绝大比例。
(5)用户购买周期(按订单)
- 用户消费周期描述
- 用户消费周期分布
order_diff=grouped_user.apply(lambda x:x.order_dt-x.order_dt.shift())
-
算出不同用户每次消费相隔多少时间
order_diff.describe()
-
可以看到每次消费相隔时间大致的分布。
(order_diff/np.timedelta64(1,'D')).hist(bins=20)
-
把分布图作出来:
-
由上图以及描述可知:
订单周期呈指数分布。
用户的平均购买周期是68天。
绝大部分用户的购买周期都低于100天。
-
(6)用户生命周期(按第一次&最后一次消费)
① 用户消费周期描述
(user_life['max']-user_life['min']).describe()
② 用户消费周期分布
((user_life['max']-user_life['min'])/np.timedelta64(1,'D')).hist(bins=40)
4.复购率和回购率分析
- (1) 复购率(自然月内,购买多次的用户占比)
- (2)回购率(曾经购买过的用户在某一时期内的再次购买的占比)
-
(1)复购率:当月多次购买(超过一次)
purchase_r=pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x == 0 else 0)
-
"1"表示消费次数大于1;"0"表示只消费过一次;"NaN"表示没买过,在.count中就不会计算在名额内。求复购率就计算消费次数大于1的占比就行了。
(purchase_r.sum()/purchase_r.count()).plot(figsize=(10,4))
-
由上图可知,复购率在前三个月高速增长,最终在大概四月份的时候稳定在20%左右。
-
(2) 回购率:上个月有过消费,这个月又有消费,就叫回购。
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 status
purchase_b=purchase.apply(lambda x:pd.Series(purchase_back(x),index=indexs),axis=1)
purchase_b.head()
-
1代表本月有消费,下个月也有消费;0代表本月有消费,但是下个月没有消费;NaN代表本月没有消费。
(purchase_b.sum()/purchase_b.count()).plot(figsize=(10,4))
-
本月与次月都有消费的 / 本月有消费的,就是回购率。
-
可以看出,回购率在前三个月快速增长,知道四月份增长到30%以后,一直维持在30%左右波动。