Подтвердить что ты не робот

Передавать аргументы функции 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 использует нестандартную оценку. Как записать эту функцию?

4b9b3361

Ответ 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