如何以编程方式构建dplyr summarize语句?

7
我正在尝试进行一些dplyr编程,但遇到了麻烦。我想要根据任意数量的变量(因此使用across)进行group_by操作,然后基于相同长度且任意长度(但相同长度)的向量进行summarize操作:

  • 要应用函数的列
  • 要应用的函数
  • 新列的名称

所以,就像在mapapply语句中一样,我希望执行的代码看起来像:

data %>%
  group_by(group_column) %>%
  summarize(new_name_1 = function_1(column_1),
  summarize(new_name_2 = function_2(column_2))

这里是我想要的示例以及迄今为止的最佳尝试。我知道如果使用across,我可以使用names参数来清理它们,但我不确定across是否是正确的方法。最后,我将应用此方法于相当大的数据框,因此我不想计算额外的列。

期望结果

mtcars %>%
  group_by(across(c("cyl", "carb"))) %>%
  summarise(across(c("disp", "hp"), list(mean = mean, sd = sd))) %>%
  select(cyl, carb, disp_mean, hp_sd)
#> `summarise()` regrouping output by 'cyl' (override with `.groups` argument)
#> # A tibble: 9 x 4
#> # Groups:   cyl [3]
#>     cyl  carb disp_mean hp_sd
#>   <dbl> <dbl>     <dbl> <dbl>
#> 1     4     1      91.4 16.1 
#> 2     4     2     117.  24.9 
#> 3     6     1     242.   3.54
#> 4     6     4     164.   7.51
#> 5     6     6     145   NA   
#> 6     8     2     346.  14.4 
#> 7     8     3     276.   0   
#> 8     8     4     406.  21.7 
#> 9     8     8     301   NA

我可以帮你做什么

mtcars %>%
  group_by(across(c("cyl", "carb"))) %>%
  summarise(across(c("disp", "hp"), list(mean = mean, sd = sd)))
#> `summarise()` regrouping output by 'cyl' (override with `.groups` argument)
#> # A tibble: 9 x 6
#> # Groups:   cyl [3]
#>     cyl  carb disp_mean disp_sd hp_mean hp_sd
#>   <dbl> <dbl>     <dbl>   <dbl>   <dbl> <dbl>
#> 1     4     1      91.4   21.4     77.4 16.1 
#> 2     4     2     117.    27.1     87   24.9 
#> 3     6     1     242.    23.3    108.   3.54
#> 4     6     4     164.     4.39   116.   7.51
#> 5     6     6     145     NA      175   NA   
#> 6     8     2     346.    43.4    162.  14.4 
#> 7     8     3     276.     0      180    0   
#> 8     8     4     406.    57.8    234   21.7 
#> 9     8     8     301     NA      335   NA
2个回答

6

在不同的列上使用不同的函数时,一个选项是使用collapse中的collap

library(collapse)
collap(mtcars, ~ cyl + carb, custom = list(fmean = 4, fsd = 5))

-输出

cyl   disp        hp carb
1   4  91.38 16.133815    1
2   4 116.60 24.859606    2
3   6 241.50  3.535534    1
4   6 163.80  7.505553    4
5   6 145.00        NA    6
6   8 345.50 14.433757    2
7   8 275.80  0.000000    3
8   8 405.50 21.725561    4
9   8 301.00        NA    8

或者索引可以使用match进行动态生成。

collap(mtcars, ~ cyl + carb, custom = list(fmean =
   match('disp', names(mtcars)), fsd = match('hp', names(mtcars))))

使用 tidyverse,一种选择是在感兴趣的列名称和 map2 中循环,并稍后执行连接操作。

library(dplyr)
library(purrr)
library(stringr)
map2(c("disp", "hp"), c("mean", "sd"), ~
   mtcars %>%
      group_by(across(c('cyl', 'carb'))) %>% 
      summarise(across(all_of(.x), match.fun(.y), 
         .names = str_c("{.col}_", .y)), .groups = 'drop')) %>% 
    reduce(inner_join)

-输出

# A tibble: 9 x 4
    cyl  carb disp_mean hp_sd
  <dbl> <dbl>     <dbl> <dbl>
1     4     1      91.4 16.1 
2     4     2     117.  24.9 
3     6     1     242.   3.54
4     6     4     164.   7.51
5     6     6     145   NA   
6     8     2     346.  14.4 
7     8     3     276.   0   
8     8     4     406.  21.7 
9     8     8     301   NA   

1
tidyverse解决方案非常好。我想不出来。 :-) - Martin Gal
1
谢谢,这是一个很棒的解决方案! - spillway18

1
我在github上有一个包{dplyover}
它可以帮助处理这种任务。在这种情况下,我们可以使用over2同时循环两个字符向量。第一个向量包含变量名作为字符串,因此在对其应用函数时,我们必须将.x包装在sym()中。第二个向量包含函数名称,我们将其用作.ydo.call中。over2会自动创建所需的名称。
library(dplyr)
library(dplyover) # https://github.com/TimTeaFan/dplyover

mtcars %>%
  group_by(across(c("cyl", "carb"))) %>%
  summarise(over2(c("disp", "hp"),
                  c("mean", "sd"),
                  ~ do.call(.y, list(sym(.x)))
                  ))

#> `summarise()` has grouped output by 'cyl'. You can override using the `.groups` argument.
#> # A tibble: 9 x 4
#> # Groups:   cyl [3]
#>     cyl  carb disp_mean hp_sd
#>   <dbl> <dbl>     <dbl> <dbl>
#> 1     4     1      91.4 16.1 
#> 2     4     2     117.  24.9 
#> 3     6     1     242.   3.54
#> 4     6     4     164.   7.51
#> 5     6     6     145   NA   
#> 6     8     2     346.  14.4 
#> 7     8     3     276.   0   
#> 8     8     4     406.  21.7 
#> 9     8     8     301   NA

在相同逻辑的基础上,另一种方法是使用 purrr::map2。然而,在这种情况下,我们需要花费一些精力来创建具有所需名称的向量。
library(purrr)

# setup vectors and names
myfuns <- c("mean", "sd")
myvars <- c("disp", "hp") %>%
  set_names(., paste(., myfuns, sep = "_"))

mtcars %>%
  group_by(across(c("cyl", "carb"))) %>%
  summarise(map2(myvars,
                 myfuns,
                 ~ do.call(.y, list(sym(.x)))
                 ) %>% bind_cols()
  )

#> `summarise()` has grouped output by 'cyl'. You can override using the `.groups` argument.
#> # A tibble: 9 x 4
#> # Groups:   cyl [3]
#>     cyl  carb disp_mean hp_sd
#>   <dbl> <dbl>     <dbl> <dbl>
#> 1     4     1      91.4 16.1 
#> 2     4     2     117.  24.9 
#> 3     6     1     242.   3.54
#> 4     6     4     164.   7.51
#> 5     6     6     145   NA   
#> 6     8     2     346.  14.4 
#> 7     8     3     276.   0   
#> 8     8     4     406.  21.7 
#> 9     8     8     301   NA

此内容由reprex package(v2.0.1)于2021-08-20创建。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接