一句命名聚合出三个值:日期取 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 秒实验
- 把观察日改成 2025-01-01,看 R 的中位数变成多少。
- 把集中度改成看前 5%、前 50%,看曲线在哪一段最陡。
- 想一想:如果按 F 排序算集中度,结论会和按 M 排一样吗?
常见误区
(观察日 - 日期)忘了.dt.days:拿到的是 timedelta,后面聚类会直接报错。- 观察日写今天:全表 R 天天变,结论不可复现。
- 只看 M 的均值:均值 4042.71 远高于中位数 1639.78,右偏严重,均值不典型。
- 中间结果不落盘:聚类那步一出错,前面全得重跑。
自测三问(点开对答案)
1. 三个值分别用什么聚合函数? —— 日期用 `max`(最近一次),订单编号用 `count`(次数),金额用 `sum`(总额)。 2. R 为什么要 `.dt.days`? —— 两个日期相减得到 timedelta,取 `.dt.days` 才是整数天数,否则不能直接进模型。 3. 前 20% 客户贡献 67.91%,说明什么? —— 客户价值高度集中。少数人撑起大头,所以他们一旦流失,损失最大。三个值有了。下一篇做最关键的一步:标准化,然后聚类分堆。
- ☐ (本篇完,下一篇 12.5 数据转换与客户聚类)