📊 第12章 Python 实现客户价值分析
📄 一句话概括

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 秒实验

  1. 把 dt.days 去掉,看打印出来是什么类型。
  2. 把 R 改成 -rfm["R"],想想标准化之后再取负和先取负有没有区别。
  3. 想一想:如果公司想加一个「平均每单金额」,它和 M 会不会打架?

常见误区

  1. R 不取反就直接聚类:方向反了,最活跃的客户会被打进「流失」那堆。
  2. 不标准化就聚类:距离被 M 占满,实际做的是「按消费额排序」。
  3. 把簇号当业务类别:0、1、2、3 是随机编号,每次重跑都可能变。
  4. 观察日写成今天:全表 R 天天变,结论没法复现。
自测三问(点开对答案) 1. 为什么 R 要取反? —— R 越小越好,F 和 M 越大越好。方向不统一,聚类会把最活跃的客户和最差的客户搞反。 2. 三个标准差 182、7、6750 说明什么? —— 量纲差三个数量级。不标准化,距离几乎全由 M 决定,R 和 F 等于没参与。 3. 聚类和分位打分,怎么选? —— 聚类不用预设档位,但堆号没有含义;打分可解释性好,但档位是人切的。看受众选。

方法清楚了。下一篇回到脏活:9308 行流水里的负金额、空客户、重复单。

相关笔记