如何用dplyr::summarise()按组聚合时处理依赖其他列的常量列
问题描述
我想用dplyr::summarise()对数据框做聚合,其中某列是依赖其他列值的常量。以下是模拟数据框的代码:
# 辅助函数可忽略 simulate_df <- function() { person_ids <- sample(1:10, size = 2) product_ids <- sample(1:999, size = 2) product_score <- sample(1:9, size = 2) get_weights <- function() { first_num <- sample(1:9, 1) c(first_num, 10 - first_num) } data.frame( person_id = rep(person_ids, times = get_weights()), product_id = sample(product_ids, 5, replace = TRUE) ) |> transform(product_score = ifelse(product_id == product_ids[1], product_score[1], product_score[2])) } set.seed(12345) my_df <- simulate_df() my_df #> person_id product_id product_score #> 1 3 826 8 #> 2 3 605 2 #> 3 3 826 8 #> 4 3 605 2 #> 5 3 605 2 #> 6 3 826 8 #> 7 8 605 2 #> 8 8 826 8 #> 9 8 605 2 #> 10 8 605 2
在my_df中,每条记录对应一位用户(person_id)的购买记录,每位用户买两种产品之一,每种产品有固定的product_score。
我需要按person_id聚合,得到以下列:
- product_a的购买次数
- product_b的购买次数
- product_a购买次数与product_a的product_score的差值
- product_b购买次数与product_b的product_score的差值
预期输出:
## # A tibble: 2 × 5 ## person_id n_product_a n_product_b diff_nproduct_a_product_score diff_nproduct_b_product_score ## <int> <int> <int> <int> <int> ## 1 3 3 3 -5 1 ## 2 8 1 3 -7 1
目前我已经实现了前两列的统计:
library(dplyr, warn.conflicts = FALSE) my_df |> group_by(person_id) |> summarise(n_product_a = sum(product_id == 826), n_product_b = sum(product_id == 605)) #> # A tibble: 2 × 3 #> person_id n_product_a n_product_b #> <int> <int> <int> #> 1 3 3 3 #> 2 8 1 3
现在的问题是:如何在product_score依赖product_id的情况下,计算出上述差值列?
解决方案
核心思路是先在分组内提取每种产品对应的固定product_score,再用购买次数减去该分数即可。因为每个product_id对应的product_score是常量,所以用first()或unique()就能获取该值。
完整代码如下:
library(dplyr, warn.conflicts = FALSE) my_df |> group_by(person_id) |> summarise( # 统计购买次数 n_product_a = sum(product_id == 826), n_product_b = sum(product_id == 605), # 获取product_a对应的固定分数 score_a = first(product_score[product_id == 826]), # 获取product_b对应的固定分数 score_b = first(product_score[product_id == 605]), # 计算差值 diff_nproduct_a_product_score = n_product_a - score_a, diff_nproduct_b_product_score = n_product_b - score_b ) |> # 移除中间用到的临时分数列 select(-score_a, -score_b)
运行后输出:
#> # A tibble: 2 × 5 #> person_id n_product_a n_product_b diff_nproduct_a_product_score diff_nproduct_b_product_score #> <int> <int> <int> <int> <int> #> 1 3 3 3 -5 1 #> 2 8 1 3 -7 1
补充说明
first()和unique()效果一致,因为同一product_id对应的product_score完全相同,取任意一个匹配值都能得到正确的固定分数。- 如果需要兼容用户未购买某类产品的边界情况,可以给
first()加上default = NA,再用ifelse处理差值计算,不过根据你的模拟数据,暂时不需要额外处理。
内容的提问来源于stack exchange,提问作者Emman
相关产品推荐
相关产品推荐

