复盘导读:在数据分析的日常中,RFM 模型几乎是用户分层的“万金油”。网上绝大多数教程都会教你直接用 NTILE(5) 窗口函数进行等频分桶。但在面对真实的亿级业务数据时,生搬硬套模板往往会带来灾难性的“业务误杀”。本文将结合淘宝亿级用户行为数据,复盘我为何放弃等频分桶,转而基于长尾分布特征进行阈值重构。

一、常规分桶法 (NTILE) 的优势与盲区

在没有强业务标准前,做 RFM 模型先用 NTILE(5) 分桶拿到客观画像,是大多数分析师的默认选择。

分桶法的优点显而易见:

  1. 客观性:让数据自己说话,避免拍脑袋自定义阈值(比如主观判定“R ≤ 30 天算高”在不同业务期可能并不准确)。
  2. 自适应:能跟着业务节奏自动调整,客户行为会随时间整体偏移。
  3. 分层均匀:每一层都有足够的分析样本,避免出现某一档人数极少导致统计不稳定。
  4. 可复用性强:只需一行 SQL 即可跨品类、跨行业通用。

常规分桶的 SQL 实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
WITH new_biao AS (
SELECT
user_id,
dateDiff('day', max(date_id), toDate('2017-12-04')) AS day_diff,
count(*) AS f_count
FROM wangchao_data.user_behavior_clean
WHERE behavior_type= 'buy'
GROUP BY user_id
),
new_biao2 AS (
SELECT
user_id,
day_diff,
f_count,
6 - ntile(5) over(ORDER BY day_diff ASC) AS r_score,
6 - ntile(5) over(ORDER BY f_count DESC) AS f_score
FROM new_biao
ORDER BY user_id
)
SELECT
CASE
WHEN (r_score + f_score) >= 8 THEN '1.核心高价值SVIP(8-10分)'
WHEN (r_score + f_score) >= 6 THEN '2.潜力活跃中产(6-7分)'
WHEN (r_score + f_score) >= 4 THEN '3.濒危沉睡用户(4-5分)'
ELSE '4.边缘流失客(2-3分)'
END AS `用户分层`,
count(*) AS total
FROM new_biao2
GROUP BY `用户分层`
ORDER BY `用户分层` ASC;

SIXTEEN

二、为什么在淘宝亿级数据中必须“弃用”分桶?

在本次淘宝数据集的特定场景下,尤其是对于 Frequency(频次) 指标,绝对不能使用 NTILE() 进行等频分桶,必须退回到基于数据分布特征的“自定义阈值”。

致命痛点:被稀释的高价值用户:
假设有 100 个用户,90 个人每人支付了 1 次,10 个人每人支付了 10 次。毫无疑问,那 10 个人才是真正的高价值用户。
但如果强制使用 NTILE(5) 分桶(前 20% 为 5 分),系统就必须在剩余的 90 人中强行抽出 10 名“只支付过 1 次”的用户,与那 10 名“支付了 10 次”的核心用户混在一起,组成 20 人的“高价值群体”。这显然违背了业务逻辑,导致高价值标签被严重稀释!

1. F 值(频次)的长尾偏态分布
自定义阈值的核心优势在于:能保留数据的天然分布形态,让标签有绝对的业务含义。
在本次淘宝数据中,F 值是典型的“长尾偏态分布”:87.83% 的用户购买次数 ≤5 次,但最大值却高达 262 次。头部与尾部的行为差距极大。如果用分桶一刀切,会引发严重的误杀。而通过自定义阈值(如 F≥6 才给满分),能一瞬间把真正跨过高门槛的极少数头部用户精准拎出来,保证高价值组的纯粹性

F 值分布验证 SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
WITH new_biao1 AS (     
SELECT user_id, count(*) AS f_count
FROM wangchao_data.user_behavior_clean
WHERE behavior_type = 'buy'
GROUP BY user_id
),
new_biao2 AS (
SELECT f_count, count(*) AS user_cnt
FROM new_biao1
GROUP BY f_count
),
new_biao3 AS (
SELECT
f_count,
user_cnt,
sum(user_cnt) over(ORDER BY f_count ASC) AS cum_cnt,
sum(user_cnt) over () AS total_users
FROM new_biao2
)
SELECT
f_count,
user_cnt,
cum_cnt,
total_users,
round(cum_cnt/total_users, 4) AS cum_percent
FROM new_biao3
ORDER BY f_count ASC;

SEVENTEEN
2. R 值(最近购买时间)的自然梯度
即使值域较小,只要分布有自然梯度,也应首选自定义阈值。
本数据集中 R 的值域只有 1-10 天,但实际分布并不极端拥挤(1天占24%,2天占20%,3-4天合计24%,5-7天23%,8-10天10%)。分位点差值明显,存在清晰的时间断点。
此时直接用天数定义层级(如 1天→5分,3-4天→3分)切出来的每层人数相对均衡,且标签是“1天内活跃”这种业务人员能一眼看懂的概念,落地性极强。

R 值分布验证 SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
WITH new_biao1 AS (
SELECT user_id, dateDiff('day', max(date_id), toDate('2017-12-04')) AS day_diff
FROM wangchao_data.user_behavior_clean
WHERE behavior_type ='buy'
GROUP BY user_id
),
new_biao2 AS (
SELECT day_diff, count(*) AS user_cnt
FROM new_biao1
GROUP BY day_diff
),
new_biao3 AS (
SELECT
day_diff,
user_cnt,
sum(user_cnt) over (ORDER BY day_diff ASC) AS cum_cnt,
sum(user_cnt) over () AS total_users
FROM new_biao2
)
SELECT
day_diff,
user_cnt,
cum_cnt,
total_users,
round(cum_cnt/total_users, 4) AS cum_percent
FROM new_biao3
ORDER BY day_diff ASC;

EIGHTEEN

三、总结与反思

什么情况下才会转而考虑分桶?——多半是被数据的“极端形态”逼出来的(例如值域跨度极小且高度拥挤,或横跨一整年但极度偏斜)。
在本次项目中,我先对 R 和 F 跑了分位数和累计占比分布,在确认分位点之间的差值足够大、分布并非高度重叠后,才最终决定使用自定义阈值。 这样既让打分有客观的数据锚点,又保留了业务标签的可解释性与稳定性,是目前最适合这套百万级淘宝行为数据的处理方式。不要做代码的搬运工,要让数据真正服务于商业逻辑。