将参数传递给 dplyr 函数

Pass arguments to dplyr functions

我想使用 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 使用 non-standard evaluation。这个函数应该怎么写?

您需要使用 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 函数的标准评估版本 "soft deprecated" 支持非标准评估。

有关使用非标准评估的更多信息,请参阅 Programming with dplyr vignette

我过去写过一个函数,它做的事情与您正在做的类似,除了它探索主键之外的所有列并在每组中查找多个唯一值。

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)

您可以通过使用 do 调用匿名函数然后使用 get 来避免 lazyeval。该解决方案可以更普遍地用于采用多个聚合。我一般都是单独写函数

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")

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 将参数和 returns 值作为 quosure(类似于 substitute in base R)通过惰性评估函数参数并在摘要内部,我们要求它取消引用(!! 或 UQ)以便对其进行评估。

与 0.5 之前的旧 dplyr 版本一样,新的 dplyr 具有标准评估 (SE) 和非标准评估 (NSE) 的功能。但它们的表达方式与以前不同。

如果你想要 NSE 功能,你 。如果你想要一个 SE 函数,只需直接传递 quosures(或符号),然后在 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() 将它们转换为符号。有关详细信息,请参阅 programming with dplyr 小插图。

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 团队 excellent vignette 提供了有关其工作原理的文章。

这是从 rlang 0.4 使用 curly curly {{ 伪运算符的方法:

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