ошибка при использовании NSE (в dplyr): объект 'value' не найден - PullRequest
0 голосов
/ 08 ноября 2018

Я пытаюсь ознакомиться с использованием NSE в моем коде, где это оправдано. Допустим, у меня есть пары столбцов и я хочу сгенерировать новую строковую переменную для каждой пары, указывающую, являются ли значения в этой паре одинаковыми.

library(tidyverse)
library(magrittr)

df <- tibble(one.x = c(1,2,3,4),
             one.y = c(2,2,4,3),
             two.x = c(5,6,7,8),
             two.y = c(6,7,7,9),
             # not used but also in df
             extra = c(5,5,5,5))

Я пытаюсь написать код, который будет выполнять то же самое, что и следующий код:

df.mod <- df %>%
  # is one.x the same as one.y?
  mutate(one.x_suffix = case_when( 
    one.x == one.y ~ "same",
    TRUE ~ "different")) %>%
  # is two.x the same as two.y?
  mutate(two.x_suffix = case_when(
    two.x == two.y ~ "same",
    TRUE ~ "different"))

df.mod
#> # A tibble: 4 x 6
#>   one.x one.y two.x two.y one.x_suffix two.x_suffix
#>   <dbl> <dbl> <dbl> <dbl> <chr>        <chr>       
#> 1    1.    2.    5.    6. different    different   
#> 2    2.    2.    6.    7. same         different   
#> 3    3.    4.    7.    7. different    same        
#> 4    4.    3.    8.    9. different    different

В моих реальных данных у меня есть произвольное количество таких пар (например, three.x и three.y, ...), поэтому я хочу написать более обобщенную процедуру, используя mutate_at.

Моя стратегия состоит в том, чтобы передать переменные ".x" как .vars, а затем gsub "x" для "y" на одной стороне теста на равенство внутри case_when, например, так:

df.mod <- df %>%
  mutate_at(vars(one.x, two.x),
            funs(suffix = case_when(
              . == !!sym(gsub("x", "y", deparse(substitute(.)))) ~ "same",
              TRUE ~ "different")))
#> Error in mutate_impl(.data, dots): Evaluation error: object 'value' not found.

Это когда я получаю исключение. Похоже, что часть gsub работает нормально:

df.debug <- df %>%
  mutate_at(vars(one.x, two.x),
            funs(suffix = gsub("x", "y", deparse(substitute(.)))))
df.debug
#> # A tibble: 4 x 6
#>   one.x one.y two.x two.y one.x_suffix two.x_suffix
#>   <dbl> <dbl> <dbl> <dbl> <chr>        <chr>       
#> 1    1.    2.    5.    6. one.y        two.y       
#> 2    2.    2.    6.    7. one.y        two.y       
#> 3    3.    4.    7.    7. one.y        two.y       
#> 4    4.    3.    8.    9. one.y        two.y

Это операция !!sym(), которая вызывает здесь исключение. Что я сделал не так?

Создано в 2018-11-07 пакетом Представить (v0.2.1)

Ответы [ 2 ]

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

Проблема не в !!sym, как вы можете видеть в следующем примере:

df %>% mutate_at( vars(one.x, two.x),
                  funs(suffix = case_when(
                    . == !!sym("one.y") ~ "same",
                    TRUE ~ "different")))
# # A tibble: 4 x 6
#   one.x one.y two.x two.y one.x_suffix two.x_suffix
#   <dbl> <dbl> <dbl> <dbl> <chr>        <chr>       
# 1     1     2     5     6 different    different   
# 2     2     2     6     7 same         different   
# 3     3     4     7     7 different    different   
# 4     4     3     8     9 different    different   

Проблема в том, что вы пытаетесь удалить substitute(.) внутри case_when:

df %>% mutate_at( vars(one.x, two.x),
                  funs(suffix = case_when(
                    . == !!substitute(.) ~ "same",
                    TRUE ~ "different")))
# Error in mutate_impl(.data, dots) : 
#   Evaluation error: object 'value' not found.

Причиной этого является приоритет оператора.Со страницы справки для !!:

The !!Оператор приводит кавычки своего аргумента.Он сразу оценивается в окружающем контексте.

В приведенном выше примере контекст для !!substitute(.) является формулой, которая сама находится внутри case_when.Это приводит к тому, что выражение немедленно заменяется на value, которое определено внутри case_when и не имеет никакого значения внутри вашего фрейма данных.

Вы хотите, чтобы выражения оставались рядом с их средой, что и есть quosures предназначены для.Заменив substitute на rlang::enquo, вы фиксируете выражение, которое привело к ., вместе с его определяющим окружением (вашим фреймом данных).Чтобы сохранить порядок, давайте перенесем ваши манипуляции gsub в отдельную функцию:

x2y <- function(.x)
{
  ## Capture the expression and its environment
  qq <- enquo(.x)

  ## Retrieve the expression and deparse it
  txt <- rlang::get_expr(qq) %>% rlang::expr_deparse()

  ## Replace x with y, as before
  txty <- gsub("x", "y", txt)

  ## Put the new expression back into the quosure
  rlang::set_expr( qq, sym(txty) )
}

Теперь вы можете использовать новую функцию x2y непосредственно в своем коде.С кавычками нет необходимости в кавычках, потому что выражения уже несут с собой свою среду;Вы можете просто оценить их, используя rlang::eval_tidy:

df %>% mutate_at(vars(one.x, two.x),
                 funs(suffix = case_when(
                   . == rlang::eval_tidy(x2y(.)) ~ "same",
                   TRUE ~ "different" )))
# # A tibble: 4 x 6
#   one.x one.y two.x two.y one.x_suffix two.x_suffix
#   <dbl> <dbl> <dbl> <dbl> <chr>        <chr>       
# 1     1     2     5     6 different    different   
# 2     2     2     6     7 same         different   
# 3     3     4     7     7 different    same        
# 4     4     3     8     9 different    different   

РЕДАКТИРОВАТЬ, чтобы ответить на вопрос в вашем комментарии : почти всегда смешивать весь код в одной строке A ПлохоИдея ™, и я настоятельно советую против этого.Тем не менее, поскольку этот вопрос касается NSE, я думаю, что важно понять, почему простое взятие содержимого x2y и вставка его в case_when приводит к проблемам.

enquo(), например substitute(),посмотрите в вызывающей среде функции и замените аргумент выражением, которое было предоставлено этой функции.substitute() поднимает только одну среду (находя value внутри case_when, когда вы ее не цитируете), тогда как enquo() продолжает двигаться вверх, пока функции в стеке вызовов правильно обрабатывают квазиквотацию .(И большинство функций dplyr / tidyverse делают.) Поэтому, когда вы вызываете enquo(.x) внутри x2y, он перемещает вверх выражения, предоставленные каждой функции в стеке вызовов, чтобы в итоге найти one.x.

КогдаВы вызываете enquo() внутри mutate_at, теперь он находится на том же уровне, что и one.x, поэтому он также заменяет аргумент (one.x в данном случае) выражением, которое его определило (вектор c(1,2,3,4) в этомдело).Это не то, что вы хотите.Вместо того, чтобы двигаться вверх по уровням, теперь вы хотите остаться на том же уровне, что и one.x.Для этого используйте rlang::quo() вместо rlang::enquo():

library( rlang )   ## To maintain at least a little bit of sanity

df %>% 
 mutate_at(vars(one.x, two.x),
   funs(suffix = case_when(
    . == eval_tidy(set_expr(quo(.), 
                            sym(gsub("x","y", expr_deparse(get_expr(quo(.)))))
                       )
            ) ~ "same",
    TRUE ~ "different" )))
# Now works as expected
0 голосов
/ 08 ноября 2018

Вот вариант с map. Мы split устанавливаем набор данных в пары столбцов 'x', 'y' с подстрокой имен столбцов, затем перебираем list наборов данных с map, transmute, чтобы создать новый столбец суффикса: сравнивая строки каждого набора данных, свяжите list наборов данных с одним набором данных и свяжите с исходным набором данных (bind_cols)

library(tidyverse)
df %>% 
    select(matches("\\.x|\\.y")) %>%
    split.default(str_remove(names(.), "\\..*")) %>%
    map( ~ .x %>%
                 transmute(!! paste0(names(.)[1], "_suffix") := 
                      reduce(., ~ c("different", "same")[(.x == .y) + 1]))) %>%
    bind_cols %>%
    bind_cols(df, .)
# A tibble: 4 x 7
#  one.x one.y two.x two.y extra one.x_suffix two.x_suffix
#   <dbl> <dbl> <dbl> <dbl> <dbl> <chr>        <chr>       
#1     1     2     5     6     5 different    different   
#2     2     2     6     7     5 same         different   
#3     3     4     7     7     5 different    same        
#4     4     3     8     9     5 different    different   

Или другой вариант - создать выражение и затем проанализировать его

library(rlang)
expr1 <- paste(grep("\\.x", names(df), value = TRUE), 
      grep("\\.y", names(df), value = TRUE), sep="==", collapse=";")
df %>% 
    mutate(!!!rlang::parse_exprs(expr1)) %>%
    rename_at(vars(matches("==")), ~ paste0(str_remove(.x, "\\s.*"), "_suffix"))
# A tibble: 4 x 7
#  one.x one.y two.x two.y extra one.x_suffix two.x_suffix
#  <dbl> <dbl> <dbl> <dbl> <dbl> <lgl>        <lgl>       
#1     1     2     5     6     5 FALSE        FALSE       
#2     2     2     6     7     5 TRUE         FALSE       
#3     3     4     7     7     5 FALSE        TRUE        
#4     4     3     8     9     5 FALSE        FALSE     

ПРИМЕЧАНИЕ. Его можно преобразовать в «тот же / другой», что и в первом решении. Но, может быть, лучше сохранить его в виде логических столбцов

...