用Python分析用户消费行为

目录

1.数据读取

2.数据概述

3.数据清洗和整理

4.数据分析和可视化



1.数据读取

pandas,numpy,matplotlib.pyplot是第三包,每次使用前都要先载入。这次多加了一个datetime。

每次绘图前,都需要载入 import matplotlib.pyplot as plt

%matplotlib inline是jupyter自带的方式 - 允许图表在cell中输出。

plt.style.use('ggplot')使用R语言中的ggplot2配色作为绘图风格,纯粹为了好看。

接下来读取源文件。

观察源文件,里面是没有列名称,所以先加列名,方便后面操作。

new point:如何添加列名

分隔符: \s+

源文件是txt,所以用read_table

歪一下楼,查看df的数据类型的时候,显示order_dt, month都是datetime64[ns];单独查看的时候又不一定。

但是用info()查看的时候,显示也是datetime64[ns]。纯属好奇,不影响后面的操作。


2.数据概述

数据来源CDNow网站的用户购买明细。一共有用户ID,购买日期,购买数量,购买金额四个字段。我们通过案例数据完成一份基础的数据分析报告

观察数据,order_dt表示时间,但现在它只是年月日组合的一串数字,没有时间含义。购买金额是小数。值得注意的是,一个用户在一天内可能购买多次,用户ID为2的用户就在1月12日买了两次,这个细节不要遗漏。


3.数据清洗和整理

没有空值,很干净的数据。


 

4.数据分析和可视化

用户平均每笔订单购买2.4个商品,标准差在2.3,稍稍具有波动性。中位数在2个商品,75分位数在3个商品,说明绝大部分订单的购买量都不多。最大值在99个,数字比较高。购买金额的情况差不多,大部分订单都集中在小额。

用户平均每笔订单购买2.4个商品,标准差在2.3,稍稍具有波动性。中位数在2个商品,75分位数在3个商品,说明绝大部分订单的购买量都不多。最大值在99个,数字比较高。购买金额的情况差不多,大部分订单都集中在小额。

一般而言,消费类的数据分布,都是长尾形态。大部分用户都是小额,然而小部分用户贡献了收入的大头,俗称二八。

没有空值,很干净的数据。接下来我们要将时间的数据类型转换。

使用函数:

pd.to_datetime(对象,format=)

 

 上图是转化后的格式。月份依旧显示日,只是变为月初的形式

 

pandas中有专门的时间序列方法tseries,它可以用来进行时间偏移,也是处理时间类型的好方法。时间格式也能作为索引,在金融、财务等领域使用较多,这里不再多叙述了。

上面的消费行为数据粒度是每笔订单,我们转换成每位用户看一下。用groupby创建一个新对象。

 

从用户角度看,每位用户平均购买7张CD,最多的用户购买了1033张,属于狂热用户了。用户的平均消费金额(客单价)100元,标准差是240,结合分位数和最大值看,平均值才和75分位接近,肯定存在小部分的高额消费用户。


 接下来按月的维度分析。

按月统计每个月的CD销量。从图中可以看到,前几个月的销量非常高涨。数据比较异常。而后期的销量则很平稳。

金额一样呈现早期销售额多,后期平稳下降的趋势。为什么会呈现这个原因呢?我们假设是用户身上出了问题,早期时间段的用户中有异常值,第二假设是各类促销营销,但这里只有消费数据,所以无法判断。

 

绘制散点图

使用函数:

df.plot.scatter(x=,y=)

绘制每笔订单的散点图。从图中观察,订单消费金额和订单商品量呈规律性,每个商品十元左右。订单的极值较少,超出1000的就几个。显然不是异常波动的罪魁祸首。

绘制用户的散点图,用户也比较健康,而且规律性比订单更强。因为这是CD网站的销售数据,商品比较单一,金额和商品量的关系也因此呈线性,没几个离群点。 

消费能力特别强的用户有,但是数量不多。为了更好的观察,用直方图。


绘制直方图

观察用户消费的金额和购买量

从直方图看,大部分用户的消费能力确实不高,高消费用户在图上几乎看不到。这也确实符合消费行为的行业规律。

-----------------

Q: x轴,y轴分别代表什么?

A : 横坐标代表变量分组,纵坐标代表各变量值出现的频数,这样,各组与相应的频数就形成了一个矩形,即直方图

代入案例,左边的直方图的x轴代表order_amount的分组,一共30组。y轴代表order_amount中对应到各个分组的频数。

Q:为什么在绘制第一个绘制的直方图时,df.order_amount.hist(bins=30)为什么不是 df.order_amount.sum().hist(bin=30) ??

A:因为df.order_amount.sum()是算order_amount的总和,就一个数字。

- -- -- -- -- -- -- -- -

观察完用户消费的金额和购买量,接下来看消费的时间节点

看下消费用户第一次消费的情况:

结果出来了,所有用户的第一次消费都集中在前三个月。

疑问:按逻辑来说,先按照 user_ID分类,然后再取各个ID下的月份最小值。那么出来的应该不仅仅就3个记录。

正常理解,应该是出来的各个ID下的最早月份的一维数据。

回答:如果没有后面的value_counts()的话,出来的结果确实是各个ID底下的最早月份的一维数据。加了value_counts之后,

就把相同的日期相加了。所以最后算出来就三个不同的最早日期的总数。

------------

我们可以这样认为,案例中的订单数据,只是选择了某个时间段消费的用户在18个月内的消费行为 (源文件的时间跨度:1997-01-01 ~1998~06~01,整个跨度为18个月)

 

观察用户的最后一次消费时间。

最后一次的消费理解逻辑跟上面的一样。

绝大部分数据依然集中在前三个月。后续的时间段内,依然有用户在消费,但缓慢减少。

---------------------------------------------------

异常趋势的原因获得了解释,现在针对消费数据进一步细分。(异常指的是 - 金额一样呈现早期销售额多,后期平稳下降的趋势。为什么会呈现这个原因呢?)

我们要明确,这只是部分用户的订单数据,所以有一定局限性。在这里,【我们统一将数据上消费的用户定义为新客。?】

先解释下【复购率】和【回购率】的区别。

复购率 - 单位时间内,消费两次及以上的用户数 / 购买总用户数

回购率 - 上一个时间窗口内的交易用户,在下一个时间窗口内仍旧消费的比率

例如某电商4月的消费用户数1000,其中600位在5月继续消费,则回购率为60%。600位中有300位在5月消费了两次以上,则复购率是50%

接下来分析消费中的复购率和回购率。首先将用户消费数据进行数据透视。

使用函数:

pivot_table()

fillna()

生成的数据透视,月份是1997-01-01 00:00:00表示,比较丑。接下来将其优化成标准格式。 

?怎么优化才能去掉后面的 小时,分钟,秒呢?尝试上网搜了一下,暂时没找到解决方案?

答疑 - 将格式优化好的Month赋予给pivoted_counts.columns就行,如下图所示。

https://www.cnblogs.com/subic/p/9000129.html

https://stackoverflow.com/questions/16176996/keep-only-date-part-when-using-pandas-to-datetime

首先求复购率,【复购率】的定义是【在某时间窗口内消费两次及以上的用户在总消费用户中占比。】

这里的时间窗口是月,如果一个用户在同一天下了两笔订单,这里也将他算作复购用户。

将数据转换一下,消费两次及以上记为1,消费一次记为0,没有消费记为NaN。

【刚才重新理了下思路,下面的表格是以 month 为 列,以user_ID为行,然后对order_dt进行count计算。实现设置好count规则,在某个时间窗口内,这里的时间窗口是月。当月下单一次,记为0;当月下单两次及以上,记为1;当月都没有下单就记为NaN. 因为 复购率 = (在某个时间窗口内)消费两次及以上的用户 / 总消费用户,所以当月下单两次及以上的,只要是同一个user_ID,都算一个用户】

使用函数:

applymap()

https://blog.csdn.net/S_o_l_o_n/article/details/80897376

https://blog.csdn.net/qq_20412595/article/details/81131502

lamdba x: ... 【lamdba没有elif的用法,但是可以使用两个if else

有一个疑问就是,为什么NaN前面为什么要加np ?这个应该是dataFrame,跟numpy有什么关系?

自己试验了下,如果把NaN前面的np删掉,就会出现error - 'name  'NaN' is not defined

用sum和count相除即可计算出复购率。因为这两个函数都会忽略NaN,而NaN是没有消费的用户,count不论0还是1都会统计,所以是总的消费用户数,而sum求和计算了两次以上的消费用户。这里用了比较巧妙的替代法计算复购率,SQL中也可以用。

 

图上可以看出复购率在早期,因为大量新用户加入的关系,新客的复购率并不高,譬如1月新客们的复购率只有6%左右。而在后期,这时的用户都是大浪淘沙剩下的老客,复购率比较稳定,在20%左右。

单看新客和老客,复购率有三倍左右的差距。

接下来计算回购率。回购率是某一个时间窗口内消费的用户,在下一个时间窗口仍旧消费的占比。我1月消费用户1000,他们中有300个2月依然消费,回购率是30%。

回购率是否也可以以order_dt为内容,创建pivot table呢??还是说必须以order_amount为内容呢?

回购率的计算比较难,因为它设计了横向跨时间窗口的对比。

将消费金额进行数据透视,这里作为练习,使用了平均值。

 接着简化表格里面的内容 - 再次用applymap+lambda转换数据,只要有过购买,记为1,反之为0。这样子看起来会更清晰。

新建一个判断函数。data是输入的数据,即用户在18个月内是否消费的记录

status是空列表,后续用来保存用户是否回购的字段。

因为有18个月,所以每个月都要进行一次判断,需要用到循环。if的主要逻辑是,如果用户本月进行过消费,且下月消费过,记为1,没有消费过是0。本月若没有进行过消费,为NaN,后续的统计中进行排除。

用apply函数应用在所有行上,获得想要的结果。 

 

在横向对比的时候,有四个大疑问:

第一个是按照文章码出来的代码,出来的结果是Series, (后面调试的时候系统说是numpy.ndarray)而不是dataFrame。以为是自己漏了哪一步,查了半天。

一个都没漏,代码也没错,运行的代码出来的结果就是Series。如下图所示。

解答方案将重置Series的 index:

第二个纳闷的是Index不是竖着的吗?为什么这里重置的index是横着的columns? 如果pd.Series里面不设置index参数的话,运行出来的结果的columns是0~17

这里如果不想在括号里写index的话,可以在下一行写 columns=columns_month;一样可以达到我们想要的结果。

尝试了一下,出现了error,可能单独写columns的时候,位置没放对。搞完再回头研究下。

 

第三个疑问是 return前面为什么要加一句代码: status.append(np.NaN)????

代码跟前面的一样,但是不可少。因为删掉之后就出现error. 不知道其中的作用是啥??

答 - 尝试了把 status.append(np.NaN)加上,comumns不设,直接return status. 出来的跟上面的一样,都是np.ndarray。

还是没查出来原因。先放着。

 

 

 

第四个疑问就是 NaN为什么一定要加np??  难道出来的表格本身就是Series,而不是dataFrame格式,所以要加np???

答 - 上面的猜想应该是对的。

 

最后的计算和复购率大同小异,用count和sum求出。

#count,sum函数会自动忽略NaN.

 

一开始不太理解 sum()/count()是怎么运行的。后来就单独操作了下sum出来的是什么样的数据,count()出来又是什么样的数据。

所以,是分别按月进行计算的。分别求出每个月的回购率,然后再绘制折线图。

 

 

 

 

从图中可以看出,用户的回购率高于复购,约在30%左右,波动性也较强。新用户的回购率在15%左右,和老客差异不大。

 将回购率和复购率综合分析,可以得出,新客的整体质量低于老客,老客的忠诚度(回购率)表现较好,消费频次稍次,这是CDNow网站的用户消费特征。

 

???新客与老客??第一次下单是新客,但是新客不一定是在一月份下单呀。所以按照逻辑来说,不能说1月份的数据就是新客的数据???

但是这是源数据是截取了1997-01-01 到 1998-05-01 18个月的数据,默认首月下单的就是新客。


接下来进行用户分层,我们按照用户的消费行为,简单划分成几个维度:新用户、活跃用户、不活跃用户、回流用户

以下的时间窗口都是按月统计。

新用户 - 定义是第一次消费。活跃用户即老客,在某一个时间窗口内有过消费。

不活跃用户 - 是时间窗口内没有消费过的老客。

回流用户 - 在上一个窗口中没有消费,而在当前时间窗口内有过消费。

比如某用户在1月第一次消费,那么他在1月的分层就是新用户;他在2月消费过,则是活跃用户;3月没有消费,此时是不活跃用户;4月再次消费,此时是回流用户,5月还是消费,是活跃用户。

分层会涉及到比较复杂的逻辑判断。

函数写得比较复杂,主要分为两部分的判断,以本月是否消费为界。本月没有消费,还要额外判断他是不是新客,因为部分用户是3月份才消费成为新客,那么在1、2月份他应该连新客都不是,用unreg表示。如果是老客,则为unactive。

 

本月若有消费,需要判断是不是第一次消费,上一个时间窗口有没有消费。大家可以多调试几次理顺里面的逻辑关系,对用户进行分层,逻辑确实不会简单,而且这里只是简化版本的。

 unreg状态排除掉,它是「未来」才作为新客,不能算在内。换算成不同分层每月的统计量。

 

生成面积图,比较丑。因为它只是某时间段消费过的用户的后续行为,蓝色和灰色区域都可以不看。只看紫色回流和红色活跃这两个分层,用户数比较稳定。这两个分层相加,就是消费用户占比(后期没新客)。 

左边的是用户回流比率图,

用户回流占比在5%~8%,有下降趋势。所谓回流占比 = 回流用户数 / 总用户数。

另外一种指标叫回流率,指【上个月】多少不活跃消费用户在本月活跃 / 消费。因为不活跃的用户总量近似不变,所以这里的回流率也近似回流占比。


右图是新客比率图。

活跃用户的下降趋势更明显,占比在3%~5%间。这里用户活跃可以看作连续消费用户,质量在一定程度上高于回流用户。


结合回流用户和活跃用户看,在后期的消费用户中,60%是回流用户,40%是活跃用户/连续消费用户怎么判断出来的??),整体质量还好,但是针对这两个分层依旧有改进的空间,可以继续细化数据。

 

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

使用函数:

cumsum() - 累加函数

新建一个对象,按用户的消费金额生序。使用cumsum,它是累加函数。逐行计算累计的金额,最后的2500315便是总消费额

转换成百分比。

绘制趋势图,横坐标是按贡献金额大小排序而成,纵坐标则是用户累计贡献可以很清楚的看到,前20000个用户贡献了40%的消费。后面4000位用户贡献了60%,确实呈现28倾向。

统计一下销量,前两万个用户贡献了45%的销量,高消费用户贡献了55%的销量。

在消费领域中,狠抓高质量用户是万古不变的道理。

 


接下来计算用户生命周期,这里定义第一次消费至最后一次消费为整个用户生命。 

统计出用户第一次消费和最后一次消费的时间,相减,得出每一位用户的生命周期。因为数据中的用户都是前三个月第一次消费,所以这里的生命周期代表的是1月~3月用户的生命周期。因为用户会持续消费,所以理论上,随着后续的消费,用户的平均生命周期会增长。

求一下平均,所有用户的平均生命周期是134天,比预想的高,但是平均数不靠谱,还是看一下用describe()看下分布吧

 

 均值是134,标准差是180,还是有点差距的,有波动~最大值是544。

 

下面开始制作客户生命周期的直方图:

使用函数:

timedelta64()

因为这里的数据类型是timedelta时间,它无法直接作出直方图,所以先换算成数值。

换算的方式直接除timedelta函数即可,这里的np.timedelta64(1, 'D'),D表示天,1表示1天,作为单位使用的。因为max-min已经表示为天了,两者相除就是周期的天数。

 

可以看到大部分用户只消费了一次,所有生命周期的大头都集中在了0天。但这不是我们想要的答案,不妨将只消费了一次的新客排除,来计算所有消费过两次以上的老客的生命周期。 

筛选出lifetime>0,即排除了仅消费了一次的那些人。做直方图。

 

这个图比上面的靠谱多了,虽然仍旧有不少用户生命周期靠拢在0天。这是双峰趋势图。

部分质量差的用户,虽然消费了两次,但是仍旧无法持续,在用户首次消费30天内应该尽量引导。

少部分用户集中在50~300天,属于普通型的生命周期;高质量用户的生命周期,集中在400天以后,这已经属于忠诚用户了。

下面看下400+占老客户比多少,筛选出life_time>0的就是老客户,然后求得比率是31.7%。就是说老客户中,生命周期在400天以上的占比31.7%。

 下面看下40+占总量多少,然后求得比率是15.49%。就是说老客户中,生命周期在400天以上的占总量的15.49%。

 

由下图得知消费两次以上的用户生命周期是276天,远高于总体。从策略看,用户首次消费后应该花费更多的引导其进行多次消费,提供生命周期,这会带来2.5倍的增量。(这个怎么判断出来的)


再来计算留存率,留存率也是消费分析领域的经典应用。

它指用户在第一次消费后,有多少比率进行第二次消费。和回流率的区别是留存倾向于计算第一次消费,并且有多个时间窗口。 

使用函数:

merge() - 它和SQL中的join差不多,用来将两个DataFrame进行合并

how(合并方式)- inner 的方式,对标inner join。即只合并能对应得上的数据。

on(对应标准) - 这里以on=user_id为对应标准

suffxes参数  - 是如果合并的内容中有重名column,加上后缀。

【除了merge,还有join,concat,用户接近,自己搜索查看文档即可。】

 这里将order_date和order_date_min相减。获得一个新的列,为用户每一次消费距第一次消费的时间差值。

下面将日期转换成时间。

下面将时间差值分桶。我这里分成0~3天内,3~7天内,7~15天等,代表用户当前消费时间距第一次消费属于哪个时间段呢。

这里date_diff=0并没有被划分入0~3天,因为计算的是留存率,如果用户仅消费了一次,留存率应该是0。另外一方面,如果用户第一天内消费了多次,但是往后没有消费,也算作留存率0。 

使用函数:

pd.cut()

 用pivot_table数据透视,获得的结果是用户在第一次消费之后,在后续各时间段内的消费总额。

【出来的数据并不是dataFrame的格式,与文章中的不一致。原因不详。】

用pivot_table数据透视,获得的结果是用户在第一次消费之后,在后续各时间段内的消费总额。

计算一下用户在后续各时间段的平均消费额,这里只统计有消费的平均值。虽然后面时间段的金额高,但是它的时间范围也宽广。从平均效果看,用户第一次消费后的0~3天内,更可能消费更多。

 

但消费更多是一个相对的概念,我们还要看整体中有多少用户在0~3天消费。

依旧将数据转换成是否,1代表在该时间段内有后续消费,0代表没有。

 

 

只有2.5%的用户在第一次消费的次日至3天内有过消费,3%的用户在3~7天内有过消费。

数字并不好看,CD购买确实不是高频消费行为。时间范围放宽后数字好看了不少,有20%的用户在第一次消费后的三个月到半年之间有过购买,27%的用户在半年后至1年内有过购买。

从运营角度看,CD机营销在教育新用户的同时,应该注重用户忠诚度的培养,放长线掉大鱼,在一定时间内召回用户购买。

 

怎么算放长线掉大鱼呢?我们计算出用户的平均购买周期 

使用函数:

shift() - x.shift()是往上偏移一个位置,x.shift(-1)是往下偏移一个位置,加参数axis=1则是左右偏移

定义一个计算间隔的函数diff,输入的是group,通过上面的演示,大家也应该知道分组后的数据依旧是DataFrame。我们将用户上下两次消费时间相减将能求出消费间隔了。 

 

 

然后就简单了,用mean函数即可求出用户的平均消费间隔时间是68天。想要召回用户,在60天左右的消费间隔是比较好的。

 

 看一下直方图,典型的长尾分布,大部分用户的消费间隔确实比较短。

不妨将时间召回点设为消费后立即赠送优惠券,消费后10天询问用户CD怎么样,消费后30天提醒优惠券到期,消费后60天短信推送。这便是数据的应用了。

  • 16
    点赞
  • 127
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值