Tidyeval: передать список столбцов как условие для выбора () - PullRequest
0 голосов
/ 31 октября 2018

Я хочу передать несколько столбцов в pmap() внутри mutate(). Позже я хочу выбрать те же столбцы.

В данный момент я передаю список имен столбцов в pmap() в качестве фразы, что прекрасно работает, хотя я не знаю, является ли это «правильным» способом сделать это. Но я не могу понять, как использовать тот же вопрос / список для select().

У меня почти нет опыта общения с тидьевалом, я зашел так далеко, только играя. Я предполагаю, что должен быть способ использовать одну и ту же вещь как для 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

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

Это немного сложно из-за сочетания семантики, связанной с этой проблемой. 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]]
}

Понимание того, как работает аккуратный 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))

Все, что находится внутри угловых скобок, передается как есть. Это признак того, что мы должны были как-то использовать !!! вместо этого, чтобы встроить каждый элемент списка предложений в окружающее выражение. Давайте попробуем это:

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
0 голосов
/ 31 октября 2018

Мы можем использовать quos, когда имеется более одного элемента, и оценивать с помощью !!!

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

Объект 'df2' может быть создан с помощью

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

Если мы хотим использовать условия, как в посте ОП

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
...