Как использовать функцию многоточия в 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(...). Однако эти двое не делают одно и то же. (Различия см. В примерах в ответах.) Может ли кто-нибудь сказать мне, в чем практическая разница между ними и какую из них я должен использовать?


person Ryan C. Thompson    schedule 16.06.2010    source источник


Ответы (5)


Я читал ответы и комментарии и вижу, что некоторые вещи не были упомянуты:

  1. data.frame использует list(...) версию. Фрагмент кода:

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

    object используется для волшебства с именами столбцов, но x используется для создания окончательного data.frame.
    Для использования неоцененного ... аргумента посмотрите код write.csv, где используется match.call.

  2. Как вы пишете в комментариях, результат в ответе Дирка не является списком списков. Это список длиной 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)
    
  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).

Если вы хотите использовать ..., вам следует использовать его, как в ответе Шейна, list(...).

person Marek    schedule 21.06.2010

Вы можете преобразовать многоточие в список с помощью 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()). Однако не рекомендуется использовать ..., если вы знаете каждый параметр заранее, поскольку он добавляет некоторую двусмысленность и дальнейшее усложнение строке аргумента (и делает сигнатуру функции неясной для любого другого пользователя). Список аргументов - важная часть документации для пользователей функций.

В противном случае это также полезно в тех случаях, когда вы хотите передать параметры в подфункцию, не раскрывая их все в своих собственных аргументах функции. Это можно отметить в документации по функциям.

person Shane    schedule 16.06.2010
comment
Я знаю об использовании многоточия для передачи аргументов подфункциям, но среди примитивов R также распространена практика использования многоточия, как я описал. Фактически, обе функции list и c работают таким образом, но обе являются примитивами, поэтому я не могу легко проверить их исходный код, чтобы понять, как они работают. - person Ryan C. Thompson; 17.06.2010
comment
rbind.data.frame используйте этот способ. - person Marek; 18.06.2010
comment
Если list(...) достаточно, почему встроенные функции R, такие как data.frame, используют вместо этого более длинную форму as.list(substitute(list(...)))[-1L]? - person Ryan C. Thompson; 18.06.2010
comment
Поскольку я не создавал data.frame, я не знаю ответа на этот вопрос (при этом я уверен, что для этого есть веская причина). Я использую list() для этой цели в своих собственных пакетах и ​​пока не сталкивался с проблемой. - person Shane; 18.06.2010

Просто добавлю к ответам Шейна и Дирка: интересно сравнить

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, хотя первая явно проще.

person Richie Cotton    schedule 17.06.2010

Вы уже дали половину ответа. Рассмотреть возможность

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 из вызова и преобразовал их в список. Разве вы не об этом просили?

person Dirk Eddelbuettel    schedule 16.06.2010
comment
Не совсем то, что я хочу. На самом деле это возвращает список списков. Обратите внимание на [[1]]. Также я хотел бы знать, как работает магическое заклинание as.list(substitute(list(...))). - person Ryan C. Thompson; 17.06.2010
comment
Внутренний list(...) создает объект list на основе аргументов. Затем substitute() создает дерево синтаксического анализа для неоцененного выражения; см. справку по этой функции. А также хороший продвинутый текст на R (или S). Это нетривиальная вещь. - person Dirk Eddelbuettel; 17.06.2010
comment
Хорошо, а как насчет части [[-1L]] (из моего вопроса)? Разве это не должно быть [[1]]? - person Ryan C. Thompson; 17.06.2010
comment
Вам нужно прочитать об индексировании. Минус означает «исключить», т.е. print(c(1:3)[-1]) будет печатать только 2 и 3. L - это новомодный способ убедиться, что он заканчивается целым числом, это часто делается в источниках R. - person Dirk Eddelbuettel; 17.06.2010
comment
Мне не нужно читать об индексировании, но мне действительно нужно уделять больше внимания выводам команд, которые вы показываете. Разница между индексами [[1]] и $a заставила меня подумать, что речь идет о вложенных списках. Но теперь я вижу, что вы действительно получаете список, который мне нужен, но с дополнительным элементом впереди. Итак, [-1L] имеет смысл. Откуда вообще берется этот лишний первый элемент? И есть ли причина, по которой я должен использовать это вместо простого list(...)? - person Ryan C. Thompson; 18.06.2010

Это работает, как ожидалось. Ниже приводится интерактивный сеанс:

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

Как видите, вы можете использовать это для передачи «дополнительных» аргументов функции внутри вашей функции, если значения по умолчанию не соответствуют вашим требованиям в конкретном случае.

person Overloaded_Operator    schedule 09.05.2014