r - Tidyeval:将列列表作为 quosure 传递给 select()

标签 r dplyr rlang nse tidyeval

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

目前,我将列名列表传递给 pmap()作为一个quosure,效果很好,虽然我不知道这是否是“正确”的方法。但我不知道如何为 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

最佳答案

这有点棘手,因为这个问题涉及到语义的混合。 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()来电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))

尖括号内的任何内容都按原样传递。这表明我们应该以某种方式使用 !!!相反,将 quosures 列表的每个元素内联到周围的表达式中。让我们尝试一下:
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

关于r - Tidyeval:将列列表作为 quosure 传递给 select(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53088321/

相关文章:

r - 将数据子集到 R 中的第一次出现

r - 使用dplyr获取基本统计数据(min、mean、max、sd)?

r - purrr::pmap 与 rlang 的混淆行为; "to quote"或不引用 Q 的参数

r - 使用 data.table 进行准报价

r - 获取当前脚本的路径

r - R中治疗组和地点的多样性指数

r - 指定不同类型的缺失值 (NA)

r - 大表破坏性过滤的解决方案

R - 如何在保持分组的同时重新排列数据框中的行?

r - rlang 包中的 sym() 和 parse_expr() 有什么区别?