Wie ordne ich eine Funktion zu, die mehr als einen Wert zurückgibt?

223

Versuchen Sie immer noch, in die R-Logik einzusteigen ... Was ist der "beste" Weg, um (auf LHS) die Ergebnisse einer Funktion zu entpacken, die mehrere Werte zurückgibt?

Ich kann das anscheinend nicht machen:

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

muss ich wirklich folgendes tun?

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

oder würde der R-Programmierer so etwas schreiben:

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

--- bearbeitet, um Shane's Fragen zu beantworten ---

Ich muss den Ergebniswertteilen nicht wirklich Namen geben. Ich wende eine Aggregatfunktion auf die erste Komponente und eine andere auf die zweite Komponente an ( minund maxwenn es für beide Komponenten dieselbe Funktion wäre, müsste ich sie nicht aufteilen).

Mariotomo
quelle
7
Zu attrIhrer Information, eine andere Möglichkeit, mehrere Werte zurückzugeben, besteht darin, einen Rückgabewert festzulegen.
Jonathan Chang
Dies entspricht dem Tupel-Entpacken von Python.
smci

Antworten:

186

(1) Liste [...] <- Ich hatte dies vor über einem Jahrzehnt auf r-help gepostet . Seitdem wurde es dem gsubfn-Paket hinzugefügt. Es ist kein spezieller Operator erforderlich, aber es ist erforderlich, dass die linke Seite folgendermaßen geschrieben wird list[...]:

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

Wenn Sie nur die erste oder zweite Komponente benötigen, funktionieren diese ebenfalls:

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

(Natürlich, wenn Sie nur einen Wert benötigen, dann functionReturningTwoValues()[[1]]oder functionReturningTwoValues()[[2]]wäre ausreichend.)

Weitere Beispiele finden Sie im zitierten R-Help-Thread.

(2) mit Wenn die Absicht besteht, lediglich die mehreren Werte anschließend zu kombinieren und die Rückgabewerte benannt werden, besteht eine einfache Alternative darin, Folgendes zu verwenden with:

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

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

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

(3) Anhängen Eine andere Alternative ist Anhängen:

attach(myfun())
a + b

HINZUGEFÜGT: withundattach

G. Grothendieck
quelle
25
Ich habe Ihre Antwort wegen des "mit" akzeptiert, aber ich kann nicht reproduzieren, was Sie für die Verwendung von "Liste" auf der linken Seite beschreiben. Alles, was ich bekomme, ist "Objekt" ein "nicht gefunden"
Mariotomo
4
Für mich geht das. Was hast du versucht? Hast du den verlinkten Beitrag gelesen und bist ihm gefolgt? Haben Sie definiert listund [<-.resultwie dort gezeigt?
G. Grothendieck
12
@ G.Grothendieck, Würde es Ihnen etwas ausmachen, wenn ich den Inhalt Ihres Links in Ihre Antwort einfügen würde? Ich denke, es würde es den Leuten leichter machen, es zu benutzen.
Merlin2011
12
Ich stimme @ merlin2011 zu; Wie geschrieben scheint diese Syntax in R base eingebettet zu sein.
Knowah
6
@ G.Grothendieck Ich stimme merlin2011 und knowah zu - es wäre am besten, wenn der tatsächliche Code, der hier wichtig ist (der Code, auf den im Link verwiesen wird), in der Antwort enthalten ist. Es ist möglicherweise keine schlechte Idee zu erwähnen, dass das Ergebnisobjekt nicht als Liste bezeichnet werden muss. Das hat mich eine Weile verwirrt, bevor ich Ihren eigentlichen Code gelesen habe. Wie bereits erwähnt, heißt es in der Antwort, dass Sie den Code im Link ausführen müssen, aber die meisten Leute werden diesen Code nicht sofort lesen, es sei denn, er steht direkt in der Antwort - dies erweckt den Eindruck, dass sich diese Syntax in der Basis R befindet.
Dason
68

Ich bin irgendwie auf diesen cleveren Hack im Internet gestoßen ... Ich bin mir nicht sicher, ob er böse oder schön ist, aber Sie können damit einen "magischen" Operator erstellen, mit dem Sie mehrere Rückgabewerte in ihre eigene Variable entpacken können. Die :=Funktion ist hier definiert und für die Nachwelt unten aufgeführt:

':=' <- 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)) 
}

Mit dieser Hand können Sie tun, wonach Sie suchen:

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

Ich weiß nicht, wie ich mich dabei fühle. Vielleicht finden Sie es in Ihrem interaktiven Arbeitsbereich hilfreich. Es ist vielleicht nicht die beste Idee, damit (wieder) verwendbare Bibliotheken (für den Massenkonsum) zu erstellen, aber ich denke, das liegt bei Ihnen.

... Sie wissen, was sie über Verantwortung und Macht sagen ...

Steve Lianoglou
quelle
12
Außerdem würde ich jetzt viel mehr davon abraten, als ich diese Antwort ursprünglich gepostet habe, da das data.table- Paket den :=Operator mucho auf viel handlichere Weise verwendet :-)
Steve Lianoglou
47

Normalerweise verpacke ich die Ausgabe in eine Liste, die sehr flexibel ist (Sie können eine beliebige Kombination von Zahlen, Zeichenfolgen, Vektoren, Matrizen, Arrays, Listen, Objekten in der Ausgabe verwenden).

So wie:

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
Federico Giorgi
quelle
Was ist, wenn ich anstelle von Ausgabe <-func2 (5) das Ergebnis in zwei Objekten haben möchte? Ich habe es mit list ("a", "b") <-func2 (5) versucht, aber es funktioniert nicht.
Skan
13
functionReturningTwoValues <- function() { 
  results <- list()
  results$first <- 1
  results$second <-2
  return(results) 
}
a <- functionReturningTwoValues()

Ich denke das funktioniert.

Jojo
quelle
11

Ich habe einen Zeallot für ein R-Paket zusammengestellt , um dieses Problem anzugehen. zeallot enthält einen Mehrfachzuweisungs- oder Auspackungszuweisungsoperator %<-%. Die LHS des Bedieners besteht aus einer beliebigen Anzahl von Variablen, die mithilfe von Aufrufen an zugewiesen werden sollen c(). Die RHS des Operators ist ein Vektor, eine Liste, ein Datenrahmen, ein Datumsobjekt oder ein beliebiges benutzerdefiniertes Objekt mit einer implementierten destructureMethode (siehe ?zeallot::destructure).

Hier sind einige Beispiele, die auf dem ursprünglichen Beitrag basieren:

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

Weitere Informationen und Beispiele finden Sie in der Paketvignette .

nteetor
quelle
Gibt es eine spezielle Syntax zum Speichern mehrerer Diagramme als Ausgaben mit dieser Methode?
Mrpargeter
2
Es ist keine spezielle Syntax erforderlich. Sie können eine Liste von Plotobjekten wie eine Liste von Zahlen zuweisen.
nteetor
10

Es gibt keine richtige Antwort auf diese Frage. Ich komme wirklich darauf an, was Sie mit den Daten machen. Im obigen einfachen Beispiel würde ich dringend empfehlen:

  1. Halte die Dinge so einfach wie möglich.
  2. Wo immer möglich, empfiehlt es sich, Ihre Funktionen vektorisiert zu halten. Das bietet auf lange Sicht ein Höchstmaß an Flexibilität und Geschwindigkeit.

Ist es wichtig, dass die obigen Werte 1 und 2 Namen haben? Mit anderen Worten, warum ist es in diesem Beispiel wichtig, dass 1 und 2 a und b heißen und nicht nur r [1] und r [2]? In diesem Zusammenhang ist es wichtig zu verstehen, dass a und b auch beide Vektoren der Länge 1 sind. Sie ändern also nichts wirklich, während Sie diese Zuweisung vornehmen, außer 2 neue Vektoren, für die keine Indizes erforderlich sind verwiesen werden:

> 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

Sie können die Namen auch dem ursprünglichen Vektor zuweisen, wenn Sie lieber auf den Buchstaben als auf den Index verweisen möchten:

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

[Bearbeiten] Da Sie min und max separat auf jeden Vektor anwenden, würde ich vorschlagen, entweder eine Matrix (wenn a und b dieselbe Länge und denselben Datentyp haben) oder einen Datenrahmen (wenn a und b gleich sind) zu verwenden die gleiche Länge, kann aber unterschiedliche Datentypen haben) oder verwenden Sie eine Liste wie in Ihrem letzten Beispiel (wenn sie unterschiedliche Längen und Datentypen haben können).

> 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
Shane
quelle
Die Frage wurde bearbeitet, um Ihre Anmerkungen aufzunehmen. Vielen Dank. Das Geben von Namen für Dinge wie r[1]kann helfen, die Dinge klarer zu machen (in Ordnung, nicht wenn Namen wie aan ihre Stelle treten).
Mariotomo
5

Listen scheinen für diesen Zweck perfekt zu sein. Zum Beispiel innerhalb der Funktion, die Sie haben würden

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

Hauptprogramm

x = returnlist[[1]]

y = returnlist[[2]]
Arnold
quelle
4
Wie können Sie beide Variablen in einem einzigen Befehl zuweisen, z. B. list ("x", "y") <-returnlist ()? Ich sage das, denn wenn Sie viele Elemente in der Liste haben, müssten Sie die gesamte Funktion mehrmals ausführen, und das kostet eine Zeit.
Skan
4

Ja zu Ihrer zweiten und dritten Frage - das müssen Sie tun, da Sie nicht mehrere 'l-Werte' links von einer Aufgabe haben können.

Dirk Eddelbuettel
quelle
3

Wie wäre es mit Zuweisen?

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

Sie können die Namen der Variablen, die Sie als Referenz übergeben möchten, übergeben.

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

Wenn Sie die vorhandenen Werte zugreifen müssen, die Umkehrung assignist get.

Steve Pitchers
quelle
... aber dazu müssen Sie die Namen der empfangenden Variablen in dieser Umgebung kennen
smci
@smci Ja. Aus diesem Grund ist die Methode "Named List" in der Frage im Allgemeinen besser: r <- function() { return(list(first=1, second=2)) }Verweisen Sie auf die Ergebnisse mit r$firstund r$second.
Steve Pitchers
2
Wenn Sie Ihre Funktion haben, wie können Sie beide Variablen in einem einzigen Befehl zuweisen, z. B. list ("x", "y") <- functionReturningTwoValues ​​('a', 'b')? Ich sage das, denn wenn Sie viele Elemente in der Liste haben, müssten Sie die gesamte Funktion mehrmals
ausführen
3

Wenn Sie die Ausgabe Ihrer Funktion an die globale Umgebung zurückgeben möchten, können Sie Folgendes verwenden list2env:

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)

Diese Funktion erstellt drei Objekte in Ihrer globalen Umgebung:

> my_obj1
  [1] 1 2 3

> my_obj2
  [1] 5 4 3

> myDF
    a b
  1 1 5
  2 2 4
  3 3 3
rafa.pereira
quelle
1

[A] Wenn foo und bar jeweils eine einzelne Zahl sind, ist c (foo, bar) nichts Falsches. und Sie können auch die Komponenten benennen: c (Foo = foo, Bar = bar). Sie können also auf die Komponenten des Ergebnisses 'res' als res [1], res [2] zugreifen. oder im genannten Fall als res ["Foo"], res ["BAR"].

[B] Wenn foo und bar Vektoren des gleichen Typs und der gleichen Länge sind, ist es wiederum nichts Falsches, cbind (foo, bar) oder rbind (foo, bar) zurückzugeben. ebenfalls benennbar. Im Fall 'cbind' würden Sie als res [, 1], res [, 2] oder als res [, "Foo"], res [, "Bar"] auf foo und bar zugreifen. Möglicherweise möchten Sie auch lieber einen Datenrahmen als eine Matrix zurückgeben:

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

und greifen Sie als res $ Foo, res $ Bar darauf zu. Dies würde auch gut funktionieren, wenn foo und bar dieselbe Länge, aber nicht denselben Typ hätten (z. B. foo ist ein Vektor von Zahlen, bar ein Vektor von Zeichenketten).

[C] Wenn foo und bar so unterschiedlich sind, dass sie nicht wie oben beschrieben kombiniert werden können, sollten Sie auf jeden Fall eine Liste zurückgeben.

Zum Beispiel könnte Ihre Funktion in ein lineares Modell passen und auch vorhergesagte Werte berechnen, so dass Sie haben könnten

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

und dann würden Sie return list(Foo=foo,Bar=bar)und dann auf die Zusammenfassung als res $ Foo zugreifen, die vorhergesagten Werte als res $ Bar

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

Prakhar Agrawal
quelle
-1

Um mehrere Ausgaben von einer Funktion zu erhalten und im gewünschten Format zu halten, können Sie die Ausgaben innerhalb der Funktion auf Ihrer Festplatte (im Arbeitsverzeichnis) speichern und dann von außerhalb der Funktion laden:

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

Mit R 3.6.1 kann ich Folgendes tun

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
radumanolescu
quelle