【问题标题】:Tidyeval: pass list of columns as quosure to select()Tidyeval:将列列表作为 quosure 传递给 select()
【发布时间】:2018-10-31 16:42:31
【问题描述】:

我想在mutate() 中将一堆列传递给pmap()。稍后,我想选择那些相同的列。

目前,我将列名列表作为 quosure 传递给 pmap(),虽然我不知道这是否是“正确”的做法。但我不知道如何为select() 使用相同的 quosure/list。

我几乎没有使用 tidyeval 的经验,我只是玩弄了这么多。我想一定有一种方法可以对pmap()select() 使用相同的东西,最好不必将我的每个列名放在引号中,但我还没有找到它。

library(dplyr)
library(rlang)
library(purrr)

df <- tibble(a = 1:3,
             b = 101:103) %>% 
    print
#> # A tibble: 3 x 2
#>       a     b
#>   <int> <int>
#> 1     1   101
#> 2     2   102
#> 3     3   103

cols_quo <- quo(list(a, b))

df2 <- df %>% 
    mutate(outcome = !!cols_quo %>% 
               pmap_int(function(..., word) {
                   args <- list(...)

                   # just to be clear this isn't what I actually want to do inside pmap
                   return(args[[1]] + args[[2]])
               })) %>% 
    print()
#> # A tibble: 3 x 3
#>       a     b outcome
#>   <int> <int>   <int>
#> 1     1   101     102
#> 2     2   102     104
#> 3     3   103     106

# I get why this doesn't work, but I don't know how to do something like this that does
df2 %>% 
    select(!!cols_quo)
#> Error in .f(.x[[i]], ...): object 'a' not found

【问题讨论】:

    标签: r dplyr rlang nse tidyeval


    【解决方案1】:

    这有点棘手,因为这个问题涉及多种语义。 pmap() 接受一个列表并将每个元素作为其自己的参数传递给一个函数(在这个意义上它相当于!!!)。因此,您的引用函数需要引用其参数并以某种方式将列列表传递给pmap()

    我们的报价功能可以采用以下两种方式之一。要么引用(即延迟)列表创建,要么立即创建引用表达式的实际列表:

    quoting_fn1 <- function(...) {
      exprs <- enquos(...)
    
      # For illustration purposes, return the quoted inputs instead of
      # doing something with them. Normally you'd call `mutate()` here:
      exprs
    }
    
    quoting_fn2 <- function(...) {
      expr <- quo(list(!!!enquos(...)))
    
      expr
    }
    

    由于我们的第一个变体只返回一个引用输入列表,它实际上等价于quos()

    quoting_fn1(a, b)
    #> <list_of<quosure>>
    #>
    #> [[1]]
    #> <quosure>
    #> expr: ^a
    #> env:  global
    #>
    #> [[2]]
    #> <quosure>
    #> expr: ^b
    #> env:  global
    

    第二个版本返回一个带引号的表达式,指示 R 创建一个带有引号输入的列表:

    quoting_fn2(a, b)
    #> <quosure>
    #> expr: ^list(^a, ^b)
    #> env:  0x7fdb69d9bd20
    

    两者之间有一个微妙但重要的区别。第一个版本创建一个实际的列表对象:

    exprs <- quoting_fn1(a, b)
    typeof(exprs)
    #> [1] "list"
    

    另一方面,第二个版本不返回列表,它返回一个用于创建列表的表达式:

    expr <- quoting_fn2(a, b)
    typeof(expr)
    #> [1] "language"
    

    让我们找出哪个版本更适合与pmap() 交互。但首先我们要为 pmapped 函数命名,以使代码更清晰、更易于试验:

    myfunction <- function(..., word) {
      args <- list(...)
      # just to be clear this isn't what I actually want to do inside pmap
      args[[1]] + args[[2]]
    }
    

    理解 tidy eval 的工作原理很难,部分原因是我们通常不会观察取消引用的步骤。我们将使用rlang::qq_show() 显示取消引用expr(延迟列表)和exprs(实际列表)与!! 的结果:

    rlang::qq_show(
      mutate(df, outcome = pmap_int(!!expr, myfunction))
    )
    #> mutate(df, outcome = pmap_int(^list(^a, ^b), myfunction))
    
    rlang::qq_show(
      mutate(df, outcome = pmap_int(!!exprs, myfunction))
    )
    #> mutate(df, outcome = pmap_int(<S3: quosures>, myfunction))
    

    当我们取消引用延迟列表时,mutate()list(a, b) 调用pmap_int(),在数据框中进行评估,这正是我们需要的:

    mutate(df, outcome = pmap_int(!!expr, myfunction))
    #> # A tibble: 3 x 3
    #>       a     b outcome
    #>   <int> <int>   <int>
    #> 1     1   101     102
    #> 2     2   102     104
    #> 3     3   103     106
    

    另一方面,如果我们取消引用一个实际的引用表达式列表,我们会得到一个错误:

    mutate(df, outcome = pmap_int(!!exprs, myfunction))
    #> Error in mutate_impl(.data, dots) :
    #>   Evaluation error: Element 1 is not a vector (language).
    

    这是因为列表中引用的表达式未在数据框中进行评估。事实上,它们根本没有被评估。 pmap() 按原样获取引用的表达式,它不理解。回想一下qq_show() 向我们展示的内容:

    #> mutate(df, outcome = pmap_int(<S3: quosures>, myfunction))
    

    尖括号内的任何内容都按原样传递。这表明我们应该以某种方式使用!!! 来内联周围表达式中引用列表的每个元素。让我们试试吧:

    rlang::qq_show(
      mutate(df, outcome = pmap_int(!!!exprs, myfunction))
    )
    #> mutate(df, outcome = pmap_int(^a, ^b, myfunction))
    

    嗯...看起来不太对劲。我们应该将一个列表传递给pmap_int(),在这里它将每个引用的输入作为单独的参数获取。事实上,我们得到了一个类型错误:

    mutate(df, outcome = pmap_int(!!!exprs, myfunction))
    #> Error in mutate_impl(.data, dots) :
    #>   Evaluation error: `.x` is not a list (integer).
    

    这很容易解决,只需调用list()

    rlang::qq_show(
      mutate(df, outcome = pmap_int(list(!!!exprs), myfunction))
    )
    #> mutate(df, outcome = pmap_int(list(^a, ^b), myfunction))
    

    瞧!

    mutate(df, outcome = pmap_int(list(!!!exprs), myfunction))
    #> # A tibble: 3 x 3
    #>       a     b outcome
    #>   <int> <int>   <int>
    #> 1     1   101     102
    #> 2     2   102     104
    #> 3     3   103     106
    

    【讨论】:

      【解决方案2】:

      当有多个元素时,我们可以使用quos,并使用!!!进行评估

      cols_quo <- quos(a, b)
      df2 %>%
          select(!!!cols_quo)
      

      对象'df2'可以用

      创建
      df %>%
          mutate(output = list(!!! cols_quo) %>% 
              reduce(`+`))
      

      如果我们想在 OP 的帖子中使用 quosure

      cols_quo <- quo(list(a, b))
      df2 %>%
          select(!!! as.list(quo_expr(cols_quo))[-1])
      # A tibble: 3 x 2
      #      a     b
      #  <int> <int>
      #1     1   101
      #2     2   102
      #3     3   103
      

      【讨论】:

      • 谢谢,我也试过了,它在 select 步骤中确实有效,但是如何使用 pmap 在中间步骤中使其工作?
      猜你喜欢
      • 1970-01-01
      • 2017-11-21
      • 2020-06-21
      • 1970-01-01
      • 2018-06-03
      • 2021-06-01
      • 2019-03-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多