как отменить кавычки (!!) внутри `map` внутри `mutate`

#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 with split (или new dplyr::group_split ), а затем работать с отдельными list элементами, см. Обновление к моему сообщению.

2. @lionel Я согласен с Маурицем — я не совсем понимаю, почему это использование намного сложнее, чем «типичное» tidyeval использование, описанное в R4DS, виньетках и т. Д. Это кажется довольно естественным способом использования вложенности и сопоставления. Мне было бы любопытно узнать, какие шаги вы бы предпочли для этой операции.

3. Я думаю, что вложенные мутации сложны, но я согласен с тем, что другие могут не найти это так. В любом случае, это усложняет семантику аккуратной оценки. @lost Шаг, который я рекомендую, — дать имя анонимной функции.

4. Может быть, мы сможем найти решение, чтобы сделать вещи более соответствующими интуиции здесь. Это продолжает отключать аккуратных пользователей eval :/

5. @LionelHenry похоже, это решение больше не работает… как я могу заставить это работать с новым {{}} синтаксисом?