跳转至

你好,我是黄佳。

今天我们正式进入“业务场景闯关篇”模块。我在开篇词中介绍过,在这个模块中,我会围绕电商场景下的运营环节,带你挑战5个关卡:获客关、变现关、激活关、留存关和裂变关,帮你逐步掌握机器学习的相关知识和实操技巧。今天,我们就从第一关“获客关”开始!

人们常说移动互联网的运营已经进入了下半场,几乎所有的企业都希望能用更优质的产品和更精准的服务留住用户,这就需要制定出合适的获客策略。而要做到这一点,前提就是为用户精准画像,也就是根据用户的人口统计信息和消费行为数据,给用户分组,然后推测出用户的消费习惯和价值高低。

所以,为用户分组、画像,找到不同用户的特点,进而挖掘出哪些才是最有价值的用户,是目前互联网大厂中的数据分析师和机器学习工程师常做的工作。既然如此,那么我们就在获客关,结合一个具体的电商项目,来看看怎么根据用户的基本信息和消费行为数据,给用户分组画像。

定义问题

按照我们前两讲所说的机器学习“实战5步”,我们首先要做的就是,把项目的问题定义清楚。在我们这个项目中,你可以想象自己就职于一家名为“易速鲜花”的创业公司,担任这家公司的运营团队机器学习工程师。你现在要接手的第一个项目就是为公司的用户分组画像。

现在,你们的运营部门已经准备好了过往用户的基本信息和消费行为数据,你可以在这里下载获取它,然后用Excel打开,就能看到这个数据集中所包含的详细信息。

既然我们要从这份销售订单数据中,为用户分组画像,那么有一个关键的问题就是,我们以什么为依据,给用户做分组?

我们知道,用户的消费行为本身是不能直接用于数据分析的,这就需要我们把用户的行为转化成具体的数值,也就是可量化的指标,让我们对用户有更直观的认识。而且我们还可以将这些指标用在数据分析、广告精准投放、产品推荐系统等多个运营场景,来提升我们产品和服务的精准度。

这个具体的数值呢,就是RFM(Recency、Frequency、Monetary ),它是用户画像的衍生品,也是目前很多互联网厂商普遍采用的分析方式。具体来看:

  • R是新进度,代表自用户上次消费以来的天数。这个指标其实也就是用户的热乎度,比如说你上次使用App是一个月前,你的系统里R值就是30。如果你今天又用了一次App,那R值就会立刻被更新为1;
  • F是消费频率,代表用户是否频繁使用服务。这也是用户黏性的风向标;
  • M是消费金额,代表用户在一段时间内消费的总金额,这个不难理解。

通过用户消费行为的新进度、消费的总体频率,还有消费总金额这三项指标,我们可以将用户划分为不同的类别或集群,来直观地描述用户的价值。什么意思呢?简单来说就是,只要我们从用户的基本信息和消费行为数据中得出RFM值,就可以根据它对用户分组画像了。

所以,我们这个项目整体上可以分为两个阶段:第一个阶段是求出RFM值,第二个阶段就是利用RFM值,给用户分组画像,进而绘制出高价值、中等价值和低价值用户的分布情况。我们今天这节课的目标就是解决第一阶段的问题。

搞清楚问题后,现在我们来看看这份数据的整体状况,根据需要对数据进行一个初步的预处理。

数据预处理

由于已经完成了数据收集工作,我们现在直接用Pandas中的read_csv工具,把这个原始数据集读入到Pandas的DataFrame中,用Dataframe形式呈现出来。关于Pandas包及其导入方式,如果你感觉比较陌生,可以再回顾一下第三讲的内容。

import pandas as pd #导入Pandas
df_sales = pd.read_csv('易速鲜花订单记录.csv') #载入数据
df_sales.head() #显示头几行数据  

运行这段代码后,输出如下:

从图中,你可以看到这个数据集主要包含了订单号、产品码、消费日期,产品说明、数量、单价、用户码和城市等字段。因为我们求的是用户消费行为的新进度(R)、消费的总体频率(F),还有消费总金额(M),所以在这些信息中,我们重点关注的是以下几个数据:

  • 用户码
  • 单价
  • (订单中产品的)数量
  • 消费日期

为了对公司运营团队交给我们的数据集有一个宏观上的了解,下面我们先做一个整体的数据可视化,看看这个数据集所覆盖的消费日期跨度是怎样的。

1.数据可视化

我们直接来看具体的实现代码:

import matplotlib.pyplot as plt #导入Matplotlib的pyplot模块
#构建月度的订单数的DataFrame
df_sales['消费日期'] = pd.to_datetime(df_sales['消费日期']) #转化日期格式
df_orders_monthly = df_sales.set_index('消费日期')['订单号'].resample('M').nunique() #每个月的订单数量
#设定绘图的画布
ax = pd.DataFrame(df_orders_monthly.values).plot(grid=True,figsize=(12,6),legend=False)
ax.set_xlabel('月份') # X轴label
ax.set_ylabel('订单数') # Y轴Label
ax.set_title('月度订单数') # 图题
#设定X轴月份显示格式
plt.xticks(
    range(len(df_orders_monthly.index)), 
    [x.strftime('%m.%Y') for x in df_orders_monthly.index], 
    rotation=45)
plt.show() # 绘图

在这段代码中,你需要注意的是,我们这里使用了Pandas的to_datetime这个API,把原始消费日期转换成了能处理的格式。而在df_orders_monthly中,则是求出了每一个月的订单数量。再往后的代码是具体的绘图工具使用,我就不过多说明了。

输出如下:

我们看到,这个数据集收集了“易速鲜花”公司一整年的订单量。所以啊,我们要求的消费额M,实际上是每个用户一整年的总消费额。

你可能已经注意到,在最后一个月,也就是2021年6月,订单量突然大幅下降。其实这是因为运营人员拉这个表的时候,正是6月的第一个礼拜。所以,6月的数据虽然不全,但并不会影响我们对用户RFM值的分析。

2.数据清洗

完成了初步可视化之后,我们来清洗一下数据。请你注意,在刚才的可视化过程中,我们已经完成了对消费日期的观察,并没有发现什么异常。所以,现在我们重点要处理的是用户码、单价和(订单中产品的)数量。

首先,我们用Pandas中的drop_duplicates方法把完全相同的重复数据行删除掉。

df_sales = df_sales.drop_duplicates() #删除重复的数据行

我之前介绍过,你还可以用DataFrame的isna().sum()函数,来统计NaN的个数。当然,如果你尝试了这个方法,会发现这个数据集中没有NaN值。

此外,对于数量、金额等类型的数据,我们还常常会使用describe方法来查看这些字段的统计信息是否有脏数据。

plain
df_sales.describe() #df_sales的统计信息

输出如下:

在图中你可以看到这个数据集中,共有9万多行的数据(count统计数据条目的数量),每条数据的平均采购数量是10(mean统计均值),商品平均单价是3.575元左右。

在概览中我们发现,(订单中产品的)数量的最小值(min)是一个负数(-9360),这显然是不符合逻辑的,所以我们要把这种脏数据清洗掉。具体的处理方式是,用loc属性通过字段名(也就是列名)访问数据集,同时只保留“数量”字段大于0的数据行:

df_sales = df_sales.loc[df_sales['数量'] > 0] #清洗掉数量小于等于0的数据

在DataFrame对象中,loc属性是通过行、列的名称来访问数据的,我们做数据预处理时会经常用到;还有一个常被用到的属性是iloc,它是通过行列的位置(也就是序号)来访问数据的。

由于数据行的名称往往也是数值,这两者非常容易被弄混。所以,在这里我特地给你做了一张图,来说明什么是行列名、什么是行列序号,帮你弄清楚它们的区别。

这个语句执行后,如果你再用一次describe方法,就会看到新的最小数量为1,这就对了。

到这里,我们就完成了数据清洗工作,这份数据集中已经没有可以剔除的脏数据了。接下来,我们就看看怎么从中求得RFM值。

求RFM值

不过在计算RFM之前,我想请你再回顾一下我们整个项目的目标。我们的目标就是建立一个机器学习模型,给用户做价值分组。而其中,RFM实际上就是我们构建模型所需的关键特征数据。

讲到这里,我想你应该很清楚了,虽说我们这节课要求出RFM的值,但实际上,我们是在完成机器学习项目中的“特征工程”环节,也就是对原始数据集中的信息进行选择、提取、合并、加工、转换,甚至是基于原始信息构建出新的、对于模型的训练更具有意义的特征。

明白了这一点,我们就可以继续往下走了。那怎么计算这个RFM值呢?

其实,这里的R值(最近一次消费的天数)和F值(消费频率),我们通过数据集中的消费日期就能得到,但是对于M值(消费金额),你会发现数据集中有采购数量,有单价,但是还没有每一笔消费的总价。

因此,我们通过一个语句对原有的数据集进行一个小小的扩展。在df_sales 这个DataFrame对象中增加一个数据列计算总价,总价等于由单价乘以数量:

df_sales['总价'] = df_sales['数量'] * df_sales['单价'] #计算每单的总价
df_sales.head() #显示头几行数据       

输出如下:

现在,在这个数据集中,用户码总价消费日期这三个字段,给我们带来了每一个用户的R、F、M信息。其中:

  • 一个用户上一次购物的日期,也就是最新的消费日期,就可以转化成这个用户的R值;
  • 一个用户下的所有订单次数之和,就是消费频率值,也就是该用户的F值;
  • 把一个用户所有订单的总价加起来,就是消费金额值,也就是该用户的M值。

不过,我们目前的这个数据集是一个订单的历史记录,并不是以用户码为主键的数据表。而R、F、M信息是和用户相关的,每一个用户都拥有一个独立的R值、F值和M值,所以,在计算RFM值之前,我们需要先构建一个用户层级表。

  1. 构建用户层级表

构建用户层级表,简单来说就是生成一个以用户码为关键字段的 Dataframe对象df_user,然后在这个Dataframe对象中,逐步加入每一个用户的新近度(R)、消费频率(F)、消费金额(M),以及最终总的分组信息。

在代码实现上,我们用Dataframe的unique() 这个API,就能创建出以用户码为关键字段的用户层级表df_user,然后我们再设定字段名,并根据用户码进行排序,最后显示出这个表,就可以了:

df_user = pd.DataFrame(df_sales['用户码'].unique()) #生成以用户码为主键的结构df_user
df_user.columns = ['用户码'] #设定字段名
df_user = df_user.sort_values(by='用户码',ascending=True).reset_index(drop=True) #按用户码排序
df_user #显示df_user

请你注意,在上述语句中,reset_index(drop=True)的意思是重置索引,生成新的默认数值类型索引,并且不保留原来的索引。运行这段代码后,我们得到这样的结果:

可以看到,我们一共有980个用户的数据。有了用户层级表,现在我们依次求出RFM,让这个用户层级表的结构越来越完整。

  1. 求出R值

我们知道,R值代表自用户上次消费以来的天数,它与最近一次消费的日期相关。所以,用表中最新订单的日期(拉出来这张表的日期)减去上一次消费的日期,就可以确定对应用户的R值。

下面是具体的代码实现,在每行代码中都给出了详细的注释,而且Pandas的语句也都不难理解,你可以看一看:

df_sales['消费日期'] = pd.to_datetime(df_sales['消费日期']) #转化日期格式
df_recent_buy = df_sales.groupby('用户码').消费日期.max().reset_index() #构建消费日期信息
df_recent_buy.columns = ['用户码','最近日期'] #设定字段名
df_recent_buy['R值'] = (df_recent_buy['最近日期'].max() - df_recent_buy['最近日期']).dt.days #计算最新日期与上次消费日期的天数
df_user = pd.merge(df_user, df_recent_buy[['用户码','R值']], on='用户码') #把上次消费距最新日期的天数(R值)合并至df_user结构
df_user.head() #显示df_user头几行数据

输出如下:

R值越大,说明该用户最近一次购物日距离当前日期越久,那么这样的用户就越是处于休眠状态。从表中可以看出来,编号为14682的用户已经有187天没有购物了。所以我们就可以判断这个用户呈现休眠态,很可能已经被别的购物平台所吸引了,也就是流失了。

  1. 求出F值

类似地,我们还可以求出F值,并把F值添加至用户层级表:

df_frequency = df_sales.groupby('用户码').消费日期.count().reset_index() #计算每个用户消费次数,构建df_frequency对象
df_frequency.columns = ['用户码','F值'] #设定字段名称
df_user = pd.merge(df_user, df_frequency, on='用户码') #把消费频率整合至df_user结构
df_user.head() #显示头几行数据

这段代码的核心就是通过给消费日期做count()计数,来求出每一个用户的消费次数。最后,我们得到的输出结果如下:

  1. 求出M值

M值很容易求出,它就是用户消费的总和:

df_revenue = df_sales.groupby('用户码').总价.sum().reset_index() #根据消费总额,构建df_revenue对象
df_revenue.columns = ['用户码','M值'] #设定字段名称
df_user = pd.merge(df_user, df_revenue, on='用户码') #把消费金额整合至df_user结构
df_user.head() #显示头几行数据

这段代码的核心是通过给用户每张订单的总价字段做sum()计数,来求出每一个用户的消费总和。最后,输出如下:

到这里,我们就求出了每一个用户的R、F、M值。不过,这只是完成了特征工程环节,在下节课,我们要根据这三个维度的值给用户分组,这就需要聚类算法大显身手了。

总结一下

现在,我们来回顾一下这节课的重点。

我们这个项目的目标是为用户分组画像。要做分组画像,RFM分析是一个不错的方法,它也是目前很多互联网厂商普遍采用的分析方式。

我们今天的重点是,根据用户码、总价和消费日期这三个字段,从消费历史数据中求出每位用户的R、F、M的值,这就好像给用户贴上了一堆数字化的标签。同时,用户行为的量化,也可以视作是进一步做机器学习项目之前的特征工程。这也为我们后续给用户做聚类分组做好了准备。

理解RFM分析的精髓,并利用好RFM三个指标,是我们这个项目实战的核心价值所在。求出了RFM值,就恭喜你闯过了我们获客阶段的数据探索这一关。

虽然我把实战的具体过程讲得很细,但我更希望你理解的是“数据处理之道”, 而不是只学几个Python语句。Python语句都很简单,但是怎么灵活应用数据,是我们一开始就应该领悟的“道”,因为只有对思路的深入理解,才能让我们走得更高、更远。

思考题

好,这节课就到这里了,最后,我给你留个思考题:

RFM模型是衡量客户价值和客户创利能力的重要工具和手段,而且也被广泛应用在企业的获客、促销、客户关系管理等诸多营销、推广环节。请你谈一谈你对RFM的理解,以及这些指标可以应用于哪些业务场景。

欢迎你在留言区和我分享你的观点,如果你认为这节课的内容有收获,也欢迎把它分享给你的朋友,我们下一讲再见!

精选留言(15)
  • GAC·DU 👍(15) 💬(2)

    上班偷偷摸鱼,把老师分享的课程由粗到细品味了三遍,老师不仅技术高超,运营也这么牛,真是望尘莫及,在没订阅课程之前,我一点运营的知识都没有,看完今天的课程感觉自己又可以了。聊下老师的思考题,我想起当年的瑞幸和滴滴就这么搞的,通过下载APP注册,发送大量的消费券,获取很多的用户。然后经过时间的沉淀,利用RFM技术对用户进行分组,对于用户粘性较高的,进行精细化推荐。对于用户粘性较低的,发送消费券,进行召回用户。对于用户关系管理,可以把R理解为最近一次见面的时间,F理解为多长时间见一次面,M理解为见面聊天的深度,如果只是say hi, 那M的值很低,如果是畅谈人生,M值就会很高。 群里老师说把解决google colab中文乱码的方法分享出来,我说一下大概的思路,然后我会在最后根据本次课程的代码分享测试地址(需要科学上网访问)。 首先是下载你喜欢的中文字体,然后通过FontProperties来指定你下载的字体和展示大小,最后在需要展示中文的地方进行调用。 https://colab.research.google.com/drive/1EuXud71LiM6QsNIA9hsCeOlMiyrB6zLV?usp=sharing

    2021-09-08

  • 茜茜 👍(14) 💬(3)

    我觉得RFM适用于高频交易的场景,如零售,不适用于低频交易场景,如赛事演出票务。通过RFM将可以用户分为高中低等价值用户,在需要对某些产品进行营销推广时,可以将高价值用户定为主要营销群体,从而获得更高的订单转化率。但在计算RFM时,可能会面临以下问题:1.用户id与用户并不是一一对应或用户id不统一:如用户有多个账号,或者是多人使用该用户的id发生购买行为,或者部分消费记录未记入该用户,通过的RFM值无法真实反应用户的消费情况。2.关于F值:对于耐用性高的商品,可能一年就买一次,这时可以去掉F值里的时间限制,用该用户累计购买值代替。最后我的感悟是:模型只是基础,不能直接套用,需要结合相应业务场景对模型进行不同程度的修正,来满足业务运营的目标。

    2021-09-08

  • 海林Lin 👍(9) 💬(2)

    课程很有收获,有个问题请教老师,为什么说RFM分析能够应用在获客环节呢,个人理解这个时候往往没有用户的行为数据

    2021-09-09

  • Geek_80f43d 👍(8) 💬(1)

    近度不应该是datetime.now()和最近一次的消费时间差值吗

    2021-11-16

  • yk 👍(4) 💬(2)

    佳哥好,有个问题,现在例子的数据集都比较小,如果是上亿的数据,也是这么处理吗?全加载到内存会不会很大。

    2022-02-14

  • !null 👍(2) 💬(1)

    对于实战课,手不能懒,得敲一下熟练熟练。

    2021-09-08

  • 小杰 👍(1) 💬(1)

    比如客服,针对不同的用户,推送不同的内容,针对R最新消费的时间越短的用户,可以推送更多的新品;针对F消费频率高的用户:可以推送更多的优惠;针对M消费金额高的用户:可以推送更高质量的商品。最后推荐搭大家走一遍代码流程,具体细节可以不用关注,老师说了,代码是很简单的,要知道如何分析,处理,提取数据,这个才是重点

    2023-02-12

  • Geek_ro 👍(1) 💬(2)

    F值为什么不是订单号的unique数?这里单纯对消费日期计数的话不会导致重复计数吗?

    2022-12-31

  • 安迪密恩 👍(1) 💬(1)

    业务场景:大数据杀熟。

    2022-07-21

  • 在路上 👍(1) 💬(2)

    佳哥好,看到github上更新了这节课的最新代码,非常开心,这对于我这种初次接触Jupyter和Python的学员来说太重要了,调试非常方便。 RFM 模型是我今天学到的新知识,F代表行为的频率,M代表行为的程度,如果行为是消费,M就是消费金额,如果行为是充值,M就是充值金额,F和M是行为的两个特征,就像我们用振频和振幅来描述振动的特征。R代表行为最近发生的时间,RFM从不同维度描述了一个行为,当然具体的行为可能还能找到特殊的特征。 把行为量化成数值,就可以根据数据聚类,大部分行为都会呈现28分布,就像80%的性能问题是20%的代码引起的,而真正的原因往往出乎你的预料,所以通过聚类可以让我们认识到事先想不到的问题。

    2021-09-08

  • 贝贝 👍(0) 💬(1)

    拉了几行数据,终于明白这里R值的求解意思....orz df_recent_buy['最近日期'].max()是指最近日期这一列的最大值,也就是最近的一天。df_recent_buy['最近日期']是每一个用户上次购买的日期。 实际使用中大概应该使用 datetime.now() - df_recent_buy['最近日期'], 但是因为历史数据,now每次都变, df_recent_buy['最近日期'] 确实不变的,会导致每次跑数据R值都不一样,所以用了max吧 用户码 最近日期 R R_max R_latest 0 14681 2021-03-30 15:52:00 70 2021-06-09 12:31:00 2021-03-30 15:52:00 1 14682 2020-12-04 12:12:00 187 2021-06-09 12:31:00 2020-12-04 12:12:00 2 14684 2021-05-15 11:33:00 25 2021-06-09 12:31:00 2021-05-15 11:33:00 3 14687 2021-02-23 11:59:00 106 2021-06-09 12:31:00 2021-02-23 11:59:00 4 14688 2021-06-02 12:26:00 7 2021-06-09 12:31:00 2021-06-02 12:26:00

    2024-03-28

  • 贝贝 👍(0) 💬(1)

    用户码 最近日期 R R_max R_latest 0 14681 2021-03-30 15:52:00 70 2021-06-09 12:31:00 2021-03-30 15:52:00 1 14682 2020-12-04 12:12:00 187 2021-06-09 12:31:00 2020-12-04 12:12:00 2 14684 2021-05-15 11:33:00 25 2021-06-09 12:31:00 2021-05-15 11:33:00 3 14687 2021-02-23 11:59:00 106 2021-06-09 12:31:00 2021-02-23 11:59:00 4 14688 2021-06-02 12:26:00 7 2021-06-09 12:31:00 2021-06-02 12:26:00

    2024-03-28

  • honmio 👍(0) 💬(1)

    哪位可以共享一下测试数据呢? github上的文件已经无法下载了,先行谢过

    2024-01-12

  • 静静呀 👍(0) 💬(1)

    一千多行负数,有没有可能是退货呢?属于正常数据

    2023-11-01

  • Geek_5b6270 👍(0) 💬(1)

    文件下载不了了

    2023-01-31