用户消费行为|数据分析

一、报告背景:
公司于1994年2月由双胞胎兄弟杰森汀和马修汀共同创立。公司在1994年8月推出telnet服务。公司在1994年9月成为一家零售CD网站。

二、报告目的:
通过案例数据完成一份Python的数据分析报告。

三、数据来源:
数据来源CDNow网站的用户购买明细(数据链接: https://pan.baidu.com/s/1TisDZYpiyeivMIKVv1Q3yQ 密码: clku
)。

四、代码解析:
(1)基本描述

#导入库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
#设定绘图风格
plt.style.use('ggplot')
# 四个列名分别表示用户ID, 购买日期, 购买数量, 购买金额
columns =['user_id', 'order_dt', 'order_products', 'order_amount']
# 读取用户行为数据,sep='\s+' :数据以空格(1~∞ )间隔分割数据列
df=pd.read_table('CDNOW_master.txt',names = columns ,sep='\s+')
#读取前五行数据
print(df.head())
#读取数据基本信息
print(df.describe())

从上可知,看数据6关键指标:样本数:69659,最小值:0,最大值:1286,平均值:36,方差36,中位数:26。平均数大于中位数,右偏分布。
没有空缺数据,不用清理直接分析,注意到日期数据是整数型,需要转换成时间格式。

#读取数据属性
print(df.info())
无残缺数据
#增加两个新时间序列
#将order_dt转化成时间格式
df['order_date']=pd.to_datetime(df.order_dt,format='%Y%m%d')
#将order_date转化成每月起始第一天
df['month']=df.order_date.values.astype('datetime64[M]')

pd.to_datetime将特定的字符串或者数字转换成时间格式,format参数表示输出的格式,%Y匹配四位数年份,%m匹配月份,%d匹配日期。输出year-month-day形式,则%Y-%m-%d。
astype也可以将时间格式进行转换,比如[M]转化成月份。将月份作为消费行为的主要事件窗口,选择哪种时间窗口(年、月、日)取决于消费频率。

#按用户ID进行分组
user_grouped=df.groupby('user_id').sum()
print(user_grouped)
print(user_grouped.describe())

原表只按订单来记录,现按用户分组看数据集概述。
从购买数量(单位:PC)角度看,用户数量:23570,平均值购买7张,最多购买1033张,属于狂热用户,中位数3张,平均值大于中位数,是右偏分布,存在小部分购买多张碟的用户。
从消费金额(单位:美元)角度看,平均数106,最高值:13990,属于土豪用户,中位数43,平均值大于中位数,是右偏分布,存在小部分高消费用户。

plt.rcParams['font.sans-serif']=['SimHei']# 用来正常显示中文标签
ax=df.groupby('month').order_products.sum().plot()
ax.set_xlabel('月份')
ax.set_ylabel('CD碟数(张)')
ax.set_title('不同月份的用户购买张数')
plt.show()
ax=df.groupby('month').order_amount.sum().plot()
ax.set_xlabel('月份')
ax.set_ylabel('消费金额(美元)')
ax.set_title('不同月份的用户消费金额')
plt.show()

由图片可知,无论是消费金额还是CD碟数都呈现相同的趋势,而且前三月数据都呈现出异常状态,由于我们不知道原数据到底从何获得,只能做出这三个月有促销活动,抑或是这是新开的店之类的,前三个月大多都是新人之类的假设,我们不得而知,继续往下看。

ax=df.groupby('user_id').sum().plot.scatter('order_amount','order_products')
ax.set_xlabel('消费金额')
ax.set_ylabel('CD碟数(张)')
ax.set_title('每个用户消费金额与购买个数的关系')
plt.show()

每个用户的消费金额跟购买数量呈现一定的线性关系。

plt.figure(figsize=(12,4))
plt.subplot(121)
ax=df.groupby('user_id').order_products.sum().plot.hist(bins=50)
ax.set_xlabel('CD碟数(张))')
ax.set_ylabel('用户数(人)')
ax.set_xlim(0,150)
ax.set_title('用户购买碟数分布直方图')
plt.subplot(122)
ax=df.groupby('user_id').order_amount.sum().plot.hist(bins=50)
ax.set_xlabel('消费金额(美元)')
ax.set_ylabel('用户数(人)')
ax.set_xlim(0,2000)
ax.set_title('用户消费金额分布直方图')

我们可以从直方图看到,大部分用户的消费能力确实不高,整个计算周期里的购买碟数在20张以内,消费金额在250美金以内,再图上几乎看不到高消费用户。

print(df.groupby('user_id').month.min().value_counts())

我们可以看到,貌似每个用户的第一次购买记录都是前三个月,最后次购买记录也是前三个月居前三。由此我们可以推测,这份数据集大概是选择了这三个月时间段消费的用户在后面的18个月的追踪记录数据。也能很好的解释我们之前看到的前三个月无论是消费金额还是购买数量的异常。

print(df.groupby('user_id').month.max().value_counts())
#创建数据透视表,对每个用户的每月订单数计数
pivoted_counts=df.pivot_table(index='user_id',columns='month',values='order_dt',aggfunc='count').fillna(0)
print(pivoted_counts.head())
pivoted_counts_transf =pivoted_counts.applymap(lambda x:1 if x>1 else np.NAN if x ==0 else 0)
print(pivoted_counts_transf.head())

(2)消费行为中的复购率和回购率
复购率:复购率的定义是在某时间窗口内消费两次及以上的用户在总消费用户中占比。这里的时间窗口是月,如果一个用户在同一天下了两笔订单,这里也将他算作复购用户。

ax=(pivoted_counts_transf.sum()/pivoted_counts_transf.count()).plot(figsize=(10,4))
ax.set_xlabel('时间(月)')
ax.set_ylabel('百分比(%)')
ax.set_title('每月用户复购率图')
plt.show()

在pandas中,数据透视有专门的函数pivot_table。pivot_table参数中,index是设置数据透视后的索引,column是设置数据透视后的列,简而言之,index是你想要的行,column是想要的列。案例中,我希望统计每个用户在每月的订单量,所以user_id是index,month是column。

values是将哪个值进行计算,aggfunc是用哪种方法。于是这里用values=order_dt和aggfunc=count,统计里order_dt出现的次数,即多少笔订单。使用数据透视表,需要明确获得什么结果。有些用户在某月没有进行过消费,会用NaN表示,这里用fillna填充。

lambda为自建函数,用到条件语句时,要注意没有elif,两个判断需要两个if语句,并且呈现 结果1 if else(结果2 if XX else 结果3)这样的倒装结构,applymap则是作用到数据里的每一个数据时使用。

从图中我们可以看出复购率因为大量新用户加入的关系,不断扩大分母,导致初期的复购率不怎么高,譬如刚开始的97年一月份复购率只有6%左右,而后直线上升,到第四个月后到达最高点,因为这次数据集是前三个月新客跟踪数据,三个月后,没有新客的加入,可以认定是前三个月的客户大浪淘沙剩下的还有购买欲望的老客,这时候的复购率比较稳定,在20%左右徘徊。

fig,ax=plt.subplots(figsize=(10,4))
ax.plot(pivoted_counts_transf.sum())
ax.plot(pivoted_counts_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 = df.pivot_table (values = 'order_dt', index= 'user_id', columns = 'month', aggfunc = 'count').fillna(0)
#转化数据,有过购买的为1,没有购买的为0
pivoted_purchase = pivoted_amount.applymap(lambda x: 1 if x>0 else 0)
pivoted_purchase.head()
#定义函数,每个月都要跟后面一个月对比下,本月有消费且下月也有消费,则本月记为1,下月没有消费则为0,本月没有消费则为NaN,由于最后个月没有下月数据,规定全为NaN
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()

由于返回的是个序列后列名发生了改变,我们需要重新倒入列名。

columns=df.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('十八个月内用户回购率图')
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')

由图可知,用户的回购率大于复购率,约在30%左右正负五个点波动,看人数分布表则发现回购人数趋近稳定,那么波动则是偶尔月份间的消费人数基数的变动,可能有营销者淡旺季,但是这部分回购用户的消费行为大抵稳定,应该跟之前每月复购的用户有一定重合,是属于优质用户。
对回购率和复购率进行综合分析,可以得出,新客的整体质量低于老客,老客的忠诚度(回购率)表现较好,消费频次稍次,这是这个网站的用户消费特征。

(3)消费行为中的用户分层

我们按照用户的消费行为,简单划分成几个维度:新用户、活跃用户、不活跃用户、回流用户。
新用户(new):新用户的定义是第一次消费的用户。
活跃用户(active):即连续两个时间窗口都消费过的用户。
不活跃用户(unactive):不活跃用户则是时间窗口内没有消费过的活跃用户,即一二月份都消费过,三月份没消费过。
回流用户(return):回流用户是在上一个窗口中没有消费,而在当前时间窗口内有过消费。

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('active')
    return pd.Series(status)

pivoted_purchase_status = pivoted_purchase.apply(active_status, axis =1)
pivoted_purchase_status.columns = columns
pivoted_purchase_status.head()

这个函数主要分为两部分的判断,以本月是否消费为界。本月若没有消费,则判断是不是第一个月,是的话直接返回unreg(不辨别),不是第一个月的话,我们就可以找出前一月标签,前一个月还是不辨别的话,就返回unreg(不辨别),因为这个月还是没消费,如果前一个月是新课还是活跃用户,还是回流用户,因为这个月都没消费,重新定义为unactive(不活跃用户)。
本月若有消费,则需要判断是不是第一次消费,若是第一月的消费,因为没有前一个月数据,所以直接是new(新客)。接下来就是不是第一个月又有消费的情况了,此时可以找出前一个月的标签,如果前一个月是不活跃,则返回return(回流用户),如果是unreg,则是第一次消费,返回new(新客),剩下的就是前一个月是新客或者是活跃用户了,此时都返回active(活跃用户)。
应用后的结果为:


pivoted_status_counts = pivoted_purchase_status.replace('unreg', np.NaN).apply(pd.value_counts)
print(pivoted_status_counts.head())
ax=pivoted_status_counts.fillna(0).T.plot.area(figsize=(12,6))
ax.set_xlabel('时间(月)')
ax.set_ylabel('用户数(人)')
ax.set_title('每月各类用户类型占比面积图')

由图可知,黑色的不活跃用户始终是占据大头的,这也跟我们之前的图表结果相符,其次红色代表的活跃用户非常稳定,是属于核心用户,以及紫色的回流用户,这两个分层相加,大抵是当月的消费用户人数的占比,与我们之前的复购率和回购率的表结果一致,这两层的用户大抵接近两千人左右。
回流占比:某个时间窗口内回流用户在总用户中的占比。
活跃占比:某个时间窗口内活跃用户在总用户中的占比。

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)用户质量

因为消费行为有明显的二八倾向,我们需要知道高质量用户为消费贡献了多少份额。

#新建一个对象,按用户的消费金额生序。新增一列,使用cumsum累加函数,逐行计算累计的金额。
user_amount = df.groupby('user_id').order_amount.sum().sort_values().reset_index()
user_amount['amount_cumsum'] = user_amount.order_amount.cumsum()
print(user_amount.tail())

通过上面的图,我们可以知道每个用户的ID及其对应的消费金额,以及累加金额。

#计算出各阶段金额占总金额的百分比
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('用户累计贡献金额百分比')

由图我们可以清晰的看到,金额排名靠后的15000名用户才贡献不到20%的销售量,而此次数据集的总用户人数为23569人,我们可以进一步再看,排名靠后的20000名用户,贡献了接近40%的消费金额,也就是说后面的3569人接近贡献了60%的消费金额,如果我们换算成金额/人单位,这后面的3569名客户相当于签名20000名客户的8.4倍!这也反应了在消费领域中,狠抓高质量用户是万古不变的道理。

(5)用户生命周期

这里我们定义第一次消费至最后一次消费为整个用户生命。我们需要找出每个用户的第一次消费和最后次消费

#用户生命周期
user_purchase = df[['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()
print(life_time.describe())

由描述可知,所有用户的平均生命周期是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()

由于这里的数据类型是timedelta时间,它无法直接作出直方图,所以先换算成数值。换算的方式直接除timedelta函数即可,这里的np.timedelta64(1, ‘D’),D表示天,1表示1天,作为单位使用的。因为max-min已经表示为天了,两者相除就是周期的天数数字。
同时绘图的时候我们加上布尔判定,选出两次消费以上的用户,查看他们的分布。

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’)来获得日期差数值。

print(user_purchase_retention.head())

然后将时间差值分桶。我这里分成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)
print(pivoted_retention_trans.head())

直接用金额表达不出留存率,我们还是按照之前的算法,把消费过的转换成1,未消费过的转换成0,毕竟留存率看的是是否有消费。

ax = (pivoted_retention_trans.sum()/pivoted_retention_trans.count()).plot.bar()
ax.set_xlabel('时间跨度(天)') 
ax.set_ylabel('百分数(%)') 
ax.set_title('各时间段的用户留存率')


如图,第一个月的留存率高达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天短信推送。

五、完整代码


#导入库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime

#设定绘图风格
plt.style.use('ggplot')
# 四个列名分别表示用户ID, 购买日期, 购买数量, 购买金额
columns =['user_id', 'order_dt', 'order_products', 'order_amount']
# 读取用户行为数据,sep='\s+' :数据以空格(1~∞ )间隔分割数据列
df=pd.read_table('CDNOW_master.txt',names = columns ,sep='\s+')

#读取前五行数据
print(df.head())

#读取数据基本信息
print(df.describe())

#读取数据属性
print(df.info())

#增加两个新时间序列
#将order_dt转化成时间格式
df['order_date']=pd.to_datetime(df.order_dt,format='%Y%m%d')
#将order_date转化成每月起始第一天
df['month']=df.order_date.values.astype('datetime64[M]')

#按用户ID进行分组
user_grouped=df.groupby('user_id').sum()
print(user_grouped)

print(user_grouped.describe())

plt.rcParams['font.sans-serif']=['SimHei']# 用来正常显示中文标签
ax=df.groupby('month').order_products.sum().plot()
ax.set_xlabel('月份')
ax.set_ylabel('CD碟数(张)')
ax.set_title('不同月份的用户购买张数')
plt.show()

ax=df.groupby('month').order_amount.sum().plot()
ax.set_xlabel('月份')
ax.set_ylabel('消费金额(美元)')
ax.set_title('不同月份的用户消费金额')
plt.show()

ax=df.groupby('user_id').sum().plot.scatter('order_amount','order_products')
ax.set_xlabel('消费金额')
ax.set_ylabel('CD碟数(张)')
ax.set_title('每个用户消费金额与购买个数的关系')
plt.show()

plt.figure(figsize=(12,4))
plt.subplot(121)
ax=df.groupby('user_id').order_products.sum().plot.hist(bins=50)
ax.set_xlabel('CD碟数(张))')
ax.set_ylabel('用户数(人)')
ax.set_xlim(0,150)
ax.set_title('用户购买碟数分布直方图')

plt.subplot(122)
ax=df.groupby('user_id').order_amount.sum().plot.hist(bins=50)
ax.set_xlabel('消费金额(美元)')
ax.set_ylabel('用户数(人)')
ax.set_xlim(0,2000)
ax.set_title('用户消费金额分布直方图')

print(df.groupby('user_id').month.min().value_counts())

print(df.groupby('user_id').month.max().value_counts())

#创建数据透视表,对每个用户的每月订单数计数
pivoted_counts=df.pivot_table(index='user_id',columns='month',values='order_dt',aggfunc='count').fillna(0)
print(pivoted_counts.head())

pivoted_counts_transf =pivoted_counts.applymap(lambda x:1 if x>1 else np.NAN if x ==0 else 0)
print(pivoted_counts_transf.head())

ax=(pivoted_counts_transf.sum()/pivoted_counts_transf.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_counts_transf.sum())
ax.plot(pivoted_counts_transf.count())
ax.set_xlabel('时间(月)')
ax.set_ylabel('用户数(人)')
ax.set_title('每月消费和二次消费以上用户人数')
legends=['消费人数', '二次消费以上人数']
ax.legend(legends,loc = 'upper right')
plt.show()

#为了不跟上图混淆,我们还是建立个新的透视表,虽然内容是一样的
pivoted_amount = df.pivot_table (values = 'order_dt', index= 'user_id', columns = 'month', aggfunc = 'count').fillna(0)
#转化数据,有过购买的为1,没有购买的为0
pivoted_purchase = pivoted_amount.applymap(lambda x: 1 if x>0 else 0)
pivoted_purchase.head()

#定义函数,每个月都要跟后面一个月对比下,本月有消费且下月也有消费,则本月记为1,下月没有消费则为0,本月没有消费则为NaN,由于最后个月没有下月数据,规定全为NaN
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()

columns=df.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('十八个月内用户回购率图')

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')


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('active')
    return pd.Series(status)


pivoted_purchase_status = pivoted_purchase.apply(active_status, axis=1)
pivoted_purchase_status.columns = columns
pivoted_purchase_status.head()

pivoted_status_counts = pivoted_purchase_status.replace('unreg', np.NaN).apply(pd.value_counts)
print(pivoted_status_counts.head())

ax=pivoted_status_counts.fillna(0).T.plot.area(figsize=(12,6))
ax.set_xlabel('时间(月)')
ax.set_ylabel('用户数(人)')
ax.set_title('每月各类用户类型占比面积图')

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()

#新建一个对象,按用户的消费金额生序。新增一列,使用cumsum累加函数,逐行计算累计的金额。
user_amount = df.groupby('user_id').order_amount.sum().sort_values().reset_index()
user_amount['amount_cumsum'] = user_amount.order_amount.cumsum()
print(user_amount.tail())

#计算出各阶段金额占总金额的百分比
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('用户累计贡献金额百分比')

#用户生命周期
user_purchase = df[['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()
print(life_time.describe())

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()

len(life_time[life_time.life_time > 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'))


print(user_purchase_retention.head())

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)
print(pivoted_retention_trans.head())

ax = (pivoted_retention_trans.sum()/pivoted_retention_trans.count()).plot.bar()
ax.set_xlabel('时间跨度(天)')
ax.set_ylabel('百分数(%)')
ax.set_title('各时间段的用户留存率')

#创建函数,返回时间差
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()

六、备注
若有错误,还望指出,我会及时更新,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 今天我要和你分享一个不变定律 “ 选择大于努力 ”。 大多数的人都有个观念,努力才能成就事业和梦想,可是辛勤努力了...
    育儿藏经阁阅读 257评论 0 0
  • 佛眼低垂处,生死皆疲劳。大千世界,芸芸众生,最重要的是放下执念。 《生死疲劳》描述了以土地为背景的中国农村五十年发...
    耕途阅读 763评论 0 9
  • 前几日姑姑家的妹妹来找我玩,我长她六岁,她是三个妹妹中最与我交好的,也难怪,毕竟我们在一起的日子最多,用她的话讲“...
    停下的猫阅读 383评论 0 1