【问题标题】:R: why group_by still requires "do" even when using quosuresR:为什么 group_by 仍然需要“做”,即使使用 quosures
【发布时间】:2019-03-25 11:25:55
【问题描述】:

如何使用户定义的函数与管道和 group_by 很好地配合使用?这是一个简单的函数:

 library(tidyverse)

 fun_head <- function(df, column) {
 column <- enquo(column)
 df %>% select(!!column) %>% head(1)
 }

该功能与管道很好地配合使用,并允许按另一列过滤:

 mtcars %>% filter(cyl == 4) %>% fun_head(mpg)

 >    mpg
   1 22.8

但是,相同的管道工作因 group_by 而失败

mtcars %>% group_by(cyl) %>% fun_head(mpg)

Adding missing grouping variables: `cyl`
# A tibble: 1 x 2
# Groups:   cyl [1]
     cyl   mpg
     <dbl> <dbl>
1     6    21

在 group_by 之后使用“do”使其工作:

 > mtcars %>% group_by(cyl) %>% do(fun_head(., mpg))
 # A tibble: 3 x 2
 # Groups:   cyl [3]
    cyl   mpg
   <dbl> <dbl>
1     4  22.8
2     6  21  
3     8  18.7

应该如何更改函数,以便它与 filter 和 group_by 一致地工作而不需要“do”?
或者quosures与问题​​无关,group_by只需要使用“do”,因为示例中的函数有多个参数?

【问题讨论】:

  • 请注意,mtcars %&gt;% group_by(cyl) %&gt;% select(mpg) %&gt;% head(1) 也只提供第一行。

标签: r dplyr tidyverse quosure


【解决方案1】:

这与quosures 无关。在fun_head() 中没有非标准评估的情况下也是同样的问题:

fun_head <- function(df) {df %>% select(mpg) %>% head(1)}
mtcars %>% group_by( cyl ) %>% fun_head()
# Adding missing grouping variables: `cyl`
# # A tibble: 1 x 2
# # Groups:   cyl [1]
#     cyl   mpg
#   <dbl> <dbl>
# 1     6    21

正如其他问题herehere 中所述,do 是允许您将任意函数应用于每个组的连接器。 dplyr 动词如 mutatefilter 不需要 do 的原因是因为它们在内部将分组数据帧作为特殊情况处理(例如,参见 the implementation of mutate)。如果您希望自己的函数模拟这种行为,则需要区分分组数据帧和未分组数据帧:

fun_head2 <- function( df )
{
  if( !is.null(groups(df)) )
    df %>% do( fun_head2(.) )
  else
    df %>% select(mpg) %>% head(1)
}

mtcars %>% group_by(cyl) %>% fun_head2()
# # A tibble: 3 x 2
# # Groups:   cyl [3]
#     cyl   mpg
#   <dbl> <dbl>
# 1     4  22.8
# 2     6  21  
# 3     8  18.7

编辑:我想指出group_by + do 的另一种替代方法是使用tidyr::nest + purrr::map。回到你原来的带有两个参数的函数定义:

fhead <- function(.df, .var) { .df %>% select(!!ensym(.var)) %>% head(1) }

以下两个链是等价的(直到行的排序,因为group_by 按分组变量排序,而nest 没有):

# Option 1: group_by + do
mtcars %>% group_by(cyl) %>% do( fhead(., mpg) ) %>% ungroup

# Option 2: nest + map
mtcars %>% nest(-cyl) %>% mutate_at( "data", map, fhead, "mpg" ) %>% unnest

【讨论】:

    【解决方案2】:

    正如你写的那样,函数从df中选择column,然后取head,这是df的第一行(head不是一个tidyverse函数,也不是知道任何分组)。 dplyr::slice(1) 占据每组的第一行,这就是你想要的。你可以使用

     fun_head <- function(df, column) {
     column <- enquo(column)
     df %>% slice(1) %>% select(!!column)
     }
    
     mtcars %>% group_by(cyl) %>% fun_head(mpg)
    
    # # A tibble: 3 x 2
    # # Groups:   cyl [3]
    #     cyl   mpg
    #   <dbl> <dbl>
    # 1     4  22.8
    # 2     6  21  
    # 3     8  18.7
    

    【讨论】:

    • 这种方法是因为“头”还是更普遍的原因而在这里起作用? Artem Sokolov 给出的答案表明 group_by 需要“do”
    • 还有一些其他的函数,比如head,它们有tidyverse等价物,比如slice。但有些不是,所以有时你需要使用do。正如另一个答案中提到的“do 是允许您将任意函数应用于每个组的连接器”,强调我的。
    猜你喜欢
    • 2015-08-15
    • 2014-06-19
    • 2014-07-31
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 2013-06-02
    • 1970-01-01
    相关资源
    最近更新 更多