Come assegnare da una funzione che restituisce più di un valore?


223

Sto ancora cercando di entrare nella logica R ... qual è il modo "migliore" per decomprimere (su LHS) i risultati da una funzione che restituisce più valori?

Apparentemente non posso farlo:

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

devo davvero fare quanto segue?

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

o il programmatore R scriverebbe qualcosa di più simile a questo:

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

--- modificato per rispondere alle domande di Shane ---

Non ho davvero bisogno di dare nomi alle parti del valore del risultato. Sto applicando una funzione aggregata al primo componente e un'altra al secondo componente ( mine max. Se fosse la stessa funzione per entrambi i componenti non avrei bisogno di dividerli).


7
Cordiali saluti, un altro modo per restituire più valori è quello di impostare un attrvalore di ritorno.
Jonathan Chang,

Questo è l'equivalente del disimballaggio della tupla di Python.
smci,

Risposte:


186

(1) list [...] <- L' ho pubblicato più di un decennio fa su r-help . Da allora è stato aggiunto al pacchetto gsubfn. Non richiede un operatore speciale ma richiede che il lato sinistro sia scritto usando in list[...]questo modo:

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

Se hai bisogno solo del primo o del secondo componente, funzionano anche tutti questi:

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

(Naturalmente, se avessi bisogno di un solo valore, allora functionReturningTwoValues()[[1]]o functionReturningTwoValues()[[2]]sarebbe sufficiente.)

Vedi il thread r-help citato per altri esempi.

(2) con Se l'intento è semplicemente quello di combinare i valori multipli successivamente e i valori di ritorno sono nominati, una semplice alternativa è quella di utilizzare with:

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

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

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

(3) allegare Un'altra alternativa è allegare:

attach(myfun())
a + b

AGGIUNTO: witheattach


25
Ho accettato la tua risposta a causa del "con", ma non riesco a riprodurre ciò che descrivi per l'uso a sinistra di "elenco", tutto ciò che ottengo è "oggetto" un "non trovato"
mariotomo

4
Per me funziona. Cosa hai provato Hai letto il post collegato e lo hai seguito? Hai definito liste [<-.resultcome mostrato lì?
G. Grothendieck,

12
@ G.Grothendieck, ti ​​dispiacerebbe se inserissi il contenuto del tuo link nella tua risposta? Penso che renderebbe più facile per le persone usarlo.
merlin2011,

12
Sono d'accordo con @ merlin2011; come scritto sembra che questa sintassi sia incorporata in R base.
knowah,

6
@ G.Grothendieck Sono d'accordo con merlin2011 e knowah - sarebbe meglio se il codice effettivo che è importante qui (il codice a cui si fa riferimento nel link) è nella risposta. Potrebbe non essere una cattiva idea ricordare che l'oggetto risultato non ha bisogno di essere nominato nell'elenco. Questo mi ha confuso per un po 'prima di leggere il tuo vero codice. Come accennato, la risposta dice che è necessario eseguire il codice nel collegamento, ma la maggior parte delle persone non leggerà immediatamente quel codice a meno che non sia direttamente nella risposta - questo dà l'impressione che questa sintassi sia nella base R.
Dason

68

In qualche modo mi sono imbattuto in questo trucco intelligente su Internet ... Non sono sicuro che sia cattivo o bello, ma ti consente di creare un operatore "magico" che ti consente di decomprimere più valori di ritorno nella propria variabile. La :=funzione è definita qui e inclusa di seguito per i posteri:

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

Con quello in mano, puoi fare quello che stai cercando:

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

Non so come mi sento al riguardo. Forse potresti trovarlo utile nel tuo spazio di lavoro interattivo. Usarlo per costruire librerie (ri) utilizzabili (per il consumo di massa) potrebbe non essere la migliore idea, ma immagino dipenda da te.

... sai cosa dicono di responsabilità e potere ...


12
Inoltre lo scoraggerei molto di più ora rispetto a quando originariamente ho pubblicato questa risposta poiché il pacchetto data.table utilizza l' :=operatore mucho in un modo molto più
pratico

47

Di solito avvolgo l'output in un elenco, che è molto flessibile (puoi avere qualsiasi combinazione di numeri, stringhe, vettori, matrici, matrici, elenchi, oggetti nell'output)

così come:

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

Cosa succede se invece di output <-func2 (5) voglio avere il risultato in due oggetti? Ho provato con list ("a", "b") <-func2 (5) ma non funziona.
skan

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

Penso che funzioni.


11

Ho messo insieme un pacchetto R. zeallot per affrontare questo problema. zeallot include un'assegnazione multipla o operatore di assegnazione disimballaggio, %<-%. L'LHS dell'operatore è un numero qualsiasi di variabili da assegnare, costruite usando le chiamate a c(). L'RHS dell'operatore è un vettore, un elenco, un frame di dati, un oggetto data o qualsiasi oggetto personalizzato con un destructuremetodo implementato (vedere ?zeallot::destructure).

Ecco alcuni esempi basati sul post originale,

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

Controlla la vignetta del pacchetto per ulteriori informazioni ed esempi.


Esiste una sintassi speciale per memorizzare diversi grafici come output usando questo metodo?
mrpargeter,

2
Non è richiesta alcuna sintassi speciale, è possibile assegnare un elenco di oggetti della trama come si farebbe con un elenco di numeri.
nteetor,

10

Non c'è una risposta giusta a questa domanda. Dipendo davvero da cosa stai facendo con i dati. Nel semplice esempio sopra, suggerirei fortemente:

  1. Mantieni le cose il più semplice possibile.
  2. Ove possibile, è consigliabile mantenere vettorizzate le funzioni. Ciò offre la massima flessibilità e velocità nel lungo periodo.

È importante che i valori 1 e 2 sopra abbiano nomi? In altre parole, perché è importante in questo esempio che 1 e 2 siano denominati aeb, anziché solo r [1] e r [2]? Una cosa importante da capire in questo contesto è che aeb sono anche entrambi vettori di lunghezza 1. Quindi non stai davvero cambiando nulla nel processo di assegnazione di quel compito, oltre ad avere 2 nuovi vettori che non richiedono abbonamenti per essere referenziato:

> 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

Puoi anche assegnare i nomi al vettore originale se preferisci fare riferimento alla lettera piuttosto che all'indice:

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

[Modifica] Dato che applicherai min e max su ciascun vettore separatamente, ti suggerirei di utilizzare una matrice (se aeb avrà la stessa lunghezza e lo stesso tipo di dati) o un frame di dati (se aeb sarà della stessa lunghezza ma possono essere tipi di dati diversi) oppure utilizzare un elenco come nel tuo ultimo esempio (se possono avere lunghezze e tipi di dati diversi).

> 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

modificato la domanda per includere le tue osservazioni. Grazie. dare nomi a cose come r[1]può aiutare a rendere le cose più chiare (va bene, non se nomi come avengono al loro posto).
mariotomo,

5

Le liste sembrano perfette per questo scopo. Ad esempio all'interno della funzione che avresti

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

programma principale

x = returnlist[[1]]

y = returnlist[[2]]

4
Come si possono assegnare entrambe le variabili in un unico comando, come list ("x", "y") <-returnlist ()? Lo dico perché se hai molti elementi nell'elenco dovresti eseguire l'intera funzione più volte e questo costa un tempo.
skan

4

Sì alla tua seconda e terza domanda: ecco cosa devi fare perché non puoi avere più "valori" a sinistra di un compito.


3

Che ne dici di usare Assegna?

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

È possibile passare i nomi della variabile che si desidera passare per riferimento.

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

Se è necessario accedere ai valori esistenti, il contrario di assignè get.


... ma questo richiede che tu conosca i nomi delle variabili di ricezione in quell'ambiente
smci

@smci Sì. Ecco perché il metodo "elenco dei nomi" nella domanda è generalmente migliore: r <- function() { return(list(first=1, second=2)) }e fare riferimento ai risultati usando r$firste r$second.
Steve Pitchers,

2
Una volta che hai la tua funzione, come puoi assegnare entrambe le variabili in un singolo comando, come list ("x", "y") <- functionReturningTwoValues ​​('a', 'b')? Lo dico perché se hai molti elementi nell'elenco dovresti eseguire l'intera funzione più volte e questo costa un tempo
skan

3

Se si desidera restituire l'output della funzione all'ambiente globale, è possibile utilizzare list2env, come in questo esempio:

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)

Questa funzione creerà tre oggetti nel tuo ambiente globale:

> 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] Se ciascuno di foo e bar è un singolo numero, allora non c'è niente di sbagliato in c (foo, bar); e puoi anche nominare i componenti: c (Foo = foo, Bar = bar). Quindi puoi accedere ai componenti del risultato 'res' come res [1], res [2]; oppure, nel caso indicato, come res ["Foo"], res ["BAR"].

[B] Se foo e bar sono vettori dello stesso tipo e lunghezza, di nuovo non c'è niente di sbagliato nel restituire cbind (foo, bar) o rbind (foo, bar); allo stesso modo nominabile. Nel caso 'cbind', si accede a foo e bar come res [, 1], res [, 2] o come res [, "Foo"], res [, "Bar"]. Potresti anche preferire restituire un dataframe piuttosto che una matrice:

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

e accedervi come res $ Foo, res $ Bar. Ciò funzionerebbe anche se foo e bar erano della stessa lunghezza ma non dello stesso tipo (ad esempio foo è un vettore di numeri, esclude un vettore di stringhe di caratteri).

[C] Se foo e bar sono sufficientemente diversi da non combinarsi convenientemente come sopra, allora si dovrebbe sicuramente restituire un elenco.

Ad esempio, la tua funzione potrebbe adattarsi a un modello lineare e anche calcolare i valori previsti, quindi potresti avere

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

e quindi si return list(Foo=foo,Bar=bar)accede e si accede al riepilogo come res $ Foo, i valori previsti come res $ Bar

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


-1

Per ottenere più output da una funzione e mantenerli nel formato desiderato, è possibile salvare gli output sul disco rigido (nella directory di lavoro) dall'interno della funzione e caricarli dall'esterno della funzione:

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

-1

Con R 3.6.1, posso fare quanto segue

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
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.