#r #dplyr #purrr #rlang #tidyeval
#r #dplyr #муррр #rlang #tidyeval
Вопрос:
Я изменяю вложенные фреймы данных внутри foo
с map2
помощью и mutate
, и я хотел бы назвать переменную в каждом вложенном фрейме данных в соответствии с foo$name
. Я не уверен, какой правильный синтаксис для nse
/ tidyeval
unquotation будет здесь. Моя попытка:
library(tidyverse)
foo <- mtcars %>%
group_by(gear) %>%
nest %>%
mutate(name = c("one", "two", "three")) %>%
mutate(data = map2(data, name, ~
mutate(.x, !!(.y) := "anything")))
#> Error in quos(...): object '.y' not found
Я хочу, чтобы имя вновь созданной переменной внутри вложенных фреймов данных было «один», «два» и «три» соответственно.
Я основываю свою попытку на обычном синтаксисе, который я бы использовал, если бы я выполнял нормальное mutate
на нормальном df
, и где name
строка:
name <- "test"
mtcars %>% mutate(!!name := "anything") # works fine
В случае успеха следующая строка должна вернуть TRUE
:
foo[1,2] %>% unnest %>% names %>% .[11] == "one"
Комментарии:
1. Вы думаете, что это дубликат? Некоторые сходства, но, насколько я вижу, это разные проблемы.
2. Я не имел в виду обман.
Ответ №1:
Похоже, это особенность / ошибка (не уверен, см. Связанную Проблему GitHub ниже) о том, Как !!
работает внутри mutate
и map
. Решение состоит в том, чтобы определить пользовательскую функцию, и в этом случае отмена кавычек работает должным образом.
library(tidyverse)
custom_mutate <- function(df, name, string = "anything")
mutate(df, !!name := string)
foo <- mtcars %>%
group_by(gear) %>%
nest %>%
mutate(name = c("one", "two", "three")) %>%
mutate(data = map2(data, name, ~
custom_mutate(.x, .y)))
foo[1,2] %>% unnest %>% names %>% .[11] == "one"
#[1] TRUE
Более подробную информацию вы найдете на GitHub в разделе проблема # 541: вызов map2 () при ошибке dplyr::mutate() при работе автономного вызова map2(); обратите внимание, что проблема была закрыта в сентябре 2018 года, поэтому я предполагаю, что это предполагаемое поведение.
Альтернативой может быть использование group_split
вместо nest
, и в этом случае мы избегаем проблемы с отменой кавычек
nms <- c("one", "two", "three")
mtcars %>%
group_split(gear) %>%
map2(nms, ~.x %>% mutate(!!.y := "anything"))
Комментарии:
1. Я не совсем уверен; У меня было ощущение, что это связано с
map
/map2
и NSE, поэтому я искал что-то вроде «динамическое имя столбца» в сочетании сmap2
иmutate
; ссылка на проблему с GitHub была среди первых обращений.2. Кстати, я знаю, что приведенный вами код — это всего лишь игрушечный пример, но что-то подобное
mutate(name = c("one", "two", "three"))
потенциально может быть довольно опасным, если в итоге у вас будет больше (или меньше), чем 3 группы послеnest
.3. он выдаст ошибку, если ваш вектор не имеет длины 1 или той же длины, что и фрейм данных.
4. @точно потеряно, так что это не очень надежно. Я думаю, вы могли бы обернуть это в
tryCatch
среду, но, возможно, было бы лучше где-нибудь проверить, чтобы убедиться в этомnms <- c("one", "two", "three"); length(nms) == length(unique(mtcars$gear))
.5. @lost PS: Я добавил альтернативный подход, используя new
dplyr::group_split
, чтобы разделить данные по группам, а затем работать с отдельнымиlist
элементамиmap2
.
Ответ №2:
Это из-за времени отмены кавычек. Вложение аккуратных функций eval может быть немного сложным, потому что это самая первая функция tidy eval, которая обрабатывает операторы отмены кавычек.
Давайте перепишем это:
mutate(data = map2(data, name, ~ mutate(.x, !!.y := "anything")))
Для
mutate(data = map2(data, name, function(x, y) mutate(x, !!y := "anything")))
Привязки x
и y
создаются только при вызове функции с помощью map2()
. Итак, при первом mutate()
запуске эти привязки еще не существуют, и вы получаете ошибку object not found. С формулой это немного сложнее увидеть, но формула расширяется до функции, принимающей .x
и .y
аргументирующей, поэтому у нас та же проблема.
В общем, лучше избегать сложной вложенной логики в вашем коде, потому что это затрудняет чтение. С аккуратным eval это еще сложнее, поэтому лучше делать все поэтапно. В качестве дополнительного бонуса пошаговое выполнение требует создания промежуточных переменных, которые, если они правильно названы, помогают понять, что делает функция.
Комментарии:
1. Хотя я согласен с вашим общим советом «делать все поэтапно», я бы сказал, что это вряд ли сложная вложенная функция; более того, я бы предположил, что это может быть довольно распространенной ситуацией, в которой можно оказаться при работе с
nest
отредактированными данными. Я, конечно, делал (или хотел делать) подобные вещи. Возможно, стоит отметить, что проблемы можно избежать, если сначала разделитьdata.frame
/tibble
withsplit
(или newdplyr::group_split
), а затем работать с отдельнымиlist
элементами, см. Обновление к моему сообщению.2. @lionel Я согласен с Маурицем — я не совсем понимаю, почему это использование намного сложнее, чем «типичное»
tidyeval
использование, описанное в R4DS, виньетках и т. Д. Это кажется довольно естественным способом использования вложенности и сопоставления. Мне было бы любопытно узнать, какие шаги вы бы предпочли для этой операции.3. Я думаю, что вложенные мутации сложны, но я согласен с тем, что другие могут не найти это так. В любом случае, это усложняет семантику аккуратной оценки. @lost Шаг, который я рекомендую, — дать имя анонимной функции.
4. Может быть, мы сможем найти решение, чтобы сделать вещи более соответствующими интуиции здесь. Это продолжает отключать аккуратных пользователей eval :/
5. @LionelHenry похоже, это решение больше не работает… как я могу заставить это работать с новым
{{}}
синтаксисом?