在编写自己的函数时如何使用R的省略号功能?

Rya*_*son 175 parameters r function ellipsis variadic

R语言有一个很好的功能,用于定义可以采用可变数量参数的函数.例如,该函数data.frame接受任意数量的参数,并且每个参数都成为结果数据表中列的数据.用法示例:

> data.frame(letters=c("a", "b", "c"), numbers=c(1,2,3), notes=c("do", "re", "mi"))
  letters numbers notes
1       a       1    do
2       b       2    re
3       c       3    mi
Run Code Online (Sandbox Code Playgroud)

函数的签名包括省略号,如下所示:

function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, 
    stringsAsFactors = default.stringsAsFactors()) 
{
    [FUNCTION DEFINITION HERE]
}
Run Code Online (Sandbox Code Playgroud)

我想编写一个类似的函数,获取多个值并将它们合并为一个返回值(以及进行一些其他处理).为了做到这一点,我需要弄清楚如何...从函数中的函数参数"解包" .我不知道该怎么做.功能定义中的相关行data.frameobject <- as.list(substitute(list(...)))[-1L],我无法理解.

那么如何将省略号从函数的签名转换为例如列表呢?

更具体地说,我如何写get_list_from_ellipsis下面的代码?

my_ellipsis_function(...) {
    input_list <- get_list_from_ellipsis(...)
    output_list <- lapply(X=input_list, FUN=do_something_interesting)
    return(output_list)
}

my_ellipsis_function(a=1:10,b=11:20,c=21:30)
Run Code Online (Sandbox Code Playgroud)

编辑

似乎有两种可能的方法来做到这一点.他们是as.list(substitute(list(...)))[-1L]list(...).但是,这两者并没有完全相同.(有关差异,请参阅答案中的示例.)任何人都可以告诉我它们之间的实际区别是什么,我应该使用哪一个?

Mar*_*rek 109

我阅读了答案和评论,我发现很少有东西没有提到:

  1. data.frame使用list(...)版本.代码片段:

    object <- as.list(substitute(list(...)))[-1L]
    mrn <- is.null(row.names)
    x <- list(...)
    
    Run Code Online (Sandbox Code Playgroud)

    object用于对列名做一些魔术,但x用于创建final data.frame.
    要使用未评估的...参数,请查看使用的write.csv代码match.call.

  2. 当您在评论结果中写入Dirk时,答案不是列表.是长度为4的列表,哪些元素是language类型.第一个对象是symbol- list,第二个是表达式1:10等等.这解释了为什么[-1L]需要:它symbol从提供的参数中删除预期...(因为它总是一个列表).
    随着德克州的substitute回归"解析树无法评价的表达".
    当你打电话my_ellipsis_function(a=1:10,b=11:20,c=21:30),然后..."创建"的参数列表:list(a=1:10,b=11:20,c=21:30)substitute使其四个元素的列表:

    List of 4
    $  : symbol list
    $ a: language 1:10
    $ b: language 11:20
    $ c: language 21:30
    
    Run Code Online (Sandbox Code Playgroud)

    第一个元素没有名称,这是[[1]]在德克答案中.我使用以下方法获得此结果

    my_ellipsis_function <- function(...) {
      input_list <- as.list(substitute(list(...)))
      str(input_list)
      NULL
    }
    my_ellipsis_function(a=1:10,b=11:20,c=21:30)
    
    Run Code Online (Sandbox Code Playgroud)
  3. 如上所述,我们可以str用来检查函数中的对象.

    my_ellipsis_function <- function(...) {
        input_list <- list(...)
        output_list <- lapply(X=input_list, function(x) {str(x);summary(x)})
        return(output_list)
    }
    my_ellipsis_function(a=1:10,b=11:20,c=21:30)
     int [1:10] 1 2 3 4 5 6 7 8 9 10
     int [1:10] 11 12 13 14 15 16 17 18 19 20
     int [1:10] 21 22 23 24 25 26 27 28 29 30
    $a
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
       1.00    3.25    5.50    5.50    7.75   10.00 
    $b
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
       11.0    13.2    15.5    15.5    17.8    20.0 
    $c
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
       21.0    23.2    25.5    25.5    27.8    30.0 
    
    Run Code Online (Sandbox Code Playgroud)

    没关系.让我们看看substitute版本:

       my_ellipsis_function <- function(...) {
           input_list <- as.list(substitute(list(...)))
           output_list <- lapply(X=input_list, function(x) {str(x);summary(x)})
           return(output_list)
       }
       my_ellipsis_function(a=1:10,b=11:20,c=21:30)
        symbol list
        language 1:10
        language 11:20
        language 21:30
       [[1]]
       Length  Class   Mode 
            1   name   name 
       $a
       Length  Class   Mode 
            3   call   call 
       $b
       Length  Class   Mode 
            3   call   call 
       $c
       Length  Class   Mode 
            3   call   call 
    
    Run Code Online (Sandbox Code Playgroud)

    不是我们需要的.您将需要额外的技巧来处理这些类型的对象(如write.csv).

如果你想使用...那么你应该像Shane的答案一样使用它list(...).


Sha*_*ane 37

您可以将省略号转换为列表list(),然后对其执行操作:

> test.func <- function(...) { lapply(list(...), class) }
> test.func(a="b", b=1)
$a
[1] "character"

$b
[1] "numeric"
Run Code Online (Sandbox Code Playgroud)

所以你的get_list_from_ellipsis功能无非就是list.

一个有效的用例,这是在你想在一个未知的数量用于操作对象的传递情况下(如在你的例子c()data.frame())....然而,当你事先知道每个参数时,使用它并不是一个好主意,因为它会给参数字符串增加一些模糊性和进一步的复杂性(并使任何其他用户都不清楚函数签名).参数列表是功能用户的重要文档.

否则,它对于您希望将参数传递给子函数而不将它们全部暴露在您自己的函数参数中的情况也很有用.这可以在功能文档中注明.

  • 如果`list(...)`就足够了,为什么R内置函数如`data.frame`使用更长的形式`as.list(substitute(list(...)))[ - 1L]`? (4认同)
  • 由于我没有创建“data.frame”,所以我不知道答案(也就是说,我确信有一个很好的理由)。我在自己的包中使用“list()”来实现此目的,但尚未遇到问题。 (2认同)

Ric*_*ton 31

只是为了补充Shane和Dirk的回答:比较有趣

get_list_from_ellipsis1 <- function(...)
{
  list(...)
}
get_list_from_ellipsis1(a = 1:10, b = 2:20) # returns a list of integer vectors

$a
 [1]  1  2  3  4  5  6  7  8  9 10

$b
 [1]  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
Run Code Online (Sandbox Code Playgroud)

get_list_from_ellipsis2 <- function(...)
{
  as.list(substitute(list(...)))[-1L]
}
get_list_from_ellipsis2(a = 1:10, b = 2:20) # returns a list of calls

$a
1:10

$b
2:20
Run Code Online (Sandbox Code Playgroud)

就目前而言,任何一个版本似乎都适合您的目的my_ellipsis_function,但第一个版本显然更简单.


Dir*_*tel 14

你已经给出了一半答案.考虑

R> my_ellipsis_function <- function(...) {
+   input_list <- as.list(substitute(list(...)))
+ }
R> print(my_ellipsis_function(a=1:10, b=2:20))
[[1]]
list

$a
1:10

$b
11:20

R> 
Run Code Online (Sandbox Code Playgroud)

所以这需要两个参数ab从调用中将其转换为列表.那不是你要的吗?

  • 我不需要阅读索引,但我*需要密切关注您显示的命令的输出.`[[1]]`和`$ a`索引之间的区别让我觉得涉及嵌套列表.但是现在我看到你实际获得的是我想要的列表,但前面有一个额外的元素.那么`[-1L]`是有道理的.无论如何,额外的第一个元素来自哪里?有什么理由我应该使用这个而不是简单的`list(...)`? (6认同)
  • 你需要阅读索引.减号表示'排除',即'print(c(1:3)[ - 1])`将仅打印2和3.`L`是一种新的方式,以确保它最终成为一个整数,这在R源中做了很多. (3认同)
  • 不是我想要的.这实际上似乎返回了一个列表列表.注意`[[1]]`.另外,我想知道魔术咒语`as.list(substitute(list(...)))是如何工作的. (2认同)
  • 内部`list(...)`基于参数创建`list`对象.然后`substitute()`为未评估的表达式创建解析树; 请参阅此功能的帮助.以及R(或S)上的高级文本.这不是一件小事. (2认同)

Ove*_*tor 5

这按预期工作.以下是交互式会话:

> talk <- function(func, msg, ...){
+     func(msg, ...);
+ }
> talk(cat, c("this", "is", "a","message."), sep=":")
this:is:a:message.
> 
Run Code Online (Sandbox Code Playgroud)

相同,除了默认参数:

> talk <- function(func, msg=c("Hello","World!"), ...){
+     func(msg, ...);
+ }
> talk(cat,sep=":")
Hello:World!
> talk(cat,sep=",", fill=1)
Hello,
World!
>
Run Code Online (Sandbox Code Playgroud)

如您所见,如果默认值不是您在特定情况下所需的值,则可以使用此参数将"额外"参数传递给函数中的函数.