#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. Спасибо за объяснение! Я не очень хорошо понимаю эти концепции, но это делает их намного понятнее. Я не понимал, что функция будет работать без объятий. Я думаю, тогда лучше не иметь значения по умолчанию.