问题
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?
回答1:
I read answers and comments and I see that few things weren't mentioned:
data.frame
useslist(...)
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, butx
is used to create finaldata.frame
.
For use of unevaluated...
argument look atwrite.csv
code wherematch.call
is used.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 asymbol
-list
, second is expression1:10
and so on. That explain why[-1L]
is needed: it removes expectedsymbol
from provided arguments in...
(cause it is always a list).
As Dirk statessubstitute
returns "parse tree the unevaluated expression".
When you callmy_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)
andsubstitute
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)
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(...)
.
回答2:
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.
回答3:
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.
回答4:
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?
回答5:
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.
来源:https://stackoverflow.com/questions/3057341/how-to-use-rs-ellipsis-feature-when-writing-your-own-function