Я хочу написать функцию, которая будет принимать как символические имена столбцов, так и имена, переданные как переменная (строка).
Позвольте мне показать вам пример:
Данные:
> ( d <- data.frame(A=1:3, B=3:1) )
A B
1 1 3
2 2 2
3 3 1
Теперь моя функция:
fn <- function(data, cols) {
return(data %>% mutate(across({{cols}}, ~. * 2)))
}
Он хорошо работает для:
А) символические имена
> d %>% fn(cols = A)
A B
1 2 3
2 4 2
3 6 1
> d %>% fn(cols = B)
A B
1 1 6
2 2 4
3 3 2
> d %>% fn(cols = c(A, B))
A B
1 2 6
2 4 4
3 6 2
B) имена, передаваемые в виде строк
> column <- "A"
> d %>% fn(cols = column)
A B
1 2 3
2 4 2
3 6 1
> d %>% fn(cols = c("A", "B"))
A B
1 2 6
2 4 4
3 6 2
Все идет нормально!
Теперь, когда я предоставляю внешний вектор> 1 столбец, он выдает предупреждение.
> d %>% fn(cols = columns)
Note: Using an external vector in selections is ambiguous.
i Use `all_of(columns)` instead of `columns` to silence this message.
i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
A B
1 2 6
2 4 4
3 6 2
Поэтому я добавил функцию all_of, которая хорошо работает со строками:
fn <- function(data, cols) {
return(data %>% mutate(across(all_of({{cols}}), ~. * 2)))
}
> d %>% fn(cols = columns)
A B
1 2 6
2 4 4
3 6 2
Но выдает ошибку, когда я передаю символическое имя:
> d %>% fn(cols = A)
Error: Problem with `mutate()` input `..1`.
x object 'A' not found
i Input `..1` is `across(all_of(A), ~. * 2)`.
Run `rlang::last_error()` to see where the error occurred. > d %>% fn(cols = B)
> d %>% fn(cols = c(A, B))
Error: Problem with `mutate()` input `..1`.
x object 'A' not found
i Input `..1` is `across(all_of(c(A, B)), ~. * 2)`.
Run `rlang::last_error()` to see where the error occurred.
Как это исправить, чтобы включить оба подхода и избежать предупреждения?
Да, точно. И это работает хорошо, если имена столбцов указаны явно: fn(A, B) или fn("A", "B"). Когда я предоставляю внешний вектор, он печатает примечание о неоднозначности выбора. В дальнейшем это примечание превратится в предупреждение, а затем - в ошибку. Теоретически я мог бы предоставить 2 функции, например fn() для NSE и fn_() для SE, но мне бы очень хотелось этого избежать. Может быть, какая-то условная проверка параметров?
Мое предложение состояло бы в том, чтобы сохранить вашу первоначальную реализацию и предупреждение, потому что ситуация действительно неоднозначна. Учитывать:
d <- data.frame(A=1:3, B=3:1, columns=4:6) # Note the new column named columns
columns <- c("A","B")
d %>% fn(cols = columns) # Which `columns` should this use?
Затем пользователи вашей функции могут устранить неоднозначность, используя all_of() сами, и вы можете задокументировать это на странице справки функции.
d %>% fn(cols = all_of(columns)) # works without a warning
Обновлено: Хотя я рекомендую описанный выше подход, другим способом является проверка существования переменной в вызывающей среде. Если переменная существует, предположите, что она содержит имена столбцов, и используйте ее в all_of(); в противном случае предположим, что имена столбцов указаны как есть:
fn <- function(data, cols) {
varExist <- rlang::enexpr(cols) %>%
rlang::expr_deparse() %>%
exists(envir=rlang::caller_env())
if (varExist)
data %>% mutate( across(all_of(cols), ~. *2) )
else
data %>% mutate( across({{cols}}, ~. * 2) )
}
rm(A) # Ensure there is no variable called A
d %>% fn(cols=A) # Mutate will operate on column A only
A <- c("A","B") # A now contains column names
d %>% fn(cols=A) # Mutate will operate on A and B
А, понял, не туда поставил! К сожалению, это будет использоваться в основном в динамическом скрипте, где имя столбца будет получено от веб-сервиса, но эта функция будет доступна и конечным пользователям, не знакомым с dplyr и NSE. Они «автоматически» предоставят вектор строк. Я не могу рассчитывать на то, что они прочитают руководство (теоретически должен, на практике - меня убил менеджер). Очень маловероятно, что это имя «столбцы» встречается во фрейме данных в производственной среде, с которой я работаю. Я должен найти другой способ... Или уйти из dplyr ради этой задачи.
Тем более, что сейчас это заметка, а в дальнейшем будет выдавать просто ошибку, которая сломает весь код.
@Bastian Похоже, ваша функция будет в основном использоваться со стандартной оценкой в производстве. Мое предложение состояло бы в том, чтобы взять на себя обязательство и отказаться от поддержки NSE. Однако, пожалуйста, смотрите мое редактирование, если вам абсолютно необходимо поддерживать оба.
Это красивая работа. rlang настолько силен, что мне нужно наконец изучить его, так как он открывает невероятные возможности с R. Большое спасибо, Артем. Это абсолютно соответствует моим потребностям.
Итак, вы хотите, чтобы функция работала как для символов, так и для строк?