R 越小越好,F 和 M 越大越好——三个方向不一致,这是后面最大的坑。 聚类不用预设「几分算高」,代价是堆号没有含义,得回头看均值表才认识。 三列标准差 182、7、6750,不标准化的话,聚类实际上只在看金额。
这一篇不讲代码,讲方法:RFM 三个字母怎么算、为什么要取反、为什么一定要标准化。 方法没搞清就写代码,跑出来的图很好看,结论是错的。
一、三个字母,各记一笔账
| 字母 | 含义 | 怎么算 |
|---|---|---|
| R | 最近一次距今多少天 | 观察日减最近下单日 |
| F | 一共下过几次单 | 按客户数订单编号 |
| M | 一共花了多少钱 | 按客户把金额加总 |
三个值都从同一张流水表 groupby 出来,一句 agg 就够,不需要额外的数据源。
R 是越小越好,F 和 M 是越大越好。方向不一致,这里埋着后面最大的坑。
举个数:C0001 最后一单在 2025-03-17,R 就是 105 天;C0004 最后一单在 2024-11-12,R 是 230 天。
C0001 比 C0004 更「新鲜」,但 R 的数更小——数字大小和好坏方向是反的。
二、R 要取反,不然聚类反着走
聚类看的是「谁和谁像」,距离越小越像。R 大表示很久没来,是坏信号。 不处理的话,聚类会把「R 都很大」的人归成一堆——数学上没问题,业务含义反了。 两种处理:把 R 直接变成负数,或者标准化之后再取负。这次用第二种。
做完之后三个值都是越大越好,方向统一,解释起来才不会自己绕晕。 记住这一条:凡是「越小越好」的指标进模型,先想清楚要不要翻个方向。
三、打分法还是聚类法
| 做法 | 怎么分 | 好处 | 坏处 |
|---|---|---|---|
| 分位打分 | 三个值各切五档 | 简单、可解释 | 档位是人切的 |
| 聚类 | 按距离自动分堆 | 不用预设档位 | 堆号没有含义 |
教材用的是聚类。好处是不用先定「几分算高」,让数据自己分。 代价是堆号 0、1、2、3 本身没有意义,必须回头看每堆的 R、F、M 均值才知道谁是谁。 分位打分的优点是能直接说「RFM 得分 555 的是重要客户」,汇报时很顺口。
两条路都能走通,选哪条要看受众:给技术看用聚类,给业务看打分更好讲。
四、标准化:三个量纲差得离谱
R 的量级是几百天,F 是个位数,M 是几千到几万。 实测标准差:R 是 182.05,F 是 7.05,M 是 6750.89。M 是 R 的三十七倍。 距离是各维平方相加,M 那一项平方后比别人大三个数量级,R 和 F 等于没参与。
StandardScaler 把每列变成「减均值除标准差」,三列都变成同量级。
做完标准化,三个维度才真正一人一票。这件事第 8 章岭回归那一节也踩过同一个坑。
五、为什么非要用「距离」
聚类是把「像的人」放一起。像不像,用距离量:三个值差的平方和。 所以每个维度的「单位」必须一样。一个是天、一个是次、一个是元,加起来没有意义。 标准化不是可选项,是聚类的入场券。不做标准化就聚类,等于按金额排序。
想验证这句话,把标准化那一步注释掉再跑一次,看每堆的成员变没变——一定会变很多。
六、动手 Demo:分析方法:RFM 模型与聚类
这一节用真实数据把三件事量出来:三个值的量级差多少、C0001 和 C0004 的 R 差多少。 不建模,只把「为什么必须标准化」变成看得见的数字。
import pandas as pd
df = pd.read_csv("订单流水.csv")
df = df.dropna(subset=["客户编号"])
df = df[df["订单金额"] > 0].drop_duplicates()
df["订单日期"] = pd.to_datetime(df["订单日期"])
rfm = df.groupby("客户编号").agg(
最近购买=("订单日期", "max"),
F=("订单编号", "count"),
M=("订单金额", "sum"))
rfm["R"] = (pd.Timestamp("2025-06-30") - rfm["最近购买"]).dt.days
print("三列标准差:R %.2f,F %.2f,M %.2f"
% (rfm["R"].std(), rfm["F"].std(), rfm["M"].std()))
print("R 越小越好,F 和 M 越大越好——方向不一致")
实跑输出:
--------------------------------------------------------------------
分析方法:RFM 模型与聚类
--------------------------------------------------------------------
① 为什么一个「消费额」不够
--------------------------------------------------------------------
最省事的做法是按总消费额排名,前 20% 就是重要客户。这个做法有两个毛病。
第一,它看不见「最近还来不来」。去年买了十万、今年一单没下的人,照样排在前面。
第二,它看不见「来了几次」。一次买十万和十次各买一万,价值完全不同。
RFM 就是给这三个角度各记一笔账:多久没来、来了几次、花了多少。
② R、F、M 怎么算
--------------------------------------------------------------------
| 字母 | 含义 | 怎么算 |
|---|---|---|
| R | 最近一次距今多少天 | 观察日减最近下单日 |
| F | 一共下过几次单 | 按客户数订单编号 |
| M | 一共花了多少钱 | 按客户把金额加总 |
三个值都从同一张流水表里 groupby 出来,一句 agg 就够。
R 是越小越好,F 和 M 是越大越好。方向不一致,这一步埋着后面最大的坑。
举个数:C0001 最后一单在 2025-06-20,那 R 就是 10 天;
C0002 最后一单在 2024-03-05,R 就是 482 天。
③ R 要取反,不然聚类会反着走
--------------------------------------------------------------------
聚类看的是「谁和谁像」,距离越小越像。R 大表示「很久没来」,是坏信号。
如果不处理,聚类会把「R 都很大」的人归成一堆——数学上没错,业务上含义反了。
两种处理:一是把 R 变成负数,二是标准化之后再取负。这次用第二种。
做完之后,三个值就都是「越大越好」,方向统一,解释起来才不乱。
④ 打分法还是聚类法
--------------------------------------------------------------------
| 做法 | 怎么分 | 好处 | 坏处 |
|---|---|---|---|
| 分位打分 | 三个值各切五档,算总分 | 简单、可解释 | 档位是人为切的 |
| 聚类 | 按距离自动分堆 | 不用预设档位 | 堆号没有含义 |
教材用的是聚类。聚类的好处是不用先定「几分算高」,让数据自己分。
代价是堆号(0、1、2、3)本身没有意义,必须回头看每堆的 R、F、M 均值才知道谁是谁。
这一步是本章最容易被跳过的一步,也是最能看出功力的一步。
⑤ 标准化:三个量纲差得离谱
--------------------------------------------------------------------
R 的量级是几百天,F 是个位数,M 是几千到几万。
不标准化的话,聚类几乎只看 M——距离被 M 一个人占满了,R 和 F 等于没参与。
StandardScaler 把每一列变成「减均值除标准差」,三列都变成同量级。
做完标准化,三个维度才真正「一人一票」。这件事第 8 章的岭回归那一节也踩过。
! 聚类之前先标准化,和回归之前先标准化,是同一条道理。量纲不统一,模型就偏心。
agg 里给三个不同的聚合:日期取 max(最近一次)、订单数 count、金额 sum。
(观察日 - 日期).dt.days 得到的是整数天数,不减的话拿到的是一个 timedelta 对象。
这一步顺手把脏数据剔了。真正的清洗在第 12.3 节,这里只是为了让数字干净点。
30 秒实验
- 把
dt.days去掉,看打印出来是什么类型。 - 把 R 改成
-rfm["R"],想想标准化之后再取负和先取负有没有区别。 - 想一想:如果公司想加一个「平均每单金额」,它和 M 会不会打架?
常见误区
- R 不取反就直接聚类:方向反了,最活跃的客户会被打进「流失」那堆。
- 不标准化就聚类:距离被 M 占满,实际做的是「按消费额排序」。
- 把簇号当业务类别:0、1、2、3 是随机编号,每次重跑都可能变。
- 观察日写成今天:全表 R 天天变,结论没法复现。
自测三问(点开对答案)
1. 为什么 R 要取反? —— R 越小越好,F 和 M 越大越好。方向不统一,聚类会把最活跃的客户和最差的客户搞反。 2. 三个标准差 182、7、6750 说明什么? —— 量纲差三个数量级。不标准化,距离几乎全由 M 决定,R 和 F 等于没参与。 3. 聚类和分位打分,怎么选? —— 聚类不用预设档位,但堆号没有含义;打分可解释性好,但档位是人切的。看受众选。方法清楚了。下一篇回到脏活:9308 行流水里的负金额、空客户、重复单。
- ☐ (本篇完,下一篇 12.3 数据抽取与探索)