【问题标题】:Use quoted variable in group_by() %>% mutate() function call在 group_by() %>% mutate() 函数调用中使用带引号的变量
【发布时间】:2019-03-29 21:59:24
【问题描述】:

可重现的例子

cats <-
  data.frame(
    name = c(letters[1:10]),
    weight = c(rnorm(5, 10, 1), rnorm(5, 20, 3)),
    type = c(rep("not_fat", 5), rep("fat", 5))
  )

get_means <- function(df, metric, group) {
  df %>%
    group_by(.[[group]]) %>%
    mutate(mean_stat = mean(.[[metric]])) %>%
    pull(mean_stat) %>%
    unique()
}

get_means(cats, metric = "weight", group = "type")

我尝试了什么

我希望得到两个值,而不是得到一个值。看来 groupby 失败了。

我尝试了所有方法,包括使用 quo()、eval() 和替代 ()、UQ()、!! 以及其他一大堆东西来尝试使 group_by() 中的东西正常工作。

这看起来非常简单,但我想不通。

代码推理

将变量放在引号中的决定是因为我在 ggplot aes_string() 调用中使用它们。我在函数中排除了 ggplot 代码以简化代码,否则会很容易,因为我们可以使用标准评估。

【问题讨论】:

    标签: r dplyr tidyeval


    【解决方案1】:

    我认为在 tidyeval 框架中执行此操作的“预期”方法是将参数作为名称(而不是字符串)输入,然后使用 enquo() 引用参数。 ggplot2 了解整洁的评估运算符,因此这也适用于 ggplot2

    首先,让我们在您的示例中调整 dplyr 汇总函数:

    library(tidyverse)
    library(rlang)
    
    get_means <- function(df, metric, group) {
    
      metric = enquo(metric)
      group = enquo(group)
    
      df %>%
        group_by(!!group) %>%
        summarise(!!paste0("mean_", as_label(metric)) := mean(!!metric))
    }
    
    get_means(cats, weight, type)
    
      type    mean_weight
    1 fat            20.0
    2 not_fat        10.2
    
    get_means(iris, Petal.Width, Species)
    
      Species    mean_Petal.Width
    1 setosa                0.246
    2 versicolor            1.33 
    3 virginica             2.03
    

    现在添加 ggplot:

    get_means <- function(df, metric, group) {
    
      metric = enquo(metric)
      group = enquo(group)
    
      df %>%
        group_by(!!group) %>%
        summarise(mean_stat = mean(!!metric)) %>% 
        ggplot(aes(!!group, mean_stat)) + 
          geom_point()
    }
    
    get_means(cats, weight, type)
    

    我不确定您想到的是哪种类型的绘图,但您可以使用 tidy 评估来绘制数据和汇总值。例如:

    plot_func = function(data, metric, group) {
    
      metric = enquo(metric)
      group = enquo(group)
    
      data %>% 
        ggplot(aes(!!group, !!metric)) + 
          geom_point() +
          geom_point(data=. %>% 
                       group_by(!!group) %>%
                       summarise(!!metric := mean(!!metric)),
                     shape="_", colour="red", size=8) + 
          expand_limits(y=0) +
          scale_y_continuous(expand=expand_scale(mult=c(0,0.02)))
    }
    
    plot_func(cats, weight, type)
    

    仅供参考,您可以允许函数使用... 参数和enquos 而不是enquo(这也需要使用!!!(取消引用拼接) 而不是 !! (取消引用))。

    get_means <- function(df, metric, ...) {
    
      metric = enquo(metric)
      groups = enquos(...)
    
      df %>%
        group_by(!!!groups) %>%
        summarise(!!paste0("mean_", quo_text(metric)) := mean(!!metric))
    }
    
    get_means(mtcars, mpg, cyl, vs)
    
        cyl    vs mean_mpg
    1     4     0     26  
    2     4     1     26.7
    3     6     0     20.6
    4     6     1     19.1
    5     8     0     15.1
    
    get_means(mtcars, mpg)
    
      mean_mpg
    1     20.1
    

    【讨论】:

    • 不错的答案!请注意,quo_text() 在这种情况下是不合适的。这是一个多行解析器。您可以改用as_label()as_name(),它们保证返回单行字符串。后者检查它的输入是一个变量名而不是一个函数调用,这在许多情况下是合适的。这里as_label() 很好,因为您的函数接受变量的内联转换,例如你可以通过get_means(mtcars, mpg * 100)
    • 谢谢@lionel。追问:像quo_text()as_label() 是一个rlang 函数。我(可能不正确)的印象是“普通” tidyeval 用户在正常的编程过程中不需要求助于rlang 函数。有没有办法只使用标准 tidyverse 包中的函数来生成动态复合列名?
    • 你是对的。我们也将在 tidyverse 包中导出as_label()
    【解决方案2】:

    如果您想使用字符串作为名称,如您的示例所示,正确的方法是使用sym 将字符串转换为符号并使用!! 取消引用:

    get_means <- function(df, metric, group) {
        df %>%
          group_by(!!sym(group)) %>%
          mutate(mean_stat = mean(!!sym(metric))) %>%
          pull(mean_stat) %>%
          unique()
    }
    
    get_means(cats, metric = "weight", group = "type")
    [1] 10.06063 17.45906
    

    如果您想在函数中使用裸名,请使用 enquo!!

    get_means <- function(df, metric, group) {
        group <- enquo(group)
        metric <- enquo(metric)
        df %>%
          group_by(!!group) %>%
          mutate(mean_stat = mean(!!metric)) %>%
          pull(mean_stat) %>%
          unique()
    }
    
    get_means(cats, metric = weight, group = type)
    [1] 10.06063 17.45906
    

    您的示例中发生了什么?

    有趣的是.[[group]],确实适用于分组,但不是你想的那样。这将数据框的指定列子集为向量,然后将其作为分组的新变量:

    cats %>%
        group_by(.[['type']])
    
    # A tibble: 10 x 4
    # Groups:   .[["type"]] [2]
       name  weight type    `.[["type"]]`
       <fct>  <dbl> <fct>   <fct>        
     1 a       9.60 not_fat not_fat      
     2 b       8.71 not_fat not_fat      
     3 c      12.0  not_fat not_fat      
     4 d       8.48 not_fat not_fat      
     5 e      11.5  not_fat not_fat      
     6 f      17.0  fat     fat          
     7 g      20.3  fat     fat          
     8 h      17.3  fat     fat          
     9 i      15.3  fat     fat          
    10 j      17.4  fat     fat  
    

    您的问题来自mutate 语句。 mutate(mean_stat = mean(.[['weight']])) 没有选择 ,而是简单地将 weight 列提取为向量,计算平均值,然后将该单个值分配给新列

    cats %>%
        group_by(.[['type']]) %>%
          mutate(mean_stat = mean(.[['weight']]))
    # A tibble: 10 x 5
    # Groups:   .[["type"]] [2]
       name  weight type    `.[["type"]]` mean_stat
       <fct>  <dbl> <fct>   <fct>             <dbl>
     1 a       9.60 not_fat not_fat            13.8
     2 b       8.71 not_fat not_fat            13.8
     3 c      12.0  not_fat not_fat            13.8
     4 d       8.48 not_fat not_fat            13.8
     5 e      11.5  not_fat not_fat            13.8
     6 f      17.0  fat     fat                13.8
     7 g      20.3  fat     fat                13.8
     8 h      17.3  fat     fat                13.8
     9 i      15.3  fat     fat                13.8
    10 j      17.4  fat     fat                13.8
    

    【讨论】:

    • 何时需要或不需要sym?例如,您可以使用group_by(!!group),它似乎可以工作。
    • sym 将字符串转换为可以使用的符号,enquo 将传递给函数的裸名称转换为可以使用的东西。所以如果你将"type"传入函数,你需要sym,但是如果你传入type,则使用enquo
    • 我认为这首先得到了回答。所以,在这里发表评论。最好还显示一个可以同时接受带引号和不带引号的参数的函数
    【解决方案3】:

    magrittr 代词 . 代表整个数据,因此您已取所有观测值的平均值。相反,请使用 tidy eval 代词 .data,它表示当前组的数据帧切片:

    get_means <- function(df, metric, group) {
      df %>%
        group_by(.data[[group]]) %>%
        mutate(mean_stat = mean(.data[[metric]])) %>%
        pull(mean_stat) %>%
        unique()
    }
    

    【讨论】:

      【解决方案4】:

      我会稍作修改(如果我理解正确的话):

       get_means <- function(df, metric, group) {
            df %>%
              group_by(!!sym(group)) %>%
              summarise(mean_stat = mean(!!sym(metric)))%>% pull(mean_stat)
          }
          get_means(cats, "weight", "type")
      
      [1] 20.671772  9.305811
      

      给出与 :

      完全相同的输出
      cats %>% group_by(type) %>% summarise(mean_stat=mean(weight)) %>%
        pull(mean_stat)
      
      [1] 20.671772  9.305811
      

      【讨论】:

      • 虽然在大多数情况下是等效的,但子集.data 比取消引用符号更安全一些。那是因为.data[[col]] 检查变量是否存在于数据框中。对于不习惯整洁 eval 的人来说,它看起来也更简单。
      【解决方案5】:

      使用*_at 函数:

      library(dplyr)
      get_means <- function(df, metric, group) {
        df %>%
          group_by_at(group) %>%
          mutate_at(metric,list(mean_stat = mean)) %>%
          pull(mean_stat) %>%
          unique()
      }
      
      get_means(cats, metric = "weight", group = "type")
      # [1] 10.12927 20.40541
      

      数据

      set.seed(1)
      cats <-
        data.frame(
          name = c(letters[1:10]),
          weight = c(rnorm(5, 10, 1), rnorm(5, 20, 3)),
          type = c(rep("not_fat", 5), rep("fat", 5))
        )
      

      【讨论】:

        【解决方案6】:

        使用across().data{} 更新答案以重命名,并将原始函数参数保留为每个 OP 的字符串:

        library(tidyverse)
        
        get_means <- function(dat = mtcars, metric = "wt", group = "cyl") {
          dat %>%
            group_by(across(all_of(c(group)))) %>%
            summarise("{paste0('mean_',metric)}" := mean(.data[[metric]]), .groups="keep")
        }
        
        get_means()
        
        

        更多详细讨论请参见:?dplyr_data_masking

        【讨论】:

          猜你喜欢
          • 2015-03-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-01
          • 1970-01-01
          • 2016-03-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多