Как использовать функцию многоточия R при написании своей собственной функции?
Язык 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(...)
, Однако эти двое не делают одно и то же. (Различия см. В примерах ответов.) Может кто-нибудь сказать мне, какова практическая разница между ними, и какую мне следует использовать?
6 ответов
Я читаю ответы и комментарии и вижу, что мало что не было упомянуто:
data.frame
использованияlist(...)
версия. Фрагмент кода:object <- as.list(substitute(list(...)))[-1L] mrn <- is.null(row.names) x <- list(...)
object
используется для создания магии с именами столбцов, ноx
используется для создания финалаdata.frame
,
Для использования неоцененных...
аргумент посмотри наwrite.csv
код гдеmatch.call
используется.По мере того как вы пишете в комментарии результат в ответе Дирка не является списком списков. Это список длиной 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
Первый элемент не имеет имени, и это
[[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)
Как указано выше, мы можем использовать
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
).
Если вы хотите использовать ...
тогда вы должны использовать его, как в ответе Шейна, list(...)
,
Вы можете преобразовать многоточие в список с помощью 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()
). Это не очень хорошая идея, чтобы использовать ...
однако, если вы заранее знаете каждый параметр, это добавляет некоторую двусмысленность и дальнейшее усложнение к строке аргумента (и делает подпись функции неясной для любого другого пользователя). Список аргументов является важной частью документации для пользователей функций.
В противном случае это также полезно для случаев, когда вы хотите передать параметры подфункции, не раскрывая их все в своих аргументах функции. Это можно отметить в документации по функции.
Просто добавить к ответам Шейна и Дирка: интересно сравнить
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
Правда, первое явно проще.
Вы уже дали половину ответа. Рассматривать
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>
Так что это заняло два аргумента a
а также b
от звонка и преобразовал его в список. Разве это не то, что вы просили?
Это работает как ожидалось. Ниже приведен интерактивный сеанс:
> 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!
>
Как вы можете видеть, вы можете использовать это для передачи "дополнительных" аргументов функции внутри вашей функции, если значения по умолчанию не те, которые вы хотите в конкретном случае.
Я думаю, что это то, что вы ожидали (следующий рис). a1,a2,a3,a4 - некоторые произвольные векторы, а 'catt' - это функция, которая принимает любое количество аргументов и возвращает объединенный вектор входных аргументов.