web-dev-qa-db-fra.com

Comment utiliser la fonction Ellipsis de R lorsque vous écrivez votre propre fonction?

Le langage R possède une fonctionnalité astucieuse pour définir des fonctions pouvant prendre un nombre variable d'arguments. Par exemple, la fonction data.frame Accepte un nombre quelconque d'arguments et chaque argument devient la donnée d'une colonne dans la table de données résultante. Exemple d'utilisation:

> 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 signature de la fonction inclut un Ellipsis, comme ceci:

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

Je voudrais écrire une fonction qui fait quelque chose de similaire, prenant plusieurs valeurs et les consolidant dans une seule valeur de retour (ainsi que d'autres traitements). Pour ce faire, je dois trouver comment "décompresser" le ... À partir des arguments de la fonction dans la fonction. Je ne sais pas comment faire ça. La ligne pertinente dans la définition de fonction de data.frame Est object <- as.list(substitute(list(...)))[-1L], ce que je ne peux en comprendre le sens.

Alors, comment puis-je convertir les Ellipsis de la signature de la fonction dans, par exemple, une liste?

Pour être plus précis, comment puis-je écrire get_list_from_Ellipsis Dans le code ci-dessous?

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)

Modifier

Il semble qu'il y ait deux façons possibles de le faire. Ils sont as.list(substitute(list(...)))[-1L] et list(...). Cependant, ces deux ne font pas exactement la même chose. (Pour les différences, voir les exemples dans les réponses.) Quelqu'un peut-il me dire quelle est la différence pratique entre elles et laquelle je devrais utiliser?

174
Ryan Thompson

Je lis les réponses et les commentaires et je vois que peu de choses ne sont pas mentionnées:

  1. data.frame Utilise list(...) version. Fragment du code:

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

    object est utilisé pour faire de la magie avec les noms de colonnes, mais x est utilisé pour créer la dernière data.frame.
    Pour utiliser l'argument non évalué ..., Regardez le code write.csv Dans lequel match.call Est utilisé.

  2. Lorsque vous écrivez dans le résultat d'un commentaire dans Dirk, la réponse n'est pas une liste de listes. Est une liste de longueur 4, dont les éléments sont de type language. Le premier objet est un symbol - list, le second est l'expression 1:10 Et ainsi de suite. Cela explique pourquoi [-1L] Est nécessaire: cela supprime symbol attendu des arguments fournis dans ... (Car il s'agit toujours d'une liste).
    Comme Dirk l'indique, substitute renvoie "analyser l'arbre sans expression".
    Lorsque vous appelez my_Ellipsis_function(a=1:10,b=11:20,c=21:30), alors ... "Crée" une liste d'arguments: list(a=1:10,b=11:20,c=21:30) et substitute en faites une liste de quatre éléments:

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

    Le premier élément n'a pas de nom et il s'agit de [[1]] Dans la réponse Dirk. J'obtiens ce résultat en utilisant:

    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. Comme ci-dessus, nous pouvons utiliser str pour vérifier quels objets sont dans une fonction.

    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 
    

    C'est bon. Voyons 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 
    

    N'est-ce pas ce dont nous avions besoin. Vous aurez besoin de nouvelles astuces pour gérer ce type d’objets (comme dans write.csv).

Si vous voulez utiliser ..., Utilisez-le comme dans Shane answer, par list(...).

109
Marek

Vous pouvez convertir les Ellipsis en une liste avec list(), puis effectuez vos opérations dessus:

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

$b
[1] "numeric"

Donc, votre fonction get_list_from_Ellipsis N’est rien d’autre que list.

Un cas d'utilisation valide pour cela est dans les cas où vous souhaitez transmettre un nombre inconnu d'objets à l'opération (comme dans votre exemple de c() ou data.frame()). Ce n'est pas une bonne idée d'utiliser le ... Lorsque vous connaissez chaque paramètre à l'avance, car cela ajoute une ambiguïté et une complication supplémentaire à la chaîne d'argument (et rend la signature de fonction peu claire pour tout autre utilisateur). La liste des arguments est une documentation importante pour les utilisateurs de fonctions.

Sinon, il est également utile dans les cas où vous souhaitez transmettre des paramètres à une sous-fonction sans les exposer tous dans vos propres arguments de fonction. Ceci peut être noté dans la documentation de la fonction.

37
Shane

Pour ajouter aux réponses de Shane et Dirk: il est intéressant de comparer

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

avec

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

En l'état, l'une ou l'autre version semble convenir à vos besoins dans my_Ellipsis_function, bien que le premier soit clairement plus simple.

31
Richie Cotton

Vous avez déjà donné la moitié de la réponse. Considérer

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> 

Donc, cela a pris deux arguments a et b de l'appel et l'a converti en liste. N'était-ce pas ce que vous aviez demandé?

14
Dirk Eddelbuettel

Cela fonctionne comme prévu. Ce qui suit est une session interactive:

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

Idem, sauf avec un argument par défaut:

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

Comme vous pouvez le constater, vous pouvez utiliser ceci pour transmettre des arguments "supplémentaires" à une fonction de votre fonction si les valeurs par défaut ne correspondent pas à ce que vous souhaitez dans un cas particulier.

5