【问题标题】:How to pass '...' argument into an interp() formula within lazyeval如何在lazyeval中将'...'参数传递给interp()公式
【发布时间】:2016-06-14 15:11:23
【问题描述】:

我正在尝试做一些参数化的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 

非常感谢您的帮助!

【问题讨论】:

  • interp 来自...?
  • @pfabri 缺少的关键信息是interp()来自包lazyeval,还有其他同名的函数,例如在akima中
  • @pfabri 我不知道以下内容是否适用于您的情况,尽管它不能直接回答您的问题interp(~do.call(f,args), .values = list(f = 'mean',args=list(na.rm=TRUE)))
  • @pfabri interp(~do.call(f,args), .values = list(f = 'mean',args=list(steps, na.rm=TRUE)))
  • 我已经实施了所有建议的更改,并将问题重写为完全可重现但简洁(我希望如此)。

标签: r lazy-evaluation nse lazyeval


【解决方案1】:

你的标题提到了...,但你的问题没有。如果我们不需要处理...,答案会很多容易,因为我们根本不需要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 也需要一个向量/列表)。但是,如果您将 func 对象传递给不是字符向量的函数,则此代码将再次不起作用。要使其更健壮,请使用以下内容:

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

如果你真的想允许传递 ... 而不是已知参数,事情会变得更加复杂,因为(据我所知)根本没有直接的方法可以通过 interp 传递 ...,而且,就像你一样,我无法让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 对象,这与我的解决方案中的操作非常相似,然后(冗余) 在结果上调用 interp

我通常发现虽然‹lazyeval›非常优雅,但在某些情况下,base R 提供了更简单的解决方案。特别是,interp 是一个强大的 substitute 替代品,但 bquote,一个未被充分利用的基本 R 函数,已经提供了许多相同的语法优势。 ‹lazyeval› 对象的最大好处是它们可以携带评估环境,这与基本 R 表达式不同。然而,这并不总是需要。

【讨论】:

    猜你喜欢
    • 2015-05-12
    • 1970-01-01
    • 2019-09-17
    • 1970-01-01
    • 2021-04-16
    • 2012-02-24
    • 1970-01-01
    • 2016-02-03
    • 2013-01-18
    相关资源
    最近更新 更多