【问题标题】:Pass named parameters to function that calls function that is not mutate将命名参数传递给调用非变异函数的函数
【发布时间】:2020-05-17 15:48:29
【问题描述】:

我正在尝试做一些与here 非常相似的事情。

本质上,我需要将一个命名列表传递给一个函数,然后将该命名列表作为另一个函数的参数提供给另一个函数。

如果我们点击那个链接,他们就可以通过 mutate 来做到这一点,我可以复制它:

df <- tribble(
    ~a,
    1
)

foo <- function(x, args) {
    mutate(x, !!! args)
}

foo(df, quos(b = 2, c = 3)

# A tibble: 1 x 3
      a     b     c
  <dbl> <dbl> <dbl>
1     1     2     3

但是,如果我尝试使用任何其他功能来执行此操作,它就会失败。比如说,如果我尝试使用 print,(第一个参数是 x,所以我传递了一个带有 x 的命名列表):

print(x= "hello")
[1] "hello"

foo <- function(x, args) {
    print(!!! args)
}

foo(df, quos(x = "hello"))

Error in !args : invalid argument type

我不确定为什么这在“tidyverse”功能之外不起作用。我尝试了 sym、enquo、bang bang、curly curly 等的不同组合,但无济于事。

当然,我的最终目标不是使用 print,而是使用另一个用户定义的函数来代替它,所以如果您对如何实现这一点有任何建议,我也将不胜感激。 (顺便说一句,我确实必须使用命名列表,我认为我不能使用......)。

非常感谢您的帮助。

【问题讨论】:

  • tidyverse 在 tidyverse 函数中运行

标签: r tidyverse rlang tidy tidyeval


【解决方案1】:

您可以在函数中使用match.call() 来获取参数列表及其名称:

myfun <- function(x, ...){
    args <- as.list(match.call())[-1]
    print(setNames(unlist(args), names(args)))
    lapply(match.call()[-1], class)
}

myfun(x=list(a=1, b="hi", c="a"), b=5)
#> $x
#> list(a = 1, b = "hi", c = "a")
#> 
#> $b
#> [1] 5
#> $x
#> [1] "call"
#> 
#> $b
#> [1] "numeric"

【讨论】:

    【解决方案2】:

    您需要blast(),它可能会包含在 rlang 0.5.0 中。

    blast <- function(expr, env = parent.frame()) {
      rlang::eval_bare(rlang::enexpr(expr), env)
    }
    
    blast(cbind(!!!letters))
    

    【讨论】:

    • 有没有办法在给blast的列表中也包含一个未命名的参数,或者单独包含另一个参数?即,如果我的函数有 3 个参数,并且我想在第一个参数中使用管道,并为其他 2 个使用 blast?代码类似于df_to_apply_to %&gt;% blast(func_to_apply(!!!other_params))
    • blast() 并不意味着通过管道输入。它必须包含准引用应该发生的表达式。所以:blast(df %&gt;% f(!!!other))
    【解决方案3】:

    我认为区分字面值(也称为常量)和未计算的表达式很重要。例如,quos( b=2, c=3 ) 将始终计算为 2 和 3,无论上下文如何。在这种情况下,您实际上并不需要它们是 quosures 或表达式,而一个简单的值列表就可以了。然后,您可以使用 purrr::lift 将任意函数从采用 ... 点转换为采用列表。不需要!!!

    arglist <- list( replace=TRUE, size=5, x=1:10 )     # Note: list, not quos
    sample2 <- purrr::lift(sample)
    sample2( arglist )         # Same as sample( x=1:10, size=5, replace=TRUE)
    # [1]  7  3 10  8  3
    

    当您想要引用可能尚未定义的变量或列时,未计算的表达式会发挥作用。在这种情况下,您可以利用rlang::list2() 来捕获!!! 拼接的参数列表:

    subset2 <- function( x, ... )
        rlang::eval_tidy(rlang::expr(subset( {{x}}, !!!rlang::list2(...) )))
    
    # Capture expressions because mpg and cyl are undefined at this point
    argexpr <- rlang::exprs( mpg < 15, select=cyl )
    
    # base::subset() doesn't support !!!, but our new function does!
    subset( mtcars, !!!argexpr )   # Error in !argexpr : invalid argument type
    subset2( mtcars, !!!argexpr )  # Same as subset( mtcars, mpg < 15, select=cyl )
    mtcars %>% subset2(!!!argexpr) # Also works with the pipe
    #                     cyl
    # Duster 360            8
    # Cadillac Fleetwood    8
    # ...
    

    在上面,subset2()“手动”构造了一个subset( x, arg1, arg2, etc. ) 表达式,然后对其求值。 curly-curly 运算符用作!!enquo(x) 将用户表达式直接粘贴到最终表达式中的快捷方式,而rlang::list2() 则扩展并拼接所有其他参数。通过使用rlang::list2() 而不是base:list(),我们将在整个函数中添加对!!! 的支持。

    还值得强调 rlang::exec()rlang::call2(),它们是基本的 do.callcall 的 tidyverse 等价物。两者都通过!!! 提供对参数拼接的无缝支持:

    rlang::exec( sample, !!!arglist )
    eval(rlang::call2( subset, mtcars, !!!argexpr ))
    

    最后,@Moody_Mudskipper 有一个非常好的adverbs/tags package。其中一个标签为任意函数添加了 NSE 支持,并与 %&gt;% 完全集成:

    library(tags)    ## installed with devtools::install_github("moodymudskipper/tags")
    using_bang$sample( !!!arglist )
    using_bang$subset( mtcars, !!!argexpr )
    mtcars %>% using_bang$subset( !!!argexpr )
    

    【讨论】:

    • subset2() 函数很奇怪。我不明白它想做什么。
    • 嗨@LionelHenry。它与what you taught me to use for aes() 的技巧基本相同,只是我在这里使用list2() 代替enquos(),因为表达式已经在argexpr 中捕获,因此不需要其他级别的引用。我将添加一些额外的说明。
    • 附带说明,您的技巧对于“修复”使用 NSE 与 map()%&gt;% 一起工作的基本函数非常有用。这包括lmglmcoxph
    • 我明白了,list2()... 内部启用!!!,在外部启用blast()(或在本例中为 eval + expr)将参数转发给subset()。我想知道我们是否可以使这种模式更容易。顺便说一句,我认为最好在subset2() 的签名中重现尽可能多的命名参数。
    • @LionelHenry:是的,你是对的。我尽量保持简单,因为我的答案已经有点长了。但我认为在这种情况下保留x 很重要,特别是因为OP 想要使用%&gt;%
    猜你喜欢
    • 2014-10-31
    • 1970-01-01
    • 1970-01-01
    • 2019-12-19
    • 2019-07-22
    • 2013-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多