①数据分析项目实战:用户消费行为分析

正所谓“纸上得来终觉浅,绝知此事要躬行”,通过对Python进行数据分析的学习掌握,现在需要通过实操总结来检验自己的学习成果。
本篇文章主要是利用Python进行数据处理,分析用户消费行为。数据来源CD网站的消费记录,有用户ID、购买日期、购买数量、购买金额四个字段科目。(模仿操作)
【分析步骤】


CD网站销售数据.png
# 导入常用的库:
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的数据分隔是以逗号的形式,但是这份网上的数据比较特殊,它是通过多个空格来进行分隔。


  • 电子商务行业一般是以用户ID、订单价、订单数、订单日期来进行分析的,基于以上这四个字段可以根据需要进行分析。
df.head()  #展示前5行数据
  • 如图所示,数据正常展示,由此判断数据导入无误。再则由此可以看出用户id为2的用户在12日当天消费了两次,这点需要注意。
df.info()  #查看数据类型
  • 从图中可以看出order_dt的数据类型需要转换为日期形式
  • 查看其他数据类型,没有发现有空值,数据很干净
  • 一般在进行数据处理前,都应先对数据类型进行查看及转换
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.每月消费总金额
grouped_month = df.groupby('month')
order_month_amount = grouped_month.order_amount.sum()
order_month_amount.head()
  • 通过groupby对数据进行分组,由此对order_amount进行求和


  • 如图所示,消费金额在前三个月达到最高峰,此后的消费金额较为稳定,但有轻微下降趋势。
# 2.每月消费次数
grouped_month.user_id.count().plot()  #plot是折线图,用于展示数据趋势
  • 前三个月的消费订单数达到10000笔左右,后续几个月的平均消费是2500笔。
# 3.每月的产品购买量
grouped_month.order_products.sum().plot()
  • 如图所示,前三个月产品购买量达到峰值,后续产品构面量较为稳定。
  • 为什么出现这种情况?我们可以假设这是因为营销活动,另外数据中可能存在异常,因只是消费数据,无法判断。
# 4.每月的消费人数
df.groupby('month').user_id.apply(lambda x: len(x.drop_duplicates())).plot() 
#这里需要注意到因为一个人在一个月内可能存在多笔消费,所以使用匿名函数lambda进行去重
  • 每月消费人数低于每月消费次数,但差异不大;前三个月每月消费人数在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'}).head()
  • 数据透视表是更加直观快捷的方法,通过这个进行作图会更加便捷一些。在以后的实际工作中,我们应根据实际需要选择合适的方法来进行数据分析,这样有利于提高我们的效率。
    第二部分小结:

通过聚合函数groupby了解分析了用户的消费趋势

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

前面是以时间(月)的维度来分析用户消费趋势,这一节以个体消费特点来分析用户消费行为,为此划分了五个方面来分析:

  • 用户消费金额、消费次数的描述统计
  • 用户消费金额和消费次数的散点图
  • 用户消费金额的分布图
  • 用户消费次数的分布图
  • 用户累计消费金额占比(百分之多少的用户占了百分之多少的消费金额)
# 1.用户消费金额和消费次数的描述统计
grouped_user = df.groupby('user_id')
grouped_user.sum().describe()
  • 从图中可以看出,每位用户平均购买了7张CD,但最大值是1033张,中位数却只有3张;而从用户消费上看,用户平均消费金额是106元,标准差是240元,根据分位数和最大值来看,平均值是和75分位数相近,由此说明有小部分用户购买了大量CD。
  • 由此可以看出消费类型的数据,符合二八法则的特点。
# 2.用户消费金额和消费次数的散点图
grouped_user.sum().query('order_amount < 4000').plot.scatter(x = 'order_amount', y = 'order_products')
# 注:query后面只支持string形式的值
  • 跟前面一样先用groupby创建一个对象来绘制散点图,因数据极值关系对‘order_amount’进行过滤,使图更加美观。
  • 如图所示,用户消费比较有规律,消费金额和商品量也呈线性分布,离群点较少。
# 3.用户消费金额的分布图(二八法则)
grouped_user.sum().order_amount.plot.hist(bins = 20)
# bins = 20是指直方图中的个数,是将order_amount的值分成20份
  • 从图中可以看出,大部分用户消费能力并不高,绝大部分是集中在小金额的消费,而高消费用户非常少,也符合二八法则的特点。
# 4.用户消费次数的分布图
grouped_user.sum().query('order_products < 100').order_products.hist(bins = 20)
  • 因小部分异常值会干扰判断,所以使用切比雪夫定理过滤掉异常值,计算95%的数据分布情况
# 5.用户累计消费金额的占比
user_cumsum = grouped_user.sum().sort_values('order_amount').apply(lambda x:x.cumsum() / x.sum())
user_cumsum.reset_index().order_amount.plot()
# 注:sort_values是对值进行排序,升序,cumsum是滚动累加求和
  • 按用户消费金额进行升序排列,由图可知50%的用户仅贡献了15%的消费额度。而排名前5000的用户就贡献了60%的消费额。由此我们可以得出维护好这5000名用户就可以完成业绩指标的60%。
    第三部分小结:

用户个体消费情况基本上符合行业规律,符合二八法则的特点;而在处理数据过程中,需要注意异常值对判断的干扰,应该对异常值进行过滤,由此也能提升图形美观。

第四部分:用户消费行为分析

  • 用户第一次消费(首购)
  • 用户最后一次消费
  • 新老客消费比
    • 多少用户仅消费了一次?
    • 每月新客占比?
  • 用户分层
    • RFM
    • 新、老、活跃、回流、流失
  • 用户购买周期(按订单)
    • 用户消费周期描述
    • 用户消费周期分布
  • 用户生命周期(按第一次&最后一次消费)
    • 用户生命周期描述
    • 用户生命周期分布
# 1.用户第一次消费(首购)
grouped_user.min().order_dt.value_counts().plot()
  • 根据月份最小值可以求出用户第一次消费时间。从图中可以看出,用户第一次购买商品的分布,主要集中在前三个月,其中2月11日-2月25日有一次剧烈波动。
# 2.用户最后一次消费
grouped_user.max().order_dt.value_counts().plot()
  • 图中显示的是用户最后一次消费时间。用户最后一次购买的分布比第一次分布广,大部分最后一次购买,集中在前三个月,说明有很多用户购买了一次后就不再进行购买了,而随着时间的递增,最后一次购买数也在递增,消费呈现流失上升的状况。
# 3.新老客消费比
user_life = grouped_user.order_dt.agg(['min','max'])
user_life.head()
  • 由此可以看出用户第一次消费和最后一次消费时间,例如用户user_id为1的用户消费时间相同,说明用户只消费了一次。
# 用户购买周期
(user_life['min'] == user_life['max']).value_counts()
  • 通过逻辑判断得出,有一半的用户只消费了一次。
# 4.用户分层
rfm = df.pivot_table(index = 'user_id',
                    values = ['order_products', 'order_amount', 'order_dt'],
                    aggfunc = {'order_dt':'max',
                               'order_amount': 'sum',
                               'order_products': 'sum'})
rfm.head()
  • 注:order_products表示的是消费的产品数,因为数据中用户消费次数比较固定,所以由消费产品数替换消费次数。
# 4.1rfm距今天数
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()
  • R表示客户最近一次交易时间的间隔,客户在最近一段时间内交易的金额。F表示客户在最近一段时间内交易的次数,F值越大,表示客户交易越频繁,反之则表示客户交易不够活跃。M表示客户在最近一段时间内交易的金额。M值越大,表示客户价值越高,反之则表示客户价值越低。
# 4.2用户分层: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()
  • 这里运用RFM模型来对用户分层,分析用户特点,这里使用的是平均数。
# 4.3用户分层:求和
rfm.groupby('label').sum()
  • 从图中可以看出,M的不同层次客户的消费累计金额,重要保持客户的累计消费金额最高
# 4.4用户分层:计数
rfm.groupby('label').count()
  • 如图所示,不同层次用户的消费人数,之前重要保持客户的累计消费金额最高,但消费人数最多的却是一般挽留客户有14074人。
# 4.5用户分层: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模型是衡量客户价值的立方体模型。充分结合数据与业务的关系,是数据分析师应该做到的,例如从以上数据看出,重要保持客户贡献金额最多,应如何与业务部分合作维护好这部分客户;再则重要发展客户和重要挽留客户静默消费了,如何唤醒这部分客户使我们需要分析的。
# 4.6用户分层:用户生命周期
pivoted_counts = df.pivot_table(index = 'user_id',
                               columns = 'month',
                               values = 'order_dt',
                               aggfunc = 'count').fillna(0)
pivoted_counts.head()
  • 用户每月的消费次数对于生命周期的划分并无关系,而需把模型进行优化,知道用户本月是否有消费就可以了。
  • 使用数据透视表可以非常直观的看出用户在某月是否有消费过,其中没有消费过的以Nan表示,用fillna填充为0
# 4.7用户生命周期
df_purchase = pivoted_counts.applymap(lambda x: 1 if x > 0 else 0)
df_purchase.tail()
  • 从user_id2W+的数据来看,数据存在问题。因为从实际业务出发,这部分的用户一二月份是没有注册的,是从三月份才进行第一次消费。而数据透视表将一二月份的数据补充为0,这里需要注意是从第一次消费作为生命周期的计算。
# 4.8用户分层:用户生命周期状态变化
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
purchase_stats = df_purchase.apply(active_status, axis = 1)
purchase_stats.head(5)
purchase_stats.tail(5)

这里要分两种情况分析:
若本月没有消费

  • 若之前是未注册,则依旧为未注册
  • 若之前有消费,则为流失/不活跃
  • 其他情况,为未注册

若本月有消费

  • 若是第一次消费,则为新用户
  • 如果之前有过消费,则上个月为不活跃,则为回流
  • 如果上个月为未注册,则为新用户
  • 除此之外,为活跃
# 每月不同活跃用户的计算
purchase_stats_ct = purchase_stats.replace('unreg',np.NaN).apply(lambda x: pd.value_counts(x))
purchase_stats_ct
#purchase_stats_ct.fillna(0).T.head()
purchase_stats_ct.fillna(0).T.plot.area()

由上表可知,每月的用户消费状态变化(图表还不太美观)

  • 活跃用户,持续消费用户,对应的是消费运营质量
  • 回流用户,之前不消费本月才消费,对应的使唤回运营
  • 不活跃用户,对应的是流失

由此我们可以得出一个结论:流失的用户增加,回流用户正在减少

# 5.用户购买周期(按订单)
order_diff = grouped_user.apply(lambda x:x.order_dt - x.order_dt.shift())  
order_diff.head(10)
# 这里运用匿名函数lambda,对分组后的用户根据订单购买时间进行错位相减
order_diff.describe()
  • 从图中可以看出,user_id 1为0,表示该客户只购买过一次;user_id 2的第一笔订单和第二笔订单是在同一天完成的
# 5.1用户消费周期分布
(order_diff / np.timedelta64(1, 'D')).hist(bins = 20)
  • 订单周期呈现指数分布,用户的平均购买周期是68天,绝大部分用户的购买周期都低于100天
# 6.用户生命周期(第一笔订单时间和最后一笔订单时间)
(user_life['max'] - user_life['min']).describe()
  • 从图中可以看出,数据偏差很大,中位数为0天,说明超过50%的用户只购买了一次,这一次主要集中在数据前三个月,但平均数是134天,最大值是544天。
# 6.1用户生命周期分布图
((user_life['max'] - user_life['min'])/ np.timedelta64(1, 'D')).hist(bins = 40)
  • 用户的生命周期受只购买一次的用户影响比较厉害(可以排除),用户均消费134天,中位数仅0天。
# 6.2用户生命周期分布图(过滤掉0天)
u_l = (user_life['max'] - user_life['min'])/np.timedelta64(1,'D')
u_l[u_l > 0].hist(bins = 40)
  • 过滤掉lifetime > 0,即排除了仅消费一次的客户和一些生命周期在0天的两次消费的用户。
  • 如图所示,少部分用户集中在50-300天,属于普通型生命周期;高质量用户体现在生命周期400天以后的用户,这部分用户属于忠诚用户。

7.复购率和回购率分析

  • 复购率:自然月内,购买多次的用户占比

  • 回购率:曾经购买过的用户在某一时间内的再次购买占比

pivoted_counts.head()
  • 用户每个月的消费次数
# 7.1复购率
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。
# 7.2复购率折线图
(purchase_r.sum() / purchase_r.count()).plot(figsize = (10, 4))
  • 用sum和count相除即可求出复购率。因为两个函数都会忽略掉NaN,而NaN代表的是没有消费的用户,count函数不论是0还是1都会统计,所以表示总的消费用户数,而sum求和是计算消费两次以上的用户。
  • 从图中可以看出,前三个月因为有大量新用户涌入,而这批用户购买了一次,所以导致复购率降低,例如1月份的复购率只有6%左右;但到了后期,忠诚客户体现出来了,复购率较为稳定,保持在20%左右。
df_purchase.head()
# 7.3回购率
def purchase_back(data):
    status = []  #创建一个空列表,用来保存用户回购的字段
    for i in range(17):
        if data[i] == 1:   #用户本月消费过下月也消费过记为1
            if data[i+1] == 1:
                status.append(1)
            if data[i+1] == 0:    #用户下月没有消费过记为0
                status.append(0)
        else:
            status.append(np.NaN)  #用户本月没消费,记为NaN
    status.append(np.NaN)
    return status
purchase_b = df_purchase.apply(purchase_back, axis = 1)
purchase_b.head(5)
(purchase_b.sum() / purchase_b.count()).plot(figsize = (10, 4))
  • 注:0表示当月消费过次月没有消费,1表示当月消费过次月依旧有消费
  • 回购率也是由count和sum计算求出的;如图所示,用户回购率为30%左右,由此可以得出:新客整体质量不如老客,老客的忠诚度(回购率)表现较好,消费频次少差。
    第四部分小结:

通过用户消费行为的分析,可以看出各类知识点的运用需要结合实际业务情况,对数据进行过滤

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

推荐阅读更多精彩内容