Come usare la funzione con i puntini di sospensione di R quando si scrive la propria funzione?


186

Il linguaggio R ha una caratteristica elegante per la definizione di funzioni che possono accettare un numero variabile di argomenti. Ad esempio, la funzione data.frameaccetta un numero qualsiasi di argomenti e ogni argomento diventa i dati per una colonna nella tabella di dati risultante. Esempio di utilizzo:

> 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

La firma della funzione include un puntino di sospensione, come questo:

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

Vorrei scrivere una funzione che fa qualcosa di simile, prendendo più valori e consolidandoli in un singolo valore di ritorno (oltre a fare qualche altra elaborazione). Per fare questo, ho bisogno di capire come "decomprimere" gli ...argomenti della funzione all'interno della funzione. Non so come farlo. La linea rilevante nella definizione della funzione di data.frameè object <- as.list(substitute(list(...)))[-1L], che non riesco a capire.

Quindi, come posso convertire i puntini di sospensione dalla firma della funzione in, ad esempio, un elenco?

Per essere più specifici, come posso scrivere get_list_from_ellipsisnel codice qui sotto?

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)

modificare

Sembra che ci siano due modi possibili per farlo. Sono as.list(substitute(list(...)))[-1L]e list(...). Tuttavia, questi due non fanno esattamente la stessa cosa. (Per le differenze, vedi esempi nelle risposte.) Qualcuno può dirmi qual è la differenza pratica tra loro e quale dovrei usare?

Risposte:


113

Ho letto risposte e commenti e vedo che alcune cose non sono state menzionate:

  1. data.frameusa la list(...)versione. Frammento del codice:

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

    objectè usato per fare un po 'di magia con i nomi delle colonne, ma xè usato per creare final data.frame.
    Per l'uso di ...argomenti non valutati , guarda il write.csvcodice dove match.callviene utilizzato.

  2. Mentre scrivi nei commenti, il risultato in Dirk answer non è un elenco di elenchi. È un elenco di lunghezza 4, quali elementi sono di languagetipo. Il primo oggetto è un symbol- list, il secondo è espressione 1:10e così via. Questo spiega perché [-1L]è necessario: rimuove gli symbolargomenti previsti dagli argomenti forniti in ...(perché è sempre un elenco).
    Come afferma Dirk substituterestituisce "parse tree l'espressione non valutata".
    Quando chiami my_ellipsis_function(a=1:10,b=11:20,c=21:30)quindi ..."crea" un elenco di argomenti: list(a=1:10,b=11:20,c=21:30)e substituterendilo un elenco di quattro elementi:

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

    Il primo elemento non ha un nome e questo è [[1]]nella risposta di Dirk. Ottengo questi risultati usando:

    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. Come sopra possiamo usare strper verificare quali oggetti sono presenti in una funzione.

    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 
    

    Va bene. Vediamo la substituteversione:

       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 
    

    Non è quello di cui avevamo bisogno. Avrai bisogno di ulteriori trucchi per gestire questo tipo di oggetti (come in write.csv).

Se vuoi usarlo, ...allora dovresti usarlo come nella risposta di Shane list(...).


38

È possibile convertire i puntini di sospensione in un elenco con list(), quindi eseguire le operazioni su di esso:

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

$b
[1] "numeric"

Quindi la tua get_list_from_ellipsisfunzione non è altro che list.

Un caso d'uso valido per questo è nei casi in cui si desidera passare un numero sconosciuto di oggetti per l'operazione (come nell'esempio di c()o data.frame()). Non è una buona idea usare il ...quando conosci ogni parametro in anticipo, tuttavia, poiché aggiunge qualche ambiguità e ulteriore complicazione alla stringa di argomenti (e rende la firma della funzione poco chiara per qualsiasi altro utente). L'elenco degli argomenti è una documentazione importante per gli utenti delle funzioni.

Altrimenti, è anche utile per i casi in cui si desidera passare parametri attraverso una sottofunzione senza esporli tutti nei propri argomenti di funzione. Questo può essere notato nella documentazione delle funzioni.


So di usare i puntini di sospensione come pass-through per argomenti su sottofunzioni, ma è anche pratica comune tra i primitivi R usare i puntini di sospensione come ho descritto. In effetti, entrambe le funzioni liste cfunzionano in questo modo, ma entrambe sono primitive, quindi non riesco a ispezionare facilmente il loro codice sorgente per capire come funzionano.
Ryan C. Thompson,

rbind.data.frameusare in questo modo.
Marek,

5
Se list(...)è sufficiente, perché invece i builtin R come data.frameusano la forma più lunga as.list(substitute(list(...)))[-1L]?
Ryan C. Thompson,

1
Poiché non ho creato data.frame, non conosco la risposta (detto questo, sono sicuro che ci sia una buona ragione per questo). Uso list()a questo scopo nei miei pacchetti e devo ancora riscontrare un problema.
Shane,

34

Solo per aggiungere alle risposte di Shane e Dirk: è interessante confrontare

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

con

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

Allo stato attuale, entrambe le versioni sembrano adatte ai tuoi scopi my_ellipsis_function, sebbene la prima sia chiaramente più semplice.


15

Hai già dato metà della risposta. Tener conto di

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> 

Quindi questo ha preso due argomenti ae bdalla chiamata e lo ha convertito in un elenco. Non è quello che hai chiesto?


2
Non proprio quello che voglio. Ciò sembra effettivamente restituire un elenco di elenchi. Notare il [[1]]. Inoltre, vorrei sapere come funziona l'incantesimo magico as.list(substitute(list(...))).
Ryan C. Thompson,

2
L'interno list(...)crea un listoggetto basato sugli argomenti. Quindi substitute()crea l'albero di analisi per l'espressione non valutata; vedere l'aiuto per questa funzione. Così come un buon testo avanzato su R (o S). Questa non è roba da poco.
Dirk Eddelbuettel,

Ok, per quanto riguarda la [[-1L]]parte (dalla mia domanda)? Non dovrebbe essere [[1]]?
Ryan C. Thompson,

3
Devi leggere sull'indicizzazione. Il meno significa "escludi", ovvero print(c(1:3)[-1])stampa solo 2 e 3. Il Lè un modo nuovo conio per garantire che finisce come un intero, questo è fatto molto nelle fonti R.
Dirk Eddelbuettel,

7
Non ho bisogno di leggere su indicizzazione, ma io non bisogno di prestare maggiore attenzione alla output dei comandi che mostrano. La differenza tra gli indici [[1]]e e $ami ha fatto pensare che fossero coinvolti elenchi nidificati. Ma ora vedo che quello che effettivamente ottieni è l'elenco che voglio, ma con un elemento in più nella parte anteriore. Quindi [-1L]ha senso. Da dove viene quel primo elemento in più, comunque? E c'è qualche motivo per cui dovrei usare questo invece di semplicemente list(...)?
Ryan C. Thompson,

6

Funziona come previsto. Quella che segue è una sessione interattiva:

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

Lo stesso, tranne con un argomento predefinito:

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

Come puoi vedere, puoi usarlo per passare argomenti "extra" a una funzione all'interno della tua funzione se le impostazioni predefinite non sono quelle che desideri in un caso particolare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.