【问题标题】:dplyr::summarise with filtering insidedplyr::summarise 内部过滤
【发布时间】:2021-02-26 15:55:19
【问题描述】:

在 dplyr::summarise 内部,如何根据与我正在汇总的行不同的行应用过滤器?

例子:

t = data.frame(
  x = c(1,1,1,1,2,2,2,2,3,3, 3, 3),
  y = c(1,2,3,4,5,6,7,8,9,10,11,12),
  z = c(1,2,1,2,1,2,1,2,1,2, 1, 2)
)

t %>%
  dplyr::group_by(x) %>%
  dplyr::summarise(
    mall = mean(y), # this should include all rows in each group
    ma = mean(y), # this should only include rows where z == 1
    mb = mean(y)  # this should only include rows where z == 2
  )

因此,这里的问题是将汇总函数应用于一列,同时基于另一列进行过滤,所有这些都在 summarise 内。

一个想法是双重分组,因此在 x 和 z 上都应用 group_by,但我不希望 所有 汇总列基于双重分组,有些(如 @987654324上例中的@) 应仅基于单一分组。

【问题讨论】:

  • 两个想法:要么多次调用,然后加入,要么类似ma = mean(ifelse (z==1, y, NA))。第二个选项给你答案 within summarise 但是,恕我直言,非常丑陋而且绝对不整洁。
  • 是的,ifelse 解决方案效果很好。我同意它不会“感觉”整洁,但它现在可以完成工作。如果我更好地理解这些 dplyr 函数及其符号列引用的魔力,我也许可以一起做一些更优雅的事情。

标签: r dplyr


【解决方案1】:

一个快速的选择是使用ifelse 过滤到您需要的行,使其余行缺失,然后使用na.rm = T 参数忽略缺失值,如下例所示。

    dplyr::group_by(x) %>%
    dplyr::summarise(
        mall = mean(y), # this should include all rows in each group
        ma = mean(ifelse(z == 1, y, NA), na.rm = T), # this should only include rows where z == 1
        mb = mean(ifelse(z == 2, y, NA), na.rm = T)  # this should only include rows where z == 2
    )

# A tibble: 3 x 4
      x  mall    ma    mb
  <dbl> <dbl> <dbl> <dbl>
1     1   2.5     2     3
2     2   6.5     6     7
3     3  10.5    10    11

【讨论】:

  • 不错。谢谢。我试图在mean 中使用dplyr::filter,但不知何故不起作用,但可能语法太复杂了。无论如何,ifelse 的这个解决方案有效。谢谢。
【解决方案2】:

虽然@Colin H 的回答肯定是这个特定示例的方法,但更灵活的解决方法可能是在第一个分组操作的子集内工作。这可以通过dplyr::group_split 加上随后的purrr::map_dfr 来实现,但也可以通过dplyr::group_modify 一步完成。

注意dplyr::group_modify的文档中的相关句子:

当 summarise() 太有限时使用 group_modify(),就您需要为每个组执行的操作和返回而言。

所以这是上面提供的示例的解决方案:

t = data.frame(
  x = c(1,1,1,1,2,2,2,2,3,3, 3, 3),
  y = c(1,2,3,4,5,6,7,8,9,10,11,12),
  z = c(1,2,1,2,1,2,1,2,1,2, 1, 2)
)

t %>%
  dplyr::group_by(x) %>%
  dplyr::group_modify(function(x, ...) {
    x %>% dplyr::mutate(
      mall = mean(y)
    ) %>%
      dplyr::group_by(z, mall) %>%
      dplyr::summarise(
        m = mean(y),
        .groups = "drop"
      )
  }) %>%
  dplyr::ungroup()

# A tibble: 6 x 4
      x     z  mall     m
  <dbl> <dbl> <dbl> <dbl>
1     1     1   2.5     2
2     1     2   2.5     3
3     2     1   6.5     6
4     2     2   6.5     7
5     3     1  10.5    10
6     3     2  10.5    11

group_modify 在按x 分组后对每个子集 tibble 应用一个函数。这个函数有两个参数:

组的数据子集,显示为 .x。

键,一个小标题,每个分组只有一行和一列 变量,暴露为 .y。

在我们的函数中,我们首先使用mutate 覆盖请求的mall-case。我们不需要任何进一步的分组,因为包装 group_modify 已经涵盖了这一点。然后我们应用另一个group_by + summarise 来覆盖z 的不同迭代。请注意,此解决方案与我们要考虑的z 中的案例数量无关。虽然此示例中的两种情况可以很容易地手动处理,但如果有更多情况,这可能会改变。

如果z 中的案例需要具有单独列的宽输出格式,那么您可以使用tidyr::pivot_wider 进一步修改我的代码的输出。

【讨论】:

  • 非常有用,谢谢。不知道group_modify
【解决方案3】:

另一种选择,也许更简洁一点是通过子集:

t %>% 
  group_by(x) %>%
  summarise(mall = mean(y), 
            ma = mean(y[z == 1]), 
            mb = mean(y[z == 2]))
# A tibble: 3 x 4
      x  mall    ma    mb
* <dbl> <dbl> <dbl> <dbl>
1     1   2.5     2     3
2     2   6.5     6     7
3     3  10.5    10    11

【讨论】:

    【解决方案4】:

    这是在汇总时对组数据执行自定义过滤的另一种通用方式(就像 group_modify)。这使用了 dplyr 的上下文相关表达式:cur_data(),它使当前组的数据在 dplyr 动词中可用,例如 mutate/summary:

    t %>%
      dplyr::group_by(x) %>%
      dplyr::summarize(
        mall = mean(y),
        ma   = mean(cur_data() %>% as.data.frame() %>% filter(z == 1) %>% pull(y)),
        mb   = mean(cur_data() %>% as.data.frame() %>% filter(z == 2) %>% pull(y))
      )
    

    使用 cur_data() 的好处是您可以在返回最终摘要之前执行任何复杂的过滤或处理。更多信息请参考:https://dplyr.tidyverse.org/reference/context.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-27
      • 2018-04-30
      • 1970-01-01
      • 2021-06-21
      • 2018-07-27
      相关资源
      最近更新 更多