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


186

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

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

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

我想编写一个执行类似操作的函数,将多个值合并到单个返回值中(以及进行其他处理)。为了做到这一点,我需要弄清楚如何...从函数内的函数参数中“解包” 。我不知道该怎么做。函数定义中的相关行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)

编辑

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

Answers:


113

我读了答案和评论,发现没有提到几件事:

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

    object <- as.list(substitute(list(...)))[-1L]
    mrn <- is.null(row.names)
    x <- list(...)

    object用于对列名进行魔术处理,但x用于创建final data.frame
    要使用未评估的...参数,请查看在write.csv何处match.call使用了代码。

  2. 当您在Dirk中写评论结果时,答案不是列表列表。是长度为4的列表,其中元素是language类型。第一个对象是symbol- list,第二个对象是表达式1:10,依此类推。这就说明了为什么[-1L]需要它:它symbol从提供的参数中删除了期望的内容...(因为它始终是一个列表)。
    由于Dirk的状态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

    第一个元素没有名称,这[[1]]在Dirk答案中。我使用以下方法实现了此结果:

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

    没关系。让我们看看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 

    这不是我们所需要的。您将需要其他技巧来处理这类对象(如所述write.csv)。

如果要使用,...则应按Shane答案中的那样使用它list(...)


38

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

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

$b
[1] "numeric"

因此,您的get_list_from_ellipsis功能不过是list

一个有效的用例是在您要传递未知数量的对象进行操作的情况下(如您的c()或示例data.frame())。但是,...当您事先知道每个参数时,最好不要使用,因为它会给参数字符串增加一些歧义和进一步的复杂性(并使其他用户不清楚函数的签名)。参数列表是函数用户的重要文档。

否则,对于想要将参数传递给子函数而不在自己的函数参数中全部公开的情况下,它也很有用。可以在功能文档中进行说明。


我知道使用省略号作为子函数自变量的传递方式,但是R原语中的惯常做法是以我所描述的方式使用省略号。实际上,listc函数都以这种方式工作,但是两者都是原语,因此我无法轻松地检查它们的源代码以了解它们如何工作。
瑞安·汤普森

rbind.data.frame用这种方式。
Marek 2010年

5
如果list(...)足够,为什么data.frame要用更长的格式as.list(substitute(list(...)))[-1L]代替R内置函数?
瑞安·汤普森

1
由于我没有创造data.frame,我不知道这个问题的答案(这么说,我敢肯定,有一个很好的理由)。我list()在自己的程序包中用于此目的,但尚未遇到问题。
Shane 2010年

34

只是为了补充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

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

就目前而言my_ellipsis_function,尽管第一个版本显然更简单,但两个版本似乎都适合您的目的。


15

您已经给出了一半的答案。考虑

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> 

因此,这从调用中获取了两个参数ab并将其转换为列表。那不是你要的吗?


2
不完全是我想要的。这实际上似乎是返回列表列表。请注意[[1]]。另外,我想知道魔咒是如何as.list(substitute(list(...)))工作的。
瑞安·汤普森

2
内部根据参数list(...)创建list对象。然后substitute()为未计算的表达式创建分析树;请参阅此功能的帮助。以及有关R(或S)的高级文字。这不是小事。
Dirk Eddelbuettel

好的,那[[-1L]]部分呢(根据我的问题)?不是[[1]]吗?
瑞安·汤普森

3
您需要阅读索引。减号表示“排除”,即print(c(1:3)[-1])仅打印2和3。这L是一种确保它以整数形式结束的新方法,这在R源代码中做了很多。
Dirk Eddelbuettel

7
我并不需要在索引读了,但我需要密切注意的是您展示命令的输出。[[1]]$a索引之间的区别使我认为涉及嵌套列表。但是现在我看到您实际得到的是我想要的列表,但前面有一个额外的元素。因此,[-1L]这才有意义。无论如何,多余的第一要素从何而来?我有什么理由而不是简单地使用它list(...)吗?
瑞安·汤普森

6

这按预期工作。以下是一个交互式会话:

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

相同,除了默认参数:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.