Como atribuir a partir de uma função que retorna mais de um valor?

223

Ainda tentando entrar na lógica R ... qual é a "melhor" maneira de descompactar (no LHS) os resultados de uma função retornando vários valores?

Aparentemente, não posso fazer isso:

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 realmente fazer o seguinte?

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

ou o programador R escreveria algo mais parecido com isto:

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

--- editado para responder às perguntas de Shane ---

Eu realmente não preciso dar nomes às partes do valor resultante. Estou aplicando uma função agregada ao primeiro componente e outra ao segundo componente ( mine max. Se fosse a mesma função para os dois componentes, não precisaria dividi-los).

mariotomo
fonte
7
Para sua informação, outra maneira de retornar vários valores é definir um attrvalor de retorno.
1113 Jonathan Chang
Isso é equivalente à descompactação de tuplas do Python.
SMCI

Respostas:

186

(1) list [...] <- Eu publiquei isso há mais de uma década no r-help . Desde então, ele foi adicionado ao pacote gsubfn. Não requer um operador especial, mas exige que o lado esquerdo seja escrito usando o list[...]seguinte:

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

Se você precisar apenas do primeiro ou do segundo componente, tudo isso funcionará também:

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

(Claro, se você só precisava de um valor, em seguida, functionReturningTwoValues()[[1]]ou functionReturningTwoValues()[[2]]seria suficiente.)

Veja o tópico r-help citado para mais exemplos.

(2) com Se a intenção é meramente combinar os vários valores subsequentemente e os valores de retorno forem nomeados, uma alternativa simples é usar with:

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

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

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

(3) anexar Outra alternativa é anexar:

attach(myfun())
a + b

ADICIONADO: witheattach

G. Grothendieck
fonte
25
Aceitei sua resposta por causa do "com", mas não consigo reproduzir o que você descreve para o uso do lado esquerdo da "lista", tudo o que recebo é "objeto 'a' não encontrado"
mariotomo
4
Funciona para mim. O que você tentou? Você leu a postagem vinculada e a seguiu? Você definiu liste [<-.resultcomo mostrado lá?
G. Grothendieck 23/03
12
@ G.Grothendieck, você se importaria se eu colocasse o conteúdo do seu link na sua resposta? Eu acho que facilitaria o uso das pessoas.
precisa saber é o seguinte
12
Eu concordo com @ merlin2011; como está escrito, parece que essa sintaxe está incorporada na base R.
knowah
6
@ G.Grothendieck Concordo com merlin2011 e knowah - seria melhor se o código real que é importante aqui (o código referenciado no link) estiver na resposta. Pode não ser uma má idéia mencionar que o objeto de resultado não precisa ser nomeado como lista. Isso me confundiu um pouco antes de ler seu código real. Como mencionado, a resposta diz que você precisa executar o código no link, mas a maioria das pessoas não lê o código imediatamente, a menos que esteja diretamente na resposta - isso dá a impressão de que essa sintaxe está na base R.
Dason
68

De alguma forma, eu tropecei nesse truque inteligente da Internet ... Não tenho certeza se é desagradável ou bonito, mas permite criar um operador "mágico" que permite descompactar vários valores de retorno em sua própria variável. A :=função é definida aqui e incluída abaixo para a posteridade:

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

Com isso em mãos, você pode fazer o que procura:

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

Não sei como me sinto sobre isso. Talvez você ache útil no seu espaço de trabalho interativo. Usá-lo para criar bibliotecas (re) utilizáveis ​​(para consumo em massa) pode não ser a melhor ideia, mas acho que depende de você.

... você sabe o que eles dizem sobre responsabilidade e poder ...

Steve Lianoglou
fonte
12
Também o desencorajaria muito mais agora do que quando originalmente postei essa resposta, já que o pacote data.table usa o :=operador mucho de uma maneira muito mais
prática
47

Normalmente, envolvo a saída em uma lista, que é muito flexível (você pode ter qualquer combinação de números, seqüências de caracteres, vetores, matrizes, matrizes, listas, objetos na saída)

assim como:

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
fonte
E se, em vez da saída <-func2 (5), eu quiser ter o resultado em dois objetos? Eu tentei com list ("a", "b") <-func2 (5), mas não funciona.
skan
13
functionReturningTwoValues <- function() { 
  results <- list()
  results$first <- 1
  results$second <-2
  return(results) 
}
a <- functionReturningTwoValues()

Eu acho que isso funciona.

Jojo
fonte
11

Eu montei um fanático do pacote R para resolver esse problema. O zeallot inclui um operador de atribuição múltipla ou de descompactação %<-%,. O LHS do operador é qualquer número de variáveis ​​a serem atribuídas, criadas usando chamadas para c(). O RHS do operador é um vetor, lista, quadro de dados, objeto de data ou qualquer objeto personalizado com um destructuremétodo implementado (consulte ?zeallot::destructure).

Aqui estão alguns exemplos baseados na postagem original,

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

Confira a vinheta do pacote para obter mais informações e exemplos.

nteetor
fonte
Existe uma sintaxe especial para armazenar vários gráficos como saídas usando este método?
Mrpargeter 10/10
2
Nenhuma sintaxe especial é necessária, você pode atribuir uma lista de objetos de plotagem como faria com uma lista de números.
Ntetor 10/10
10

Não há resposta certa para esta pergunta. Eu realmente depende do que você está fazendo com os dados. No exemplo simples acima, eu sugeriria fortemente:

  1. Mantenha as coisas o mais simples possível.
  2. Sempre que possível, é uma prática recomendada manter suas funções vetorizadas. Isso fornece a maior quantidade de flexibilidade e velocidade a longo prazo.

É importante que os valores 1 e 2 acima tenham nomes? Em outras palavras, por que é importante neste exemplo que 1 e 2 sejam nomeados aeb, em vez de apenas r [1] e r [2]? Uma coisa importante a entender neste contexto é que aeb também são vetores de comprimento 1. Portanto, você não está realmente alterando nada no processo de fazer essa atribuição, além de ter 2 novos vetores que não precisam de subscritos para ser referenciado:

> 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

Você também pode atribuir os nomes ao vetor original se preferir fazer referência à letra do que ao índice:

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

[Edit] Como você aplicará min e max a cada vetor separadamente, sugiro usar uma matriz (se aeb tiver o mesmo comprimento e o mesmo tipo de dados) ou quadro de dados (se aeb for o mesmo comprimento, mas podem ser tipos de dados diferentes) ou use uma lista como no seu último exemplo (se eles puderem ter comprimentos e tipos de dados diferentes).

> 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
fonte
editou a pergunta para incluir seus comentários. obrigado. dar nomes a coisas como isso r[1]pode ajudar a tornar as coisas mais claras (tudo bem, não se nomes como avierem em seu lugar).
Mariotomo 01/12/2009
5

As listas parecem perfeitas para esse fim. Por exemplo, dentro da função que você teria

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

programa principal

x = returnlist[[1]]

y = returnlist[[2]]
Arnold
fonte
4
Como você pode atribuir as duas variáveis ​​em um único comando, como list ("x", "y") <-returnlist ()? Eu digo isso porque se você tiver muitos elementos na lista, precisará executar toda a função várias vezes e isso custa um tempo.
skan
4

Sim para sua segunda e terceira perguntas - é isso que você precisa fazer, pois não pode ter vários 'valores' à esquerda de uma tarefa.

Dirk Eddelbuettel
fonte
3

Que tal usar o assign?

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

Você pode passar os nomes da variável que deseja que sejam passados ​​por referência.

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

Se você precisar acessar os valores existentes, o inverso assigné get.

Steve Pitchers
fonte
... mas isso requer que você sabe os nomes das variáveis que receberam nesse ambiente
SMCI
@smci Sim. É por isso que o método "lista nomeada" na pergunta geralmente é melhor: r <- function() { return(list(first=1, second=2)) }e faça referência aos resultados usando r$firste r$second.
23815 Steve Pitchers
2
Depois de ter sua função, como você pode atribuir as duas variáveis ​​em um único comando, como list ("x", "y") <- functionReturningTwoValues ​​('a', 'b')? Digo isso porque se você tem muitos elementos da lista que você precisa para executar os inteira de função várias vezes e que custa um tempo
skan
3

Se você deseja retornar a saída de sua função para o Global Environment, pode usar list2env, como neste exemplo:

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)

Esta função criará três objetos no seu ambiente 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
rafa.pereira
fonte
1

[A] Se cada foo e barra for um único número, não há nada errado com c (foo, barra); e você também pode nomear os componentes: c (Foo = foo, Bar = bar). Então você pode acessar os componentes do resultado 'res' como res [1], res [2]; ou, no caso nomeado, como res ["Foo"], res ["BAR"].

[B] Se foo e bar são vetores do mesmo tipo e comprimento, então novamente não há nada errado em retornar cbind (foo, bar) ou rbind (foo, bar); igualmente nomeado. No caso 'cbind', você acessaria foo e bar como res [, 1], res [, 2] ou como res [, "Foo"], res [, "Bar"]. Você também pode preferir retornar um quadro de dados em vez de uma matriz:

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

e acesse-os como res $ Foo, res $ Bar. Isso também funcionaria bem se foo e bar fossem do mesmo comprimento, mas não do mesmo tipo (por exemplo, foo é um vetor de números, exceto um vetor de cadeias de caracteres).

[C] Se foo e bar forem suficientemente diferentes para não combinar convenientemente como acima, você definitivamente retornará uma lista.

Por exemplo, sua função pode ajustar-se a um modelo linear e também calcular valores previstos, para que você possa ter

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

e então você faria return list(Foo=foo,Bar=bar) acessaria o resumo como res $ Foo, os valores previstos como res $ Bar

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

Prakhar Agrawal
fonte
-1

Para obter várias saídas de uma função e mantê-las no formato desejado, você pode salvar as saídas no disco rígido (no diretório de trabalho) de dentro da função e carregá-las de fora da função:

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

Com o R 3.6.1, eu posso fazer o seguinte

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