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

Відповіді:


113

Я читаю відповіді та коментарі, і бачу, що мало речей не згадували:

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


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 примітивів також поширена практика використовувати еліпсис у описаному нами способі. Насправді listі cфункції, і функції працюють таким чином, але обидві - примітиви, тому я не можу легко перевірити їх вихідний код, щоб зрозуміти, як вони працюють.
Райан Ч. Томпсон

rbind.data.frameвикористовувати цей спосіб.
Марек

5
Якщо list(...)достатньо, чому R-вбудовані такі, як замість цього, data.frameвикористовують довшу форму as.list(substitute(list(...)))[-1L]?
Райан Ч. Томпсон

1
Оскільки я не створив data.frame, я не знаю відповіді на це (що сказав, я впевнений, що для цього є вагома причина). Я використовую list()для цього свої власні пакунки і ще не стикався з проблемою.
Шейн

34

Просто додати відповіді Шейна та Дірка: цікаво порівняти

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> 

Тож це взяло два аргументи aі bз виклику та перетворило його на список. Чи не про це ви просили?


2
Не зовсім те, що я хочу. Це, власне, повертає список списків. Зауважте [[1]]. Також я хотів би знати, як працює магічний заповіт as.list(substitute(list(...))).
Райан Ч. Томпсон

2
Внутрішня list(...)створює listоб’єкт на основі аргументів. Потім substitute()створює дерево розбору для неоціненого вираження; див. довідку щодо цієї функції. А також хороший розширений текст на R (або S). Це не дрібниці.
Дірк Еддельбуеттель

Гаразд, що з [[-1L]]частиною (з мого запитання)? Чи не повинно бути [[1]]?
Райан Ч. Томпсон

3
Вам потрібно прочитати про індексацію. Мінус означає «виключити», тобто print(c(1:3)[-1])буде надруковано лише 2 та 3. Це Lновий спосіб переконатися, що він закінчується цілим числом, це робиться багато в джерелах R.
Дірк Еддельбуеттель

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

Як бачите, ви можете використовувати це для передачі «зайвих» аргументів функції у межах вашої функції, якщо значення за замовчуванням не є тим, що потрібно в конкретному випадку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.