Tidyeval:将列列表作为确定传递给select()

时间:2018-10-31 16:42:32

标签: r dplyr rlang nse tidyeval

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

目前,我正在将列名称列表传递给pmap(),这很正常,尽管我不知道这是否是“正确”的方法。但是我不知道如何为select()使用相同的quosure / list。

我几乎没有tidyeval的经验,我只是通过玩耍而达到了这一目标。我想必须有一种方法可以对pmap()select()使用相同的东西,最好不必将我的每个列名都用引号引起来,但我还没有找到。< / p>

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

2 个答案:

答案 0 :(得分:4)

这有点棘手,因为此问题涉及多种语义。 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]]
}

了解整洁的评估方式是很困难的,部分原因是我们通常无法观察到取消报价的步骤。我们将使用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()pmap_int()调用list(a, b),并在数据框中对其进行了评估,这正是我们所需要的:

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

答案 1 :(得分:1)

当元素多于一个时,我们可以使用quos并用!!!求值

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

可以使用以下对象创建对象“ df2”

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

如果我们要像OP的帖子中那样使用quasure,

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