分享

第一篇数据分析项目实战:用户消费行为分析

 昵称70304747 2020-06-19

CD消费分析自己已模仿练习了很多遍,也总结过2篇文章,但任然存在很多问题,主要原因是自己对业务不了解、对pandas的相关操作不熟悉。
如果你已经在其他地方见过CD消费分析,把这段话看完就不用看啦。分享两个自己在练习时遇见的问题:

1、用SQL如何将新用户、回流用户、活跃用户、不活跃用户进行分层。
2、用SQL如何求回购率、复购率
3、关于1、2问题的解决方法可看此视频学习

当然自己写这篇文章的目的是为了加深印象,所谓书读百遍其义自见,练习也是如此。

本篇文章以模仿为主,利用pandas进行数据处理,分析用户消费行为。数据来源CDNow网站的用户购买明细。一共有用户ID,购买日期,购买数量,购买金额四个字段。

具体的分析思路可参考秦路老师七周七数据分析数据下载地址、密码g6vv

《分析步骤》

第一部分:数据类型的处理—字段的清洗
缺失值的处理、数据类型的转化
第二部分:按月数据分析
每月的消费总金额、每月的消费次数、每月的产品购买量、每月的消费人数
第三部分:用户个体消费数据分析
用户消费金额和消费次数的描述统计、用户消费金额和消费次数的散点图、用户消费金额的分布图(二八法则)、用户消费次数的分布图
、用户累计消费金额的占比
第四部分:用户消费行为分析
用户第一次消费时间、用户最后一次消费时间、新老客消费比、用户分层、用户购买周期、用户生命周期。

第一部分:数据类型的处理—字段的清洗

导入常用的库:
import pandas as pd 
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
%matplotlib inline    #‘%’内置的命令,jupyter专有的定义,比如在pycharm不常用到,inline意思是我做好图之后可以在html页面的单元格进行显示
plt.style.use('ggplot')  #更改设计风格,使用自带的形式进行美化,这是一个r语言的风格
  • 加载包和数据,文件是txt,用read_table方法打开,因为原始数据不包含表头,所以需要赋予。字符串是空格分割,用\s+表示匹配任意空白符。
  • 一般csv的数据分隔是以逗号的形式,但是这份来源于网上的数据比价特殊,它是通过多个空格来进行分隔
columns = ['user_id','order_dt','order_products','order_amount']
df = pd.read_table("CDNOW_master.txt",names = columns,sep = '\s+')

列字段的含义:
user_id:用户ID
order_dt:购买日期
order_products:购买产品数
order_amount:购买金额
  • 消费行业或者是电商行业一般是通过订单数,订单额,购买日期,用户ID这四个字段来分析的。基本上这四个字段就可以进行很丰富的分析。
df.head()
  • 观察数据,判断数据是否正常识别。值得注意的是一个用户可能在一天内购买多次,用户ID为2的用户在1月12日买了两次,这个细节不要遗漏。
df.info()
  • 查看数据类型、数据是否存在空值;原数据没有空值,很干净的数据。接下来我们要将时间的数据类型转化。
  • 当利用pandas进行数据处理的时候,经常会遇见数据类型的问题,当拿到数据的时候,首先要确定拿到的是正确的数据类型,如果数据类型不正确需要进行数据类型的转化,再进行数据处理。附:常见pandas数据类型转化
df.describe()
  • 用户平均每笔订单购买2.4个商品,标准差在2.3,稍稍具有波动性。中位数在2个商品,75分位数在3个商品,说明绝大部分订单的购买量都不多。最大值在99个,数字比较高。购买金额的情况差不多,大部分订单都集中在小额。
  • 一般而言,消费类的数据分布,都是长尾形态。大部分用户都是小额,然而小部分用户贡献了收入的大头,俗称二八。
数据类型的转化
df['order_dt'] = pd.to_datetime(df.order_dt,format = '%Y%m%d') #Y四位数的日期部分,y表示两位数的日期部分
df['month'] = df.order_dt.values.astype('datetime64[M]')  

【图】

  • 时间序列的变化方式。
第一部分小结:

到目前为止,我们已经把数据类型处理成我们想要的类型了。我们通过四个字段及衍生字段就可以进行后续的分析了。

第二部分:按月数据分析

接下来我们用之前清洗好的字段进行数据分析。从用户方向、订单方向、消费趋势等进行分析。
1、消费趋势的分析

  • 每月的消费总金额
  • 每月的消费次数
  • 每月的产品购买量
  • 每月的消费人数
    目的:了解这批数据的波动形式。
01-每月消费总金额
grouped_month = df.groupby('month')
order_month_amount = grouped_month.order_amount.sum()
order_month_amount.head()
  • 用groupby创建一个新的对象。这里要观察消费总金额,需要将order_amount求和


  • 按月统计每个月的CD消费总金额。从图中可以看到,前几个月的销量非常高涨。数据比较异常。而后期的销量则很平稳。
02-每月的消费次数(订单数)
grouped_month.user_id.count().plot()
图片发自简书App
  • 前三个月的消费订单数在10000笔左右,后续月份的消费人数则在2500人左右。
03-每月的产品购买量
grouped_month.order_products.sum().plot()
  • 每月的产品购买量一样呈现早期购买量多,后期平稳下降的趋势。为什么会呈现这个原因呢?我们假设是用户身上出了问题,早期时间段的用户中有异常值,第二假设是各类促销营销,但这里只有消费数据,所以无法判断。
04-每月的消费人数(去重)
方法一:
df.groupby('month').user_id.apply(lambda x:len(x.drop_duplicates())).plot()

方法二:
df.groupby('month').user_id.nunique().plot()  #nunique查看数据有多少不同的行
#这里会有一点区别,因为一个人在一个月内可能多笔消费,可能一号买了一点二号买了一点,所以要进行去重的操作
图片发自简书App
  • 每月的消费人数小于每月的消费次数,但是区别不大。前三个月每月的消费人数在8000—10000之间,后续月份,平均消费人数在2000不到。一样是前期消费人数多,后期平稳下降的趋势。
** 附加:上面消费趋势的分析可以通过数据透视表分析 **
df.pivot_table(index = 'month',
              values = ['order_products','order_amount','user_id'],
              aggfunc = {'order_products':'sum','order_amount':'sum','user_id':'count'})
  • 数据透视表是更简单的方法,有了这个之后大家用里面的数据进行作图也是OK的,而且更加的快捷,所以pandas到后面的话解决一个问题会想到两到三个方法。具体看那个方便,那个简单。
第二部分小结:

按月数据分析主要用group by简单了解了消费趋势

第三部分:用户个体消费数据分析

之前我们维度都是月,来看的是趋势。有时候我们也需要看个体来看这个人的消费能力如何,这里划分了五个方向如下:

  • 用户消费金额和消费次数的描述统计
  • 用户消费金额和消费次数的散点图
  • 用户消费金额的分布图(二八法则)
  • 用户消费次数的分布图
  • 用户累计消费金额的占比(百分之多少的用户占了百分之多少的消费额)
01-用户消费金额和消费次数的描述统计
group_user = df.groupby('user_id')
group_user.sum().describe()
  • 从用户角度看,每位用户平均购买7张CD,最多的用户购买了1033张。用户的平均消费金额(客单价)100元,标准差是240,结合分位数和最大值看,平均值才和75分位接近,肯定存在小部分的高额消费用户。
  • 如果大家能够接触到消费、金融和钱相关的数据,基本上都符合二八法则,小部分的用户占了消费的大头
02-用户消费金额和消费次数的散点图
group_user.sum().query('order_amount < 4000'). plot.scatter(x = 'order_amount' , y = 'order_products')
#query后面只支持string形式的值
  • 绘制用户的散点图,用户比较健康而且规律性很强。因为这是CD网站的销售数据,商品比较单一,金额和商品量的关系也因此呈线性,没几个离群点。
03-用户消费金额的分布图(二八法则)
group_user.sum().order_amount. plot.hist(bins = 20)
#bins = 20,就是分成20块,最高金额是14000,每个项就是700
  • 从上图直方图可知,大部分用户的消费能力确实不高,绝大部分呈现集中在很低的消费档次。高消费用户在图上几乎看不到,这也确实符合消费行为的行业规律。
  • 虽然有极致干扰了我们的数据,但是大部分的用户还是集中在比较低的而消费档次。
04-用户消费次数的分布图(二八法则)
group_user.sum().query('order_products < 100').order_products.hist(bins = 40)
  • 到目前为止关于用户的消费行为有一个大概的了解
05-用户累计消费金额的占比(百分之多少的用户占了百分之多少的消费额)
方法一:
user_cumsum = (group_user.sum().sort_values('order_amount').cumsum)/ 2500315.63

方法二:
user_cumsum = group_user.sum().sort_values('order_amount').apply(lambda x: x.cumsum() / x.sum())
user_cumsum
#axis = 0按列计算
#cumsum滚动累加求和
#sort_values排序,升序
  • 按用户消费金额进行升序排序,由图可知50%的用户仅贡献了15%的销售额度。而排名前5000的用户就贡献了60%的消费额。也就是说我们只要维护了这5000个用户就可以把业绩KPI完成60%,如果能把5000个用户运营的更好就可以占比70%—80%之间。
第三部分小结:
第四部分:用户消费行为分析
- 用户第一次消费(首购)。
#在很多行业里面首购是一个很重要的维度,它和渠道息息相关,尤其是针对客单价比较高客户留存率比价低的行业,第一次客户从哪里来可以拓展出很多运营方式。
- 用户最后一次消费
- 新老客消费比
    - 多少用户仅消费了一次
    - 每月新客占比
- 用户分层
    - RFM
    - 新、老、活跃、流失
- 用户购买周期(按订单)
    - 用户消费周期描述
    - 用户消费周期分布
- 用户生命周期(按第一次&最后一次消费)
    - 用户生命周期描述
    - 用户生命周期分布
01-用户第一次消费(首购)
up_user.month.min().value_counts()
  • 求月份的最小值,即用户消费行为中的第一次消费时间。所有用户的第一次消费都集中在前三个月.
02-用户最后一次消费
group_user.month.max().value_counts()
  • 观察用户的最后一次消费时间。用户最后一次消费比第一次消费分布广,大部分最后一次消费集中在前三个月,说明很多客户购买一次就不再进行购买。随着时间的增长,最后一次购买数也在递增,消费呈现流失上升的情况,用户忠诚度在慢慢下降。
03-新老客的消费比
user_life = group_user.order_dt.agg(['min','max'])
user_life.head()
  • user_id为1的用户第一次消费时间和最后一次消费时间为19970101,说明他只消费了一次
用户购买周期
(user_life['min'] == user_life['max']).value_counts()
  • -有一半的用户只消费了一次
04 - 用户分层
rfm = df.pivot_table(index = 'user_id',
                    values = ['order_products','order_amount','order_dt'],
                    aggfunc = {'order_products':'max','order_amount':'sum','order_products':'sum'})

rfm.head()
#order_amount消费总金额,order_products消费产品数,order_products最近一次消费时间
  • order_products求的是消费产品数,把它替换成消费次数也是可以,但是因为我们这里消费次数是比较固定的,所以使用消费产品数的维度。
04-rfm距今天数
rfm['R'] =-(rfm.order_dt - rfm.order_dt.max()) / np.timedelta64(1,'D')
#-(rfm.order_dt - rfm.order_dt.max())结果为时间类型,将时间格式转化为整数或者浮点数的形式,可以除以单位‘D’,也可以用astype转化
rfm.rename(columns ={'order_products':'F', 'order_amount':'M'},inplace = True )
rfm.head()
#rename,重命名
  • R表示客户最近一次交易时间的间隔,客户在最近一段时间内交易的金额。F表示客户在最近一段时间内交易的次数,F值越大,表示客户交易越频繁,反之则表示客户交易不够活跃。M表示客户在最近一段时间内交易的金额。M值越大,表示客户价值越高,反之则表示客户价值越低。
04-用户分层:RFM
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()
  • 用户分层,这里使用平均数
04-用户分层:求和
rfm.groupby('label').sum()
  • M不同层次客户的消费累计金额,重要保持客户的累计消费金额最高
04-用户分层:计数
rfm.groupby('label').count()
  • 不同层次用户的消费人数,之前重要保持客户的累计消费金额最高,这里重要保持客户的消费人数排名第二,但离一般挽留用户差距比较大,一般挽留用户有14074人,重要保持客户4554人
04-RFM用户分层
rfm.loc[rfm.label == '重要价值客户','color'] = 'g'
rfm.loc[~(rfm.label == '重要价值客户'),'color'] = 'r'
rfm.plot.scatter('F','R',c = rfm.color)
#pandas中增加颜色需要新增加一列
  • 从RFM分层可知,大部分用户为重要保持客户,但是这是由于极致的影响,所以RFM的划分应该尽量以业务为准。尽量用小部分的用户覆盖大部分的额度,不要为了数据好看划分等级。
  • RFM是人工使用象限法把数据划分为几个立方体,立方体对应相应的标签,我们可以把标签运用到业务层面上。比如重要保持客户贡献金额最多159203.62,我们如何与业务方配合把数据提高或者维护;而重要发展客户和重要挽留客户他们有一段时间没有消费了,我们如何把他们拉回来
04-用户分层:用户生命周期
pivoted_counts = df.pivot_table(index = 'user_id',
                               columns = 'month',
                               values = 'order_dt',
                               aggfunc = 'count').fillna(0)
pivoted_counts.head()
  • 用户每个月的消费次数,对于生命周期的划分只需要知道用户本月是否消费,消费次数在这里并不重要,需要将模型进行简化
  • 使用数据透视表,需要明确获得什么结果。有些用户在某月没有进行过消费,会用NaA表示,这里用filna填充。
04-用户生命周期
df_purchase = pivoted_counts.applymap(lambda x  : 1 if x > 0 else 0)
df_purchase.tail()
  • 对于尾部数据,user_id2W+的数据是有问题的,因为从实际的业务场景上说,他们一月和二月都没有注册三月份才是他们第一次消费。透视会把他们一月和二月的数据补上为0,这里面需要进行判断将第一次消费作为生命周期的起始,不能从一月份开始就粗略的计算
04-用户分层:用户生命周期状态变化
#此处代码有些许问题
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 status

pivoted_status = df_purchase.apply( active_status,axis = 1)
pivoted_status.head()   
  • 主要分为两部分的判断,以本月是否消费为界。本月没有消费,还要额外判断他是不是新客,因为部分用户是3月份才消费成为新客,那么在1、2月份他连新客都不是,用unreg表示。如果是老客,则为unactive
  • 本月若没有消费,需要判断是不是第一次消费,上一个时间窗口有没有消费。可以多调试几次理顺里面的逻辑关系,对用户进行分层。
  • 《业内主流写法》
  • 这里用户生命周期的状态变化是用数据透视表一次性做的,但在实际业务场景中我们可能用SQL把它作为中间表来处理。我们有了明细表,会通过明细表来计算出状态表;也就是它的数据上个月是什么样的情况得出来,比如上个月是新用户或者回流用户,我们直接用上个月的状态left join本月的状态。直接用SQL进行对比
  • 可以用pandas将每个月的状态计算出来,不是逐行而是月份计算,先算出一月份哪些用户是新购买的,然后判断二月份是否购买,两者left join
每月不同活跃用户的计数
purchase_status_ct = pivoted_status.replace('unreg',np.NaN).apply(lambda x : pd.value_counts(x))
purchase_status_ct
此处代码有部分问题
消费用户构成 = 活跃+新增+回流,不活跃没消费过
  • 由上表可知,每月用户的消费状态变化。活跃用户、持续消费的用户对应的是消费运营质量。回流用户,之前不消费本月才消费对应的是唤回运营。不活跃的用户对应的是流失
  • 这里可以针对业务模型下个定义:流失用户增加,回流用户正在减少
05-用户购买周期(按订单)
order_diff = group_user.apply(lambda x : x.order_dt - x.order_dt.shift())
order_diff.head(10)
#将用户分组后,每个用户的订单购买时间进行错位相减
  • user_id 1为空值,表示该客户只购买过一个订单。user_id为2 的用户第二笔订单与第二笔订单在同一天购买
05-用户消费周期分布
(order_diff / np.timedelta64(1,'D')).hist(bins = 20)
  • 订单周期呈指数分布,用户的平均购买周期是68天,绝大部分用户的购买周期都低于100天。
06-用户生命周期(第一笔订单时间 & 最后一笔订单时间)
(user_life['max'] - user_life['min']).describe()
  • 数据偏移比较大,中位数是0天也就是超过50%的用户他的生命周期是0天只购买了一次,但是平均生命周期有134天,最大值是544天
06-用户生命周期(第一笔订单时间 & 最后一笔订单时间)
((user_life['max'] - user_life['min'])/np.timedelta64(1,'D')).hist(bins = 40)
  • 用户的生命周期受只购买一次的用户影响比较厉害(可以排除),用户均消费134天,中位数仅0天
06-用户生命周期大于0天分布图
u_l = (user_life['max'] - user_life['min'])/np.timedelta64(1,'D')
u_l[u_l > 0].hist(bins = 40)
  • 筛选出lifetime>0,既排除了仅消费了一次那些人,有不少用户生命周期靠拢在0天,部分质量差的用户虽然消费了两次,但是任然无法持续,在用户首次消费30天内应该尽量引导。少部分用户集中在50—300天,属于普通型的生命周期。高质量用户的生命周期,集中在400天以后,这属于忠诚用户。
07-复购率和回购率的分析
#复购率:自然月内,购买多次的用户占比
#回购率:曾经购买过的用户咋某一时期内的再次购买占比
07-回购率
purchase_r = pivoted_counts.applymap(lambda x : 1 if x > 1 else np.NaN if x == 0 else 0)
purchase_r.head()
  • applymap针对DataFrame里的所有数据。用lambda进行判断,因为这里设计了多个结果,所以要两个if else
07-回购率
(purchase_r.sum() / purchase_r.count()).plot(figsize = (10,4))
  • 用sum和count相除即可计算出复购率。因为这两个函数都会忽略NAN,而NAN是没有消费的用户,count不论是0还是1都会统计,所以是总的消费用户数,而sum求何计算了两次以上的消费用户。这里用了比较巧妙的替代法计算复购率,SQL中也可以用。
  • 图上可以看出复购率在早期,因为大量新用户加入的关系,新客的复购率并不高,譬如1月新客们的复购率只有6%左右。而在后期,这时的用户都是大浪淘沙剩下的老客户,复购率比较稳定,在20%左右.
    单看新客和老客,复购率有三倍左右的差距
  • 接下来计算回购率。回购率是某一个时间窗口内消费的用户,在下一个时间窗口人就消费的占比。我1月消费用户1000,他们中有300个2月依然消费,回购率是30%
07-回购率
#和前面用户生命周期是相关的
消费金额进行数据透视
pivoted_amount = df.pivot_table(index = 'user_id',columns = 'month',values = 'order_amount',aggfunc = 'mean').fillna(0)
columns_month = df.month.sort_values().astype('str').unique()
pivoted_amount.columns = columns_month
pivoted_amount.head()
  • 消费变量,1代表这个月消费过,0代表没有消费过
07-回购率
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 status
pivoted_purchase_return = pivoted_purchase.apply(purchase_return,axis = 1)
pivoted_purchase_return.head(5)
  • 0代表当月消费过次月没有消费过,1代表当月消费过次月依然消费
  • 新建一个判断函数。data是输入数据,既用户在18个月内是否消费的记录,status是空列表,后续用来保存用户是否回购的字段。因为有18个月,所以每个月都要进行一次判断,需要用到循环。if的主要逻辑是,如果用户本月进行过消费,且下月消费过,记为1,没有消费过是0.本月若没有进行过消费,为NAN,后续的统计中进行排除。apply函数应用在所有行上,获得想要的结果。
  • 最后计算和复购率大同小异,用count和sum求出,从图中可以看出,用户的回购率高于复购,约在30%左右,和老客户差异不大。从回购率和复购率综合分析可以得出,新客的整体质量低于老客,老客的忠诚度(回购率)表现较好,消费频次稍次,这是CDNow网站的用户消费特征。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多