Передавать аргументы функции dplyr
Я хочу параметризовать следующее вычисление, используя dplyr
, который находит, какие значения Sepal.Length
связаны с более чем одним значением Sepal.Width
:
library(dplyr)
iris %>%
group_by(Sepal.Length) %>%
summarise(n.uniq=n_distinct(Sepal.Width)) %>%
filter(n.uniq > 1)
Обычно я писал бы что-то вроде этого:
not.uniq.per.group <- function(data, group.var, uniq.var) {
iris %>%
group_by(group.var) %>%
summarise(n.uniq=n_distinct(uniq.var)) %>%
filter(n.uniq > 1)
}
Однако этот подход вызывает ошибки, поскольку dplyr
использует нестандартную оценку. Как записать эту функцию?
Ответы
Ответ 1
Вам нужно использовать стандартные оценочные версии функций dplyr
(просто добавьте '_' к именам функций, т. group_by_
& summarise_
) и передать строки в свою функцию, которые затем вам нужно превратить в символы. Для параметризации аргумента summarise_ вам необходимо использовать interp()
, который определен в пакете lazyeval
. В частности:
library(dplyr)
library(lazyeval)
not.uniq.per.group <- function(df, grp.var, uniq.var) {
df %>%
group_by_(grp.var) %>%
summarise_( n_uniq=interp(~n_distinct(v), v=as.name(uniq.var)) ) %>%
filter(n_uniq > 1)
}
not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width")
Обратите внимание, что в последних версиях dplyr
стандартные оценочные версии функций dplyr были "мягко признаны устаревшими" в пользу нестандартной оценки.
См. Программирование с помощью dplyr
для получения дополнительной информации о работе с нестандартной оценкой.
Ответ 2
Подобно старым версиям dplyr до 0.5, новый dplyr имеет возможности как для стандартной оценки (SE), так и для нестандартной оценки (NSE). Но они выражены иначе, чем раньше.
Если вам нужна функция NSE, вы передаете голые выражения и используете enquo, чтобы записать их в качестве предложений. Если вам нужна функция SE, просто передайте кавычки (или символы) напрямую, а затем заключите их в кавычки в вызовах dplyr. Вот SE решение этого вопроса:
library(tidyverse)
library(rlang)
f1 <- function(df, grp.var, uniq.var) {
df %>%
group_by(!!grp.var) %>%
summarise(n_uniq = n_distinct(!!uniq.var)) %>%
filter(n_uniq > 1)
}
a <- f1(iris, quo(Sepal.Length), quo(Sepal.Width))
b <- f1(iris, sym("Sepal.Length"), sym("Sepal.Width"))
identical(a, b)
#> [1] TRUE
Обратите внимание, что версия SE позволяет вам работать со строковыми аргументами - сначала просто превратите их в символы с помощью sym()
. Для получения дополнительной информации см. Программирование с помощью dplyr vignette.
Ответ 3
В версии devel dplyr
(вскоре будет выпущен 0.6.0
), мы также можем использовать несколько иной синтаксис для передачи переменных.
f1 <- function(df, grp.var, uniq.var) {
grp.var <- enquo(grp.var)
uniq.var <- enquo(uniq.var)
df %>%
group_by(!!grp.var) %>%
summarise(n_uniq = n_distinct(!!uniq.var)) %>%
filter(n_uniq >1)
}
res2 <- f1(iris, Sepal.Length, Sepal.Width)
res1 <- not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width")
identical(res1, res2)
#[1] TRUE
Здесь enquo
принимает аргументы и возвращает значение как quosure
(аналогично подстановке в базе R), оценивая аргументы функции лениво и внутри суммирования, мы просим его unquote (!! или UQ), поэтому что он получает оценку.
Ответ 4
В текущей версии dplyr
(0.7.4) использование стандартных версий оценочных функций (добавлено "_" к имени функции, например group_by_
) устарело. Вместо этого вы должны полагаться на tidyeval при написании функций.
Вот пример того, как будет выглядеть ваша функция:
# definition of your function
not.uniq.per.group <- function(data, group.var, uniq.var) {
# enquotes variables to be used with dplyr-functions
group.var <- enquo(group.var)
uniq.var <- enquo(uniq.var)
# use '!!' before parameter names in dplyr-functions
data %>%
group_by(!!group.var) %>%
summarise(n.uniq=n_distinct(!!uniq.var)) %>%
filter(n.uniq > 1)
}
# call of your function
not.uniq.per.group(iris, Sepal.Length, Sepal.Width)
Если вы хотите узнать все о деталях, отличная виньетка команды dplyr о том, как это работает.
Ответ 5
Я написал функцию в прошлом, которая делает что-то похожее на то, что вы делаете, за исключением того, что она исследует все столбцы вне первичного ключа и ищет несколько уникальных значений для каждой группы.
find_dups = function(.table, ...) {
require(dplyr)
require(tidyr)
# get column names of primary key
pk <- .table %>% select(...) %>% names
other <- names(.table)[!(names(.table) %in% pk)]
# group by primary key,
# get number of rows per unique combo,
# filter for duplicates,
# get number of distinct values in each column,
# gather to get df of 1 row per primary key, other column,
# filter for where a columns have more than 1 unique value,
# order table by primary key
.table %>%
group_by(...) %>%
mutate(cnt = n()) %>%
filter(cnt > 1) %>%
select(-cnt) %>%
summarise_each(funs(n_distinct)) %>%
gather_('column', 'unique_vals', other) %>%
filter(unique_vals > 1) %>%
arrange(...) %>%
return
# Final dataframe:
## One row per primary key and column that creates duplicates.
## Last column indicates how many unique values of
## the given column exist for each primary key.
}
Эта функция также работает с оператором трубопровода:
dat %>% find_dups(key1, key2)
Ответ 6
Вы можете избежать lazyeval
, используя do
для вызова анонимной функции, а затем используя get
. Это решение можно использовать в более общем плане для использования нескольких агрегатов. Обычно я пишу функцию отдельно.
library(dplyr)
not.uniq.per.group <- function(df, grp.var, uniq.var) {
df %>%
group_by_(grp.var) %>%
do((function(., uniq.var) {
with(., data.frame(n_uniq = n_distinct(get(uniq.var))))
}
)(., uniq.var)) %>%
filter(n_uniq > 1)
}
not.uniq.per.group(iris, "Sepal.Length", "Sepal.Width")
Ответ 7
Вот способ сделать это из Rlang 0.4, используя фигурные фигурные {{
псевдооператор:
library(dplyr)
not.uniq.per.group <- function(data, group.var, uniq.var) {
data %>%
group_by({{group.var}}) %>%
summarise(n.uniq=n_distinct({{uniq.var}})) %>%
filter(n.uniq > 1)
}
iris %>% not.uniq.per.group(Sepal.Length, Sepal.Width)
#> # A tibble: 25 x 2
#> Sepal.Length n.uniq
#> <dbl> <int>
#> 1 4.4 3
#> 2 4.6 4
#> 3 4.8 3
#> 4 4.9 5
#> 5 5 8
#> 6 5.1 6
#> 7 5.2 4
#> 8 5.4 4
#> 9 5.5 6
#> 10 5.6 5
#> # ... with 15 more rows