使用 dplyr 识别订阅交易中的用户套餐切换行为
结论
先按 user_id 分组,并按交易日期排序。然后以每个用户的首单套餐为判断基准:
- 首单是
"monthly"时,第一次切换到"3-months"或"6-months"的订单标记为"upgrade"。 - 首单是
"3-months"或"6-months"时,第一次切换到"monthly"的订单标记为"downgrade"。 - 同一用户之后继续使用该套餐时,不再重复标记。
实现时可以先找出所有候选行为,再通过累计计数只保留第一次出现的行为。
dplyr 实现
library(dplyr)
df_output <- df %>%
mutate(
.txn_date = lubridate::mdy(date)
) %>%
arrange(user_id, .txn_date) %>%
group_by(user_id) %>%
mutate(
.initial_plan = first(plan),
.candidate = case_when(
row_number() > 1L &&
.initial_plan == "monthly" &&
plan %in% c("3-months", "6-months") &&
plan != lag(plan) ~ "upgrade",
row_number() > 1L &&
.initial_plan %in% c("3-months", "6-months") &&
plan == "monthly" &&
plan != lag(plan) ~ "downgrade",
TRUE ~ "-"
),
behavior_type = if_else(
.candidate != "-" & cumsum(.candidate != "-") == 1L,
.candidate,
"-"
)
) %>%
ungroup() %>%
select(-.txn_date, -.initial_plan, -.candidate)
生成的 behavior_type 与题目中的预期结果一致:用户 1 和用户 3 的第一笔长期套餐订单会标记为 "upgrade",用户 4 的第一笔月度套餐订单会标记为 "downgrade",其他记录均为 "-"。
判断逻辑
数据按时间排好后,.initial_plan = first(plan) 会取得每个用户的首单套餐。
plan != lag(plan) 用于确认当前套餐与上一笔订单不同。比如用户已经从 "monthly" 切换到 "3-months",下一笔订单仍是 "3-months",这笔订单就不算新的切换。
.candidate 会先标记所有符合业务定义的候选订单。下面的条件则只保留第一个候选行为:
cumsum(.candidate != "-") == 1L
第二次及以后出现的候选行为都会改为 "-"。
如果 date 已经是 Date 类型
如果 date 已经是 Date 或可靠的日期时间类型,可以省略 mdy():
df_output <- df %>%
arrange(user_id, date) %>%
group_by(user_id) %>%
mutate(
.initial_plan = first(plan),
.candidate = case_when(
row_number() > 1L &&
.initial_plan == "monthly" &&
plan %in% c("3-months", "6-months") &&
plan != lag(plan) ~ "upgrade",
row_number() > 1L &&
.initial_plan %in% c("3-months", "6-months") &&
plan == "monthly" &&
plan != lag(plan) ~ "downgrade",
TRUE ~ "-"
),
behavior_type = if_else(
.candidate != "-" & cumsum(.candidate != "-") == 1L,
.candidate,
"-"
)
) %>%
ungroup() %>%
select(-.initial_plan, -.candidate)
注意事项
示例日期使用月/日/年格式,因此代码调用了 lubridate::mdy()。如果实际数据采用日/月/年格式,需要改用 lubridate::dmy()。
这段代码假设每个用户按时间排序后的第一笔交易就是 "acquisition"。如果数据可能缺少首单,或者同一用户有多条 "acquisition",建议先检查:
df %>%
count(user_id, order_type) %>%
filter(order_type == "acquisition", n != 1L)
如果同一天可能有多笔交易,只按日期无法判断真实顺序。此时应在 arrange() 中加入订单时间或订单编号,例如:
arrange(user_id, transaction_time, order_id)
这里的升级和降级是相对于首单套餐定义的,并非对所有相邻套餐变化进行分类。例如,从 "3-months" 切换到 "6-months" 不会标记为升级,因为题目只把首单为 "monthly" 后切换到长期套餐定义为升级。
备注:内容仅供参考。
