将parameter passing给dplyr函数

我想使用dplyr来参数化下面的计算,它发现哪个Sepal.Length值与多个Sepal.Length值相关联:

 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使用非标准评估 。 这个函数应该如何写?

您需要使用dplyr函数的标准评估版本(只需在函数名称中附加'_',即group_by_summarise_ ),并将string传递给函数,然后将其转换为符号。 要参数化sumrise_的参数,您需要使用在lazyeval包中定义的lazyeval interp() 。 具体来说:

 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版本高达0.5,新的dplyr有标准评估(SE)和非标准评估(NSE)的设施。 但他们expression的比以前有所不同。

如果你想要一个NSE函数,你可以传递一些简单的expression式,并使用enquo来把它们作为quards来捕获 。 如果你想要一个SEfunction,你可以省略enquo ,直接传递quards(或者symbols)。 这是SE解决scheme的问题:

 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版本使您能够使用string参数 – 只需使用sym()将它们转换为符号即可。 有关更多信息,请参阅使用dplyr晕影的编程 。

dplyr (即将发布0.6.0 )的开发版本中,我们也可以使用稍微不同的语法来传递variables。

 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通过对函数参数进行懒惰的评估并在总结中进行评估,然后将其作为参数并返回值(类似于在R中的替代),我们要求它不引用(!!或UQ)以便得到评估。

我在过去写过一个函数,它和你所做的相似,除了它探索主键之外的所有列,并为每个组寻找多个唯一值。

 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. } 

该function也适用于pipe道操作员:

 dat %>% find_dups(key1, key2) 

您可以通过使用do来调用匿名函数然后使用get来避免lazyeval 。 这种解决scheme可以更普遍地使用多个聚合。 我通常分开写函数。

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