Объятие Tidyeval не работает со значением по умолчанию

#r #dplyr #tidyeval

#r #dplyr #tidyeval

Вопрос:

У меня есть функция perc_diff , которую я использую в dplyr mutate . Он вычисляет относительные отличия от первого значения в группе по умолчанию. Но он также может работать с mean , max , nth или любой функцией, которая возвращает одно значение для сравнения других.

 perc_diff <- function(num, fun = first, ...) {
    (num - fun(num, ...)) / fun(num, ...) * 100
}
 

Иногда мне нужно больше контроля над тем, с какой группой сравнивать. В этом случае я упорядочиваю data.frame, обнаруживая шаблон, а затем использую first .

 test_data <- data.frame(group = paste0("group_", rep(LETTERS[1:3], 3)), value = 1:9, other = rep(1:3, each = 3)) %>%
arrange(rnorm(9)) 

test_data %>%
group_by(other) %>%
arrange(other, desc(str_detect(group, "A$"))) %>%
mutate(pdiff = perc_diff(value))
 

Я хотел пропустить этап упорядочивания и встроить его в функцию, а также вернуть NAs, если он не может найти контрольную группу. Я создал get_control_value функцию, которая perc_diff могла бы использовать вместо first . Я использовал технику embrace для программирования с помощью dplyr, чтобы получить столбец тестовой группы.

 get_control_value <- function(value, test_group_column = test_group, control_group_pattern = "A$") {
    test_vector <- stringr::str_detect({{test_group_column}}, control_group_pattern)
    if (sum(test_vector) == 1) {
        value[test_vector]
    } else {
        NA
    }
}
 

Это работает хорошо, если я даю ему значение для test_group_column .

 test_data %>%
group_by(other) %>%
mutate(pdiff = perc_diff(value, get_control_value, test_group_column = group)) %>%
arrange(other, group)
 

Но оно не работает со значением по умолчанию.

 test_data %>%
rename(group = test_group) %>%
group_by(other) %>%
mutate(pdiff = perc_diff(value, get_control_value)) %>%
arrange(other, group)
 

Мой вопрос — почему это не работает со значением по умолчанию? Я предполагаю, что это как-то связано с str_detect отсутствием надлежащего контекста квазиквотации. Но почему тогда это работает, если я задаю ему значение вручную? Потому что я делаю это внутри mutate ?

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

Ответ №1:

Просто подумайте, что произойдет, если вы вызовете просто

 perc_diff(5, get_control_value)
 

Каким будет значение по умолчанию? Нет mutate() , поэтому нет столбца с именем «test_group». Как написано, perc_diff функция не знает, что она предназначена для запуска внутри a mutate() . Он не знает о «контексте данных». get_control_value Для функции нет места для поиска значений для групп. Поскольку str_detect не понимает квазинотации, передача {{test_group}} — это то же самое, что и передача test_group . Фигурные скобки ничего не делают. Точно так {{5}} же, как и 5 вне синтаксиса rlang. Вы можете удалить фигурные скобки, и он будет вести себя так же.

Когда вы вызываете

 perc_diff(value, get_control_value, test_group_column = group)
 

Вы не передаете имя столбца, вы фактически передаете значения столбца. (опять же, поскольку {{}} ничего не делает для str_detect ). Когда вы вызываете функции в R, переменные просматриваются в соответствии с лексической областью видимости. Это означает, что значения поступают оттуда, где определены функции, а не там, где они вызываются. Это означает, что необходимо передать все значения, которые вы хотите, чтобы ваша функция mutate() использовала внутри. Вызываемая функция не имеет доступа к фрейму данных, поскольку он не попадает в дерево лексической области видимости.

Из-за того, что функции являются вложенными, не так-то просто подняться по стеку вызовов, чтобы найти, откуда могут поступать данные. Итак, правило таково: если вашей функции нужны значения из вашего фрейма данных, вам нужно передать их в качестве параметра.

Но в данном конкретном случае вы могли бы технически сделать

 get_control_value <- function(value, test_group_column = eval.parent(quote(test_group), 2), control_group_pattern = "A$") {
  test_vector <- stringr::str_detect(test_group_column, control_group_pattern)
  if (sum(test_vector) == 1) {
    value[test_vector]
  } else {
    NA
  }
}
 

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

Комментарии:

1. Спасибо за объяснение! Я не очень хорошо понимаю эти концепции, но это делает их намного понятнее. Я не понимал, что функция будет работать без объятий. Я думаю, тогда лучше не иметь значения по умолчанию.