如何将 '...' 参数传递给 lazyeval 中的 interp() 公式
How to pass '...' argument into an interp() formula within lazyeval
我正在尝试进行一些参数化 dplyr
操作。表达问题根源的最简单的可重现示例是:
# Data
test <- data.frame(group = rep(1:5, each = 2),
value = as.integer(c(NA, NA, 2, 3, 3, 5, 7, 8, 9, 0)))
> test
group value
1 1 NA
2 1 NA
3 2 2
4 2 3
5 3 3
6 3 5
7 4 7
8 4 8
9 5 9
10 5 0
# Summarisation example, this is what I'd like to parametrise
# so that I can pass in functions and grouping variables dynamically
test.summary <- test %>%
group_by(group) %>%
summarise(group.mean = mean(value, na.rm = TRUE))
> test.summary
Source: local data frame [5 x 2]
group group.mean
<int> <dbl>
1 1 NaN
2 2 2.5
3 3 4.0 # Correct results
4 4 7.5
5 5 4.5
这是我一个人走的路
# This works fine, but notice there's no 'na.rm = TRUE' passed in
doSummary <- function(d_in = data, func = 'mean', by = 'group') {
# d_in: data in
# func: required function for summarising
# by: the variable to group by
# NOTE: the summary is always for the 'value' column in any given dataframe
# Operations for summarise_
ops <- interp(~f(value),
.values = list(f = as.name(func),
value = as.name('value')))
d_out <- d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(ops, func))
}
> doSummary(test)
Source: local data frame [5 x 2]
group mean(value)
<int> <dbl>
1 1 NA
2 2 2.5
3 3 4.0
4 4 7.5
5 5 4.5
尝试使用 'na.rm' 参数
# When I try passing in the 'na.rm = T' parameter it breaks
doSummary.na <- function(d_in = data, func = 'mean', by = 'group') {
# Doesn't work
ops <- interp(~do.call(f, args),
.values = list(f = func,
args = list(as.name('value'), na.rm = TRUE)))
d_out <- d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(ops, func))
}
> doSummary.na(test)
Error: object 'value' not found
非常感谢您的帮助!
你的标题提到 ...
但你的问题没有。如果我们不需要处理 ...
,答案会变得 容易很多 ,因为我们根本不需要 do.call
,我们可以调用直接发挥作用;只需将您的 ops
定义替换为:
ops = interp(~f(value, na.rm = TRUE),
f = match.fun(func), value = as.name('value'))
请注意,我在这里使用 match.fun
而不是 as.name
。这通常是一个更好的主意,因为它在函数查找方面“就像 R 一样”。因此,您不能只将函数名称字符作为参数传递,还可以传递函数名称或匿名函数:
doSummary.na(test, function (x, ...) mean(x, ...) / sd(x, ...)) # x̂/s?! Whatever.
说起来,你设置列名的尝试也失败了;您需要将 ops
放入列表中以解决该问题:
d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(list(ops), func))
... 因为 .dots
需要一个操作列表(并且 setNames
也需要一个 vector/list)。但是,如果您将 func
object 传递给不是字符向量的函数,此代码将再次无效。为了使它更健壮,使用这样的东西:
fname = if (is.character(func)) {
func
} else if (is.name(substitute(func))) {
as.character(substitute(func))
} else {
'func'
}
d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(list(ops), fname))
如果你真的想允许传递 ...
而不是已知的参数,事情会变得更复杂,因为(据我所知)根本没有通过 [= 传递 ...
的直接方法29=],并且像你一样,我无法使用 do.call
方法。
‹lazyeval›包提供了非常好的功能make_call
,它可以帮助我们找到解决方案。上面也可以写成
# Not good. :-(
ops = make_call(as.name(func), list(as.name('value'), na.rm = TRUE))
这行得通。 BUT 仅当 func
作为字符向量传递时。如上所述,这根本不灵活。
然而,make_call
简单地包装了基 R 的 as.call
,我们可以直接使用它:
ops = as.call(list(match.fun(func), as.name('value'), na.rm = TRUE))
现在我们可以简单地将 ...
传递给:
doSummary = function (d_in = data, func = 'mean', by = 'group', ...) {
ops = as.call(list(match.fun(func), as.name('value'), ...))
fname = if (is.character(func)) {
func
} else if (is.name(substitute(func))) {
as.character(substitute(func))
} else {
'func'
}
d_in %>%
group_by_(by) %>%
summarize_(.dots = setNames(list(ops), fname))
}
需要说明的是:使用 interp
也可以实现同样的效果,但我认为这需要从列表中手动构建 formula
object,这相当于做了很多与我的解决方案相同,然后(冗余地)对结果调用 interp
。
我通常发现 ‹lazyeval› 非常优雅,但在某些情况下,基础 R 提供了更简单的解决方案。特别是,interp
是一个强大的 substitute
替代品,但 bquote
,一个未被充分利用的基本 R 函数,已经提供了许多相同的语法优势。 ‹lazyeval› objects 的最大好处是它们可以携带它们的计算环境,这与基本 R 表达式不同。然而,这并不总是需要的。
我正在尝试进行一些参数化 dplyr
操作。表达问题根源的最简单的可重现示例是:
# Data
test <- data.frame(group = rep(1:5, each = 2),
value = as.integer(c(NA, NA, 2, 3, 3, 5, 7, 8, 9, 0)))
> test
group value
1 1 NA
2 1 NA
3 2 2
4 2 3
5 3 3
6 3 5
7 4 7
8 4 8
9 5 9
10 5 0
# Summarisation example, this is what I'd like to parametrise
# so that I can pass in functions and grouping variables dynamically
test.summary <- test %>%
group_by(group) %>%
summarise(group.mean = mean(value, na.rm = TRUE))
> test.summary
Source: local data frame [5 x 2]
group group.mean
<int> <dbl>
1 1 NaN
2 2 2.5
3 3 4.0 # Correct results
4 4 7.5
5 5 4.5
这是我一个人走的路
# This works fine, but notice there's no 'na.rm = TRUE' passed in
doSummary <- function(d_in = data, func = 'mean', by = 'group') {
# d_in: data in
# func: required function for summarising
# by: the variable to group by
# NOTE: the summary is always for the 'value' column in any given dataframe
# Operations for summarise_
ops <- interp(~f(value),
.values = list(f = as.name(func),
value = as.name('value')))
d_out <- d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(ops, func))
}
> doSummary(test)
Source: local data frame [5 x 2]
group mean(value)
<int> <dbl>
1 1 NA
2 2 2.5
3 3 4.0
4 4 7.5
5 5 4.5
尝试使用 'na.rm' 参数
# When I try passing in the 'na.rm = T' parameter it breaks
doSummary.na <- function(d_in = data, func = 'mean', by = 'group') {
# Doesn't work
ops <- interp(~do.call(f, args),
.values = list(f = func,
args = list(as.name('value'), na.rm = TRUE)))
d_out <- d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(ops, func))
}
> doSummary.na(test)
Error: object 'value' not found
非常感谢您的帮助!
你的标题提到 ...
但你的问题没有。如果我们不需要处理 ...
,答案会变得 容易很多 ,因为我们根本不需要 do.call
,我们可以调用直接发挥作用;只需将您的 ops
定义替换为:
ops = interp(~f(value, na.rm = TRUE),
f = match.fun(func), value = as.name('value'))
请注意,我在这里使用 match.fun
而不是 as.name
。这通常是一个更好的主意,因为它在函数查找方面“就像 R 一样”。因此,您不能只将函数名称字符作为参数传递,还可以传递函数名称或匿名函数:
doSummary.na(test, function (x, ...) mean(x, ...) / sd(x, ...)) # x̂/s?! Whatever.
说起来,你设置列名的尝试也失败了;您需要将 ops
放入列表中以解决该问题:
d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(list(ops), func))
... 因为 .dots
需要一个操作列表(并且 setNames
也需要一个 vector/list)。但是,如果您将 func
object 传递给不是字符向量的函数,此代码将再次无效。为了使它更健壮,使用这样的东西:
fname = if (is.character(func)) {
func
} else if (is.name(substitute(func))) {
as.character(substitute(func))
} else {
'func'
}
d_in %>%
group_by_(by) %>%
summarise_(.dots = setNames(list(ops), fname))
如果你真的想允许传递 ...
而不是已知的参数,事情会变得更复杂,因为(据我所知)根本没有通过 [= 传递 ...
的直接方法29=],并且像你一样,我无法使用 do.call
方法。
‹lazyeval›包提供了非常好的功能make_call
,它可以帮助我们找到解决方案。上面也可以写成
# Not good. :-(
ops = make_call(as.name(func), list(as.name('value'), na.rm = TRUE))
这行得通。 BUT 仅当 func
作为字符向量传递时。如上所述,这根本不灵活。
然而,make_call
简单地包装了基 R 的 as.call
,我们可以直接使用它:
ops = as.call(list(match.fun(func), as.name('value'), na.rm = TRUE))
现在我们可以简单地将 ...
传递给:
doSummary = function (d_in = data, func = 'mean', by = 'group', ...) {
ops = as.call(list(match.fun(func), as.name('value'), ...))
fname = if (is.character(func)) {
func
} else if (is.name(substitute(func))) {
as.character(substitute(func))
} else {
'func'
}
d_in %>%
group_by_(by) %>%
summarize_(.dots = setNames(list(ops), fname))
}
需要说明的是:使用 interp
也可以实现同样的效果,但我认为这需要从列表中手动构建 formula
object,这相当于做了很多与我的解决方案相同,然后(冗余地)对结果调用 interp
。
我通常发现 ‹lazyeval› 非常优雅,但在某些情况下,基础 R 提供了更简单的解决方案。特别是,interp
是一个强大的 substitute
替代品,但 bquote
,一个未被充分利用的基本 R 函数,已经提供了许多相同的语法优势。 ‹lazyeval› objects 的最大好处是它们可以携带它们的计算环境,这与基本 R 表达式不同。然而,这并不总是需要的。