将参数传递给 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
我想使用 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 功能,你
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