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

发布于 2024-09-05 22:55:54 字数 1265 浏览 8 评论 0原文

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.frame 函数定义中的相关行是 object <- 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(...)。然而,这两者所做的事情并不完全相同。 (有关差异,请参阅答案中的示例。)谁能告诉我它们之间的实际差异是什么,以及我应该使用哪一个?

The R language has a nifty feature for defining functions that can take a variable number of arguments. For example, the function data.frame takes any number of arguments, and each argument becomes the data for a column in the resulting data table. Example usage:

> 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

The function's signature includes an ellipsis, like this:

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

I would like to write a function that does something similar, taking multiple values and consolidating them into a single return value (as well as doing some other processing). In order to do this, I need to figure out how to "unpack" the ... from the function's arguments within the function. I don't know how to do this. The relevant line in the function definition of data.frame is object <- as.list(substitute(list(...)))[-1L], which I can't make any sense of.

So how can I convert the ellipsis from the function's signature into, for example, a list?

To be more specific, how can I write get_list_from_ellipsis in the code below?

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)

Edit

It seems there are two possible ways to do this. They are as.list(substitute(list(...)))[-1L] and list(...). However, these two do not do exactly the same thing. (For differences, see examples in the answers.) Can anyone tell me what the practical difference between them is, and which one I should use?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

萌化 2024-09-12 22:55:54

我阅读了答案和评论,发现几乎没有提到什么:

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

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

    object 用于对列名进行一些魔法,但 x 用于创建最终的data.frame。
    要使用未评估的 ... 参数,请查看使用 match.callwrite.csv 代码。

  2. 正如您在德克的评论结果中所写的那样,答案不是列表的列表。是一个长度为4的列表,其中元素是language类型。第一个对象是 symbol - list,第二个对象是表达式 1:10 等等。这解释了为什么需要 [-1L] :它从 ... 中提供的参数中删除了预期的 symbol (因为它始终是一个列表) .
    正如德克所说,substitute 返回“解析树未计算的表达式”。
    当您调用 my_ellipsis_function(a=1:10,b=11:20,c=21:30) 时,...“创建”参数列表:< code>list(a=1:10,b=11:20,c=21:30) 和 substitute 使其成为一个包含四个元素的列表:

    列表 4
    $:符号列表
    $ a: 语言 1:10
    $ b:语言 11:20
    $ c: 语言 21:30
    

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

    my_ellipsis_function <- 函数(...) {
      input_list <- as.list(替换(list(...)))
      str(输入列表)
      无效的
    }
    my_ellipsis_function(a=1:10,b=11:20,c=21:30)
    
  3. 如上所述,我们可以使用 str 来检查函数中有哪些对象。

    my_ellipsis_function <- 函数(...) {
        input_list <- 列表(...)
        输出列表 <- lapply(X=输入列表, 函数(x) {str(x);summary(x)})
        返回(输出列表)
    }
    my_ellipsis_function(a=1:10,b=11:20,c=21:30)
     整型 [1:10] 1 2 3 4 5 6 7 8 9 10
     整型 [1:10] 11 12 13 14 15 16 17 18 19 20
     整型 [1:10] 21 22 23 24 25 26 27 28 29 30
    $a
       分钟。第一曲。第三曲区中位数平均值。最大限度。 
       1.00 3.25 5.50 5.50 7.75 10.00 
    $b
       分钟。第一曲。第三曲区中位数平均值。最大限度。 
       11.0 13.2 15.5 15.5 17.8 20.0 
    $c
       分钟。第一曲。第三曲区中位数平均值。最大限度。 
       21.0 23.2 25.5 25.5 27.8 30.0 
    

    没关系。让我们看看替代版本:

     my_ellipsis_function <- function(...) {
           input_list <- as.list(替换(list(...)))
           输出列表 <- lapply(X=输入列表, 函数(x) {str(x);summary(x)})
           返回(输出列表)
       }
       my_ellipsis_function(a=1:10,b=11:20,c=21:30)
        符号列表
        语言 1:10
        语言 11:20
        语言 21:30
       [[1]]
       长度类别模式 
            1 姓名 姓名 
       $a
       长度类别模式 
            3 通话 通话 
       $b
       长度类别模式 
            3 通话 通话 
       $c
       长度类别模式 
            3 通话 通话 
    

    这不是我们需要的。您将需要额外的技巧来处理此类对象(如 write.csv 中所示)。

如果您想使用 ... 那么您应该像 Shane 答案中那样使用它,通过 list(...)

I read answers and comments and I see that few things weren't mentioned:

  1. data.frame uses list(...) version. Fragment of the code:

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

    object is used to do some magic with column names, but x is used to create final data.frame.
    For use of unevaluated ... argument look at write.csv code where match.call is used.

  2. As you write in comment result in Dirk answer is not a list of lists. Is a list of length 4, which elements are language type. First object is a symbol - list, second is expression 1:10 and so on. That explain why [-1L] is needed: it removes expected symbol from provided arguments in ... (cause it is always a list).
    As Dirk states substitute returns "parse tree the unevaluated expression".
    When you call my_ellipsis_function(a=1:10,b=11:20,c=21:30) then ... "creates" a list of arguments: list(a=1:10,b=11:20,c=21:30) and substitute make it a list of four elements:

    List of 4
    $  : symbol list
    $ a: language 1:10
    $ b: language 11:20
    $ c: language 21:30
    

    First element doesn't have a name and this is [[1]] in Dirk answer. I achieve this results using:

    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. As above we can use str to check what objects are in a function.

    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 
    

    It's ok. Lets see substitute version:

       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 
    

    Isn't what we needed. You will need additional tricks to deal with these kind of objects (as in write.csv).

If you want use ... then you should use it as in Shane answer, by list(...).

我最亲爱的 2024-09-12 22:55:54

您可以使用 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())。然而,当您提前知道每个参数时,使用 ... 并不是一个好主意,因为它会增加参数字符串的歧义性和进一步的复杂性(并使函数签名对于任何其他参数都不清楚)用户)。对于函数用户来说,参数列表是一个重要的文档。

否则,当您想要将参数传递给子函数而不将它们全部暴露在您自己的函数参数中时,它也很有用。这可以在函数文档中注明。

You can convert the ellipsis into a list with list(), and then perform your operations on it:

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

$b
[1] "numeric"

So your get_list_from_ellipsis function is nothing more than list.

A valid use case for this is in cases where you want to pass in an unknown number of objects for operation (as in your example of c() or data.frame()). It's not a good idea to use the ... when you know each parameter in advance, however, as it adds some ambiguity and further complication to the argument string (and makes the function signature unclear to any other user). The argument list is an important piece of documentation for function users.

Otherwise, it is also useful for cases when you want to pass through parameters to a subfunction without exposing them all in your own function arguments. This can be noted in the function documentation.

山有枢 2024-09-12 22:55:54

只是补充一下 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 中的目的,尽管第一个版本显然更简单。

Just to add to Shane and Dirk's responses: it is interesting to compare

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

with

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

As it stands, either version appears suitable for your purposes in my_ellipsis_function, though the first is clearly simpler.

痞味浪人 2024-09-12 22:55:54

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

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 并将其转换为列表。这不是你要求的吗?

You gave half the answer already. Consider

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> 

So this took two arguments a and b from the call and converted it to a list. Wasn't that what you asked for?

舟遥客 2024-09-12 22:55:54

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

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

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

This works as expected.
The following is an interactive session:

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

Same, except with a default argument:

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

As you can see, you can use this to pass 'extra' arguments to a function within your function if the defaults are not what you want in a particular case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文