Совет по использованию base R для простых функций хорош, однако он не масштабируется до более сложных функций обратного хода, и вы теряете переносимость на бэкэнды dplyr, такие как базы данных.Если вы хотите создавать функции вокруг конвейеров, вам нужно немного узнать о R-выражениях и операторе отмены кавычек !!
.Я рекомендую пролистать первые разделы https://tidyeval.tidyverse.org, чтобы получить общее представление об используемых здесь понятиях.
Поскольку функция, которую вы хотите создать, принимает пустое имя столбца и не содержит сложныхвыражения (как если бы вы передавали mutate()
или summarise()
), нам не нужны такие причудливые вещи, как фразы.Мы можем работать с символами.Чтобы создать символ, используйте as.name()
или rlang::sym()
.
as.name("mycolumn")
#> mycolumn
rlang::sym("mycolumn")
#> mycolumn
. Преимущество последнего состоит в том, что он является частью большого семейства функций: ensym()
, а варианты множественного числа syms()
иensyms()
.Мы собираемся использовать ensym()
для захвата имени столбца, т.е. отложить выполнение столбца, чтобы передать его dplyr после нескольких преобразований.Задержка выполнения называется «цитированием».
Я внес несколько изменений в интерфейс вашей функции:
Сначала возьмем кадры данных для согласованности с функциями dplyr
Не указывайте значения по умолчанию для фреймов данных.Эти значения по умолчанию основаны на слишком многих допущениях.
Настройка by
и suffix
настраивается пользователем, с разумными значениями по умолчанию.
Воткод с пояснениями:
mydiff <- function(df1, df2, var, by = "id", suffix = c(".x", ".y")) {
stopifnot(is.character(suffix), length(suffix) == 2)
# Let's start by the easy task, joining the data frames
df <- dplyr::inner_join(df1, df2, by = by, suffix = suffix)
# Now onto dealing with the diff variable. `ensym()` takes a column
# name and delays its execution:
var <- rlang::ensym(var)
# A delayed column name is not a string, it's a symbol. So we need
# to transform it to a string in order to work with paste() etc.
# `quo_name()` works in this case but is generally only for
# providing default names.
#
# Better use base::as.character() or rlang::as_string() (the latter
# works a bit better on Windows with foreign UTF-8 characters):
var_string <- rlang::as_string(var)
# Now let's add the suffix to the name:
col1_string <- paste0(var_string, suffix[[1]])
col2_string <- paste0(var_string, suffix[[2]])
# dplyr::select() supports column names as strings but it is an
# exception in the dplyr API. Generally, dplyr functions take bare
# column names, i.e. symbols. So let's transform the strings back to
# symbols:
col1 <- rlang::sym(col1_string)
col2 <- rlang::sym(col2_string)
# The delayed column names now need to be inserted back into the
# dplyr code. This is accomplished by unquoting with the !!
# operator:
df %>%
dplyr::select(id, !!col1, !!col2) %>%
dplyr::filter(!!col1 != !!col2)
}
mydiff(df1, df2, b)
#> # A tibble: 1 x 3
#> id b.x b.y
#> <dbl> <chr> <chr>
#> 1 18 bar foo
mydiff(df1, df2, "a")
#> # A tibble: 1 x 3
#> id a.x a.y
#> <dbl> <chr> <chr>
#> 1 14 f k
Вы также можете упростить функцию, взяв строки вместо имен столбцов.В этой версии я буду использовать syms()
для создания списка символов и !!!
, чтобы передать все сразу select()
:
mydiff2 <- function(df1, df2, var, by = "id", suffix = c(".x", ".y")) {
stopifnot(
is.character(suffix), length(suffix) == 2,
is.character(var), length(var) == 1
)
# Create a list of symbols from a character vector:
cols <- rlang::syms(paste0(var, suffix))
df <- dplyr::inner_join(df1, df2, by = by, suffix = suffix)
# Unquote the whole list as once with the big bang !!!
df %>%
dplyr::select(id, !!!cols) %>%
dplyr::filter(!!cols[[1]] != !!cols[[2]])
}
mydiff2(df1, df2, "a")
#> # A tibble: 1 x 3
#> id a.x a.y
#> <dbl> <chr> <chr>
#> 1 14 f k