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

一句命名聚合出三个值:日期取 max、订单数 count、金额 sum,每个客户一行。 R 是观察日减最近购买日,取 .dt.days 才是整数天。 金额最高的 10% 客户贡献 53.45% 的钱——帕累托法则落到客户身上。

📝 本章和前面几章的关系

第 12.2 节讲了三个值是什么,这一篇把它算出来,并看清它们的分布。 这一步是整章的分水岭:前面是流水,后面才有「客户」这个单位。

一、一句 agg 出三个值

groupby("客户编号").agg(...) 里给三个不同的聚合:日期取 max、订单数用 count、金额用 sum。 同一句 groupby,三列三种算法,这是命名聚合最典型的用法。 结果每个客户一行,共 1200 行。从这一行起,「客户」才是一个能算的对象。

命名聚合的写法是 新列名=(原列名, 函数名),比先 groupby 再一个个 rename 清爽得多。

二、R 是算出来的天数

(观察日 - 最近购买日) 得到一个 timedelta,.dt.days 取出整数天数。 C0001 最后一单 2025-03-17,R 是 105 天;C0004 最后一单 2024-11-12,R 是 230 天。 观察日一变,R 全变。所以这个日子必须写死,不能写 pd.Timestamp.today()。

写死之后,同一份数据跑一百遍结果一样,别人才敢拿它做决策。 顺带一句:R 的单位是「天」,不是「月」也不是「次」,报告里要写清。

三、三个值的分布差得离谱

指标 最小 中位数 最大 均值
R(天) 2 208.50 886 250.05
F(次) 1 6 40 7.75
M(元) 199.83 1639.78 37980.75 4042.71

三个值的量级差得离谱:R 是几百,F 是个位数,M 是几万。 这一眼就该看出问题:直接拿去聚类,距离会被 M 一个人占满。 R 最小 2 天、最大 886 天,跨度两年多;F 从 1 到 40,最长的一个客户买了 40 次。

M 的最大值 37980.75 元,是均值 4042.71 的九倍多——说明少数客户贡献了大头。

四、集中度:少数人贡献大头

把客户按 M 从大到小排,看前几成占了多少金额。 前 10%(120 人)贡献 53.45%;前 20%(240 人)贡献 67.91%;前 30%(360 人)贡献 77.12%。 这就是第 7 章的帕累托法则,落到客户身上。两成人拿走将近七成的钱。

但注意:这里按金额排,还没考虑「最近来不来」。一个去年买了很多、今年消失的客户,照样排前面。 这正是 RFM 要补的短板——M 说的是「曾经多重要」,R 说的是「现在还重不重要」。

五、落成 rfm.csv

存成 rfm.csv,1200 行 3 列,索引是客户编号。 中间结果要落盘。聚类那一步万一跑错,不用从头再算一遍。 这也是长流程项目的规矩:每算完一段就存一份,越到后面越值钱。

下一篇从这份文件读,做标准化和聚类。

六、动手 Demo:计算 RFM 值

洗完数据,一句 agg 出 R、F、M,再看分布和集中度,最后存成 rfm.csv。 重点看第三节那张表:三个值的量级差多少。

import pandas as pd

df = pd.read_csv("订单流水.csv")
df = df.drop_duplicates().dropna(subset=["客户编号"])
df = df[df["订单金额"] > 0]
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(rfm[ ["R", "F", "M"]].describe().round(2).to_string())

s = rfm["M"].sort_values(ascending=False)
for k in (0.1, 0.2, 0.3):
    n = int(len(s) * k)
    print("前 %.0f%% 的人贡献 %.2f%%" % (k * 100, s.iloc[:n].sum() / s.sum() * 100))

rfm[ ["R", "F", "M"]].to_csv("rfm.csv", encoding="utf-8-sig")

实跑输出:

--------------------------------------------------------------------
计算 RFM 值
--------------------------------------------------------------------

  ① 一句 agg 出三个值
  --------------------------------------------------------------------
  agg 里给三个不同的聚合:日期取 max、订单数用 count、金额用 sum。
  同一句 groupby,三列三种算法,这是命名聚合的典型用法。
  结果每个客户一行,共 1200 行。

  ② R 是算出来的天数
  --------------------------------------------------------------------
  观察日 2025-06-30 减最近购买日,得到天数。

| 客户 | 最近购买 | R(天) | F | M(元) |
|---|---|---|---|---|
| C0001 | 2025-03-17 | 105 | 6 | 3305.67 |
| C0002 | 2024-07-19 | 346 | 7 | 1899.80 |
| C0003 | 2024-09-27 | 276 | 9 | 3102.78 |
| C0004 | 2024-11-12 | 230 | 5 | 1397.93 |
| C0005 | 2024-12-13 | 199 | 5 | 1536.97 |

  观察日一变,R 全变。所以这个日子必须写死,不能写 pd.Timestamp.today()。
  写死之后,同一份数据跑一百遍结果一样,别人才敢拿它做决策。

  ③ 三个值的分布
  --------------------------------------------------------------------

| 指标 | 最小 | 中位数 | 最大 | 均值 |
|---|---:|---:|---:|---:|
| R | 2.00 | 208.50 | 886.00 | 250.05 |
| F | 1.00 | 6.00 | 40.00 | 7.75 |
| M | 199.83 | 1639.78 | 37980.75 | 4042.71 |

  三个值的量级差得离谱:R 是几百,F 是个位数,M 是几万。
  这一眼就该看出问题:直接拿去聚类,距离会被 M 一个人占满。
  还有一点:M 的最大值远高于均值,说明少数客户贡献了大头。

  ④ 少数人贡献大头
  --------------------------------------------------------------------
  金额最高的 10% 客户(120 人),贡献了 53.45% 的金额。
  金额最高的 20% 客户(240 人),贡献了 67.91% 的金额。
  金额最高的 30% 客户(360 人),贡献了 77.12% 的金额。
  这就是第 7 章的帕累托法则,落到客户身上。
  但注意:这里按金额排,还没考虑「最近来不来」。一个去年买了很多、今年消失的客户,照样排在前面。
  这正是 RFM 要补的短板。

  ⑤ 落成 rfm.csv
  --------------------------------------------------------------------
  存成 rfm.csv,1200 行 3 列,索引是客户编号。
  中间结果要落盘。聚类那一步万一跑错,不用从头再算一遍。
  ! 每步留一份中间结果,是长流程项目的保命习惯。越到后面越值钱。

describe() 一次给出计数、均值、标准差、四分位和最大最小,比逐个 .mean() 快。 s.iloc[:n] 是「前 n 个」,s[:n] 在索引不是数字时会按标签切,容易错。 存 rfm.csv 时把索引也写进去(默认 index=True),客户编号才不会丢。

30 秒实验

  1. 把观察日改成 2025-01-01,看 R 的中位数变成多少。
  2. 把集中度改成看前 5%、前 50%,看曲线在哪一段最陡。
  3. 想一想:如果按 F 排序算集中度,结论会和按 M 排一样吗?

常见误区

  1. (观察日 - 日期) 忘了 .dt.days:拿到的是 timedelta,后面聚类会直接报错。
  2. 观察日写今天:全表 R 天天变,结论不可复现。
  3. 只看 M 的均值:均值 4042.71 远高于中位数 1639.78,右偏严重,均值不典型。
  4. 中间结果不落盘:聚类那步一出错,前面全得重跑。
自测三问(点开对答案) 1. 三个值分别用什么聚合函数? —— 日期用 `max`(最近一次),订单编号用 `count`(次数),金额用 `sum`(总额)。 2. R 为什么要 `.dt.days`? —— 两个日期相减得到 timedelta,取 `.dt.days` 才是整数天数,否则不能直接进模型。 3. 前 20% 客户贡献 67.91%,说明什么? —— 客户价值高度集中。少数人撑起大头,所以他们一旦流失,损失最大。

三个值有了。下一篇做最关键的一步:标准化,然后聚类分堆。

相关笔记