Comment attribuer à partir d'une fonction qui renvoie plus d'une valeur?


223

Toujours en train d'entrer dans la logique R ... quelle est la "meilleure" façon de décompresser (sur LHS) les résultats d'une fonction renvoyant plusieurs valeurs?

Je ne peux pas faire ça apparemment:

R> functionReturningTwoValues <- function() { return(c(1, 2)) }
R> functionReturningTwoValues()
[1] 1 2
R> a, b <- functionReturningTwoValues()
Error: unexpected ',' in "a,"
R> c(a, b) <- functionReturningTwoValues()
Error in c(a, b) <- functionReturningTwoValues() : object 'a' not found

dois-je vraiment faire ce qui suit?

R> r <- functionReturningTwoValues()
R> a <- r[1]; b <- r[2]

ou le programmeur R écrirait-il quelque chose de plus comme ceci:

R> functionReturningTwoValues <- function() {return(list(first=1, second=2))}
R> r <- functionReturningTwoValues()
R> r$first
[1] 1
R> r$second
[1] 2

--- modifié pour répondre aux questions de Shane ---

Je n'ai pas vraiment besoin de donner de noms aux parties de valeur de résultat. J'applique une fonction d'agrégation au premier composant et une autre au deuxième composant ( minet maxsi c'était la même fonction pour les deux composants, je n'aurais pas besoin de les diviser).


7
Pour info, une autre façon de renvoyer plusieurs valeurs est de définir un attrsur votre valeur de retour.
Jonathan Chang

C'est l'équivalent du déballage de tuple de Python.
smci

Réponses:


186

(1) list [...] <- Je l'avais posté il y a plus d'une décennie sur r-help . Depuis lors, il a été ajouté au package gsubfn. Il ne nécessite pas d'opérateur spécial mais nécessite que le côté gauche soit écrit en utilisant list[...]comme ceci:

library(gsubfn)  # need 0.7-0 or later
list[a, b] <- functionReturningTwoValues()

Si vous n'avez besoin que du premier ou du deuxième composant, tous fonctionnent également:

list[a] <- functionReturningTwoValues()
list[a, ] <- functionReturningTwoValues()
list[, b] <- functionReturningTwoValues()

(Bien sûr, si vous n'aviez besoin que d'une seule valeur, alors functionReturningTwoValues()[[1]]ou functionReturningTwoValues()[[2]]serait suffisant.)

Voir le fil r-help cité pour plus d'exemples.

(2) avec Si l'intention est simplement de combiner les valeurs multiples par la suite et que les valeurs de retour sont nommées, une alternative simple consiste à utiliser with:

myfun <- function() list(a = 1, b = 2)

list[a, b] <- myfun()
a + b

# same
with(myfun(), a + b)

(3) attacher Une autre alternative est attacher:

attach(myfun())
a + b

AJOUTÉ: withetattach


25
J'ai accepté votre réponse à cause du "avec", mais je ne peux pas reproduire ce que vous décrivez pour l'utilisation à gauche de la "liste", tout ce que je reçois est "objet" un "introuvable"
mariotomo

4
Ça marche pour moi. Qu'as-tu essayé? Avez-vous lu le message lié et l'avez-vous suivi? Avez-vous défini listet [<-.resultcomme indiqué ici?
G. Grothendieck du

12
@ G.Grothendieck, cela vous dérangerait si je mettais le contenu de votre lien dans votre réponse? Je pense qu'il serait plus facile pour les gens de l'utiliser.
merlin2011

12
Je suis d'accord avec @ merlin2011; telle qu'elle est écrite, il semble que cette syntaxe soit intégrée dans la base R.
knowah

6
@ G.Grothendieck Je suis d'accord avec merlin2011 et knowah - il serait préférable que le code réel qui est important ici (le code référencé dans le lien) soit dans la réponse. Ce n'est peut-être pas une mauvaise idée de mentionner que l'objet résultat n'a pas besoin d'être nommé liste. Cela m'a un peu dérouté avant de lire votre code actuel. Comme mentionné, la réponse indique que vous devez exécuter le code dans le lien, mais la plupart des gens ne liront pas ce code immédiatement, sauf s'il est directement dans la réponse - cela donne l'impression que cette syntaxe est dans la base R.
Dason

68

J'ai en quelque sorte trébuché sur ce hack intelligent sur Internet ... Je ne sais pas si c'est méchant ou beau, mais cela vous permet de créer un opérateur "magique" qui vous permet de décompresser plusieurs valeurs de retour dans leur propre variable. La :=fonction est définie ici et incluse ci-dessous pour la postérité:

':=' <- function(lhs, rhs) {
  frame <- parent.frame()
  lhs <- as.list(substitute(lhs))
  if (length(lhs) > 1)
    lhs <- lhs[-1]
  if (length(lhs) == 1) {
    do.call(`=`, list(lhs[[1]], rhs), envir=frame)
    return(invisible(NULL)) 
  }
  if (is.function(rhs) || is(rhs, 'formula'))
    rhs <- list(rhs)
  if (length(lhs) > length(rhs))
    rhs <- c(rhs, rep(list(NULL), length(lhs) - length(rhs)))
  for (i in 1:length(lhs))
    do.call(`=`, list(lhs[[i]], rhs[[i]]), envir=frame)
  return(invisible(NULL)) 
}

Avec cela en main, vous pouvez faire ce que vous recherchez:

functionReturningTwoValues <- function() {
  return(list(1, matrix(0, 2, 2)))
}
c(a, b) := functionReturningTwoValues()
a
#[1] 1
b
#     [,1] [,2]
# [1,]    0    0
# [2,]    0    0

Je ne sais pas ce que je ressens à ce sujet. Peut-être le trouverez-vous utile dans votre espace de travail interactif. L'utiliser pour construire des bibliothèques (réutilisables) (pour la consommation de masse) n'est peut-être pas la meilleure idée, mais je suppose que cela dépend de vous.

... vous savez ce qu'ils disent sur la responsabilité et le pouvoir ...


12
De plus, je la découragerais beaucoup plus maintenant que lorsque j'ai initialement publié cette réponse, car le package data.table utilise :=beaucoup l' opérateur de manière beaucoup plus pratique :-)
Steve Lianoglou

47

Habituellement, j'emballe la sortie dans une liste, qui est très flexible (vous pouvez avoir n'importe quelle combinaison de nombres, chaînes, vecteurs, matrices, tableaux, listes, objets dans la sortie)

donc comme:

func2<-function(input) {
   a<-input+1
   b<-input+2
   output<-list(a,b)
   return(output)
}

output<-func2(5)

for (i in output) {
   print(i)
}

[1] 6
[1] 7

Et si au lieu de la sortie <-func2 (5) je veux avoir le résultat dans deux objets? J'ai essayé avec list ("a", "b") <-func2 (5) mais cela ne fonctionne pas.
skan

13
functionReturningTwoValues <- function() { 
  results <- list()
  results$first <- 1
  results$second <-2
  return(results) 
}
a <- functionReturningTwoValues()

Je pense que cela fonctionne.


11

J'ai mis en place un package R zeallot pour résoudre ce problème. zeallot comprend une affectation multiple ou opérateur d'affectation décompactage, %<-%. Le LHS de l'opérateur est un nombre quelconque de variables à affecter, construit à l'aide d'appels à c(). Le RHS de l'opérateur est un vecteur, une liste, un bloc de données, un objet de date ou tout autre objet personnalisé avec une destructureméthode implémentée (voir ?zeallot::destructure).

Voici quelques exemples basés sur le message d'origine,

library(zeallot)

functionReturningTwoValues <- function() { 
  return(c(1, 2)) 
}

c(a, b) %<-% functionReturningTwoValues()
a  # 1
b  # 2

functionReturningListOfValues <- function() {
  return(list(1, 2, 3))
}

c(d, e, f) %<-% functionReturningListOfValues()
d  # 1
e  # 2
f  # 3

functionReturningNestedList <- function() {
  return(list(1, list(2, 3)))
}

c(f, c(g, h)) %<-% functionReturningNestedList()
f  # 1
g  # 2
h  # 3

functionReturningTooManyValues <- function() {
  return(as.list(1:20))
}

c(i, j, ...rest) %<-% functionReturningTooManyValues()
i     # 1
j     # 2
rest  # list(3, 4, 5, ..)

Consultez la vignette du package pour plus d'informations et d'exemples.


Existe-t-il une syntaxe spéciale pour stocker plusieurs tracés en tant que sorties à l'aide de cette méthode?
mrpargeter

2
Aucune syntaxe particulière n'est requise, vous pouvez affecter une liste d'objets de tracé comme vous le feriez pour une liste de nombres.
nteetor

10

Il n'y a pas de bonne réponse à cette question. Je dépend vraiment de ce que vous faites avec les données. Dans l'exemple simple ci-dessus, je suggère fortement:

  1. Gardez les choses aussi simples que possible.
  2. Dans la mesure du possible, il est recommandé de garder vos fonctions vectorisées. Cela offre la plus grande flexibilité et vitesse à long terme.

Est-il important que les valeurs 1 et 2 ci-dessus aient des noms? En d'autres termes, pourquoi est-il important dans cet exemple que 1 et 2 soient nommés a et b, plutôt que simplement r [1] et r [2]? Une chose importante à comprendre dans ce contexte est que a et b sont également des vecteurs de longueur 1. Donc, vous ne changez vraiment rien dans le processus de cette affectation, à part avoir 2 nouveaux vecteurs qui n'ont pas besoin d'indices pour être référencé:

> r <- c(1,2)
> a <- r[1]
> b <- r[2]
> class(r)
[1] "numeric"
> class(a)
[1] "numeric"
> a
[1] 1
> a[1]
[1] 1

Vous pouvez également attribuer les noms au vecteur d'origine si vous préférez référencer la lettre que l'index:

> names(r) <- c("a","b")
> names(r)
[1] "a" "b"
> r["a"]
a 
1 

[Modifier] Étant donné que vous appliquerez les valeurs min et max à chaque vecteur séparément, je suggérerais d'utiliser soit une matrice (si a et b auront la même longueur et le même type de données) ou un cadre de données (si a et b seront la même longueur mais peuvent être de types de données différents) ou bien utilisez une liste comme dans votre dernier exemple (si elles peuvent être de longueurs et de types de données différents).

> r <- data.frame(a=1:4, b=5:8)
> r
  a b
1 1 5
2 2 6
3 3 7
4 4 8
> min(r$a)
[1] 1
> max(r$b)
[1] 8

édité la question afin d'inclure vos remarques. Merci. donner des noms à des choses comme r[1]peut aider à rendre les choses plus claires (d'accord, pas si des noms comme aviennent à leur place).
mariotomo

5

Les listes semblent parfaites à cet effet. Par exemple, dans la fonction que vous auriez

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

programme principal

x = returnlist[[1]]

y = returnlist[[2]]

4
Comment pouvez-vous affecter les deux variables dans une seule commande, comme list ("x", "y") <-returnlist ()? Je dis cela parce que si vous avez plusieurs éléments dans la liste, vous devrez exécuter la fonction entière plusieurs fois et cela coûte un certain temps.
skan

4

Oui à vos deuxième et troisième questions - c'est ce que vous devez faire car vous ne pouvez pas avoir plusieurs «valeurs» à gauche d'une affectation.


3

Que diriez-vous d'utiliser assign?

functionReturningTwoValues <- function(a, b) {
  assign(a, 1, pos=1)
  assign(b, 2, pos=1)
}

Vous pouvez transmettre les noms de la variable que vous souhaitez transmettre par référence.

> functionReturningTwoValues('a', 'b')
> a
[1] 1
> b
[1] 2

Si vous devez accéder aux valeurs existantes, l'inverse de assignest get.


... mais cela vous oblige à connaître les noms des variables de réception dans cet environnement
smci

@smci Oui. C'est pourquoi la méthode de "liste nommée" dans la question est généralement meilleure: r <- function() { return(list(first=1, second=2)) }et référencez les résultats en utilisant r$firstet r$second.
Steve Pitchers

2
Une fois que vous avez votre fonction, comment pouvez-vous affecter les deux variables dans une seule commande, telles que list ("x", "y") <- functionReturningTwoValues ​​('a', 'b')? Je dis cela parce que si vous avez de nombreux éléments dans la liste, vous devrez exécuter la fonction entière plusieurs fois et cela coûte un certain temps
skan

3

Si vous souhaitez renvoyer la sortie de votre fonction dans l'environnement global, vous pouvez utiliser list2env, comme dans cet exemple:

myfun <- function(x) { a <- 1:x
                       b <- 5:x
                       df <- data.frame(a=a, b=b)

                       newList <- list("my_obj1" = a, "my_obj2" = b, "myDF"=df)
                       list2env(newList ,.GlobalEnv)
                       }
    myfun(3)

Cette fonction créera trois objets dans votre environnement global:

> my_obj1
  [1] 1 2 3

> my_obj2
  [1] 5 4 3

> myDF
    a b
  1 1 5
  2 2 4
  3 3 3

1

[A] Si chacun de foo et bar est un nombre unique, alors il n'y a rien de mal avec c (foo, bar); et vous pouvez également nommer les composants: c (Foo = foo, Bar = bar). Ainsi, vous pouvez accéder aux composants du résultat «res» en tant que res [1], res [2]; ou, dans le cas indiqué, comme res ["Foo"], res ["BAR"].

[B] Si foo et bar sont des vecteurs du même type et de la même longueur, alors là encore, il n'y a rien de mal à retourner cbind (foo, bar) ou rbind (foo, bar); également nommable. Dans le cas 'cbind', vous accéderez à foo et bar en tant que res [, 1], res [, 2] ou res [, "Foo"], res [, "Bar"]. Vous pouvez également préférer renvoyer une trame de données plutôt qu'une matrice:

data.frame(Foo=foo,Bar=bar)

et accédez-y en tant que res $ Foo, res $ Bar. Cela fonctionnerait aussi bien si foo et bar étaient de la même longueur mais pas du même type (par exemple foo est un vecteur de nombres, bar un vecteur de chaînes de caractères).

[C] Si foo et bar sont suffisamment différents pour ne pas se combiner comme ci-dessus, alors vous devriez certainement retourner une liste.

Par exemple, votre fonction peut s'adapter à un modèle linéaire et également calculer des valeurs prédites, vous pouvez donc avoir

LM<-lm(....) ; foo<-summary(LM); bar<-LM$fit

et ensuite vous return list(Foo=foo,Bar=bar)accéderiez au résumé en tant que res $ Foo, les valeurs prédites en tant que res $ Bar

source: http://r.789695.n4.nabble.com/How-to-return-multiple-values-in-a-function-td858528.html


-1

Pour obtenir plusieurs sorties d'une fonction et les conserver dans le format souhaité, vous pouvez enregistrer les sorties sur votre disque dur (dans le répertoire de travail) depuis la fonction, puis les charger depuis l'extérieur de la fonction:

myfun <- function(x) {
                      df1 <- ...
                      df2 <- ...
                      save(df1, file = "myfile1")
                      save(df2, file = "myfile2")
}
load("myfile1")
load("myfile2")

-1

Avec R 3.6.1, je peux faire ce qui suit

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.