Проблема не в !!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