Jak przypisać z funkcji, która zwraca więcej niż jedną wartość?


223

Wciąż próbuję dostać się do logiki R. ... jaki jest „najlepszy” sposób na rozpakowanie (na LHS) wyników funkcji zwracającej wiele wartości?

Najwyraźniej nie mogę tego zrobić:

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

czy naprawdę muszę wykonać następujące czynności?

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

lub czy programista R napisałby coś takiego:

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

--- edytowane, aby odpowiedzieć na pytania Shane'a ---

Naprawdę nie potrzebuję nadawać nazw częściom wartości wynikowej. Stosuję jedną funkcję agregującą do pierwszego komponentu, a drugą do drugiego komponentu ( mini max. Jeśli byłaby to ta sama funkcja dla obu komponentów, nie musiałbym ich dzielić).


7
Do Twojej wiadomości, innym sposobem na zwrócenie wielu wartości jest ustawienie attrwartości zwracanej.
Jonathan Chang

Jest to odpowiednik rozpakowywania krotek w Pythonie.
smci

Odpowiedzi:


185

(1) lista [...] <- Wysłałem to ponad dekadę temu na r-help . Od tego czasu został dodany do pakietu gsubfn. Nie wymaga specjalnego operatora, ale wymaga napisania lewej strony w następujący sposób list[...]:

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

Jeśli potrzebujesz tylko pierwszego lub drugiego komponentu, wszystkie one również działają:

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

(Oczywiście, jeśli potrzebowałbyś tylko jednej wartości wtedy functionReturningTwoValues()[[1]]lub functionReturningTwoValues()[[2]]byłby wystarczający.)

Więcej przykładów można znaleźć w cytowanym wątku r-help.

(2) z Jeśli celem jest jedynie połączenie wielu wartości później, a wartości zwracane są nazwane, wówczas można zastosować prostą alternatywę with:

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

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

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

(3) dołącz Kolejną alternatywą jest załącznik:

attach(myfun())
a + b

DODANE: withiattach


25
Zaakceptowałem twoją odpowiedź z powodu „z”, ale nie mogę odtworzyć tego, co
opisujesz dla

4
Mi to pasuje. Co próbowałeś Czy czytałeś link do posta i obserwowałeś go? Czy zdefiniowałeś listi [<-.resultjak tam pokazano?
G. Grothendieck

12
@ G.Grothendieck, czy miałbyś coś przeciwko, żebym podał treść twojego linku w twojej odpowiedzi? Myślę, że ułatwiłoby to ludziom korzystanie z niego.
merlin2011

12
Zgadzam się z @ merlin2011; jak napisano, wydaje się, że ta składnia jest osadzona w bazie R.
knowah

6
@ G.Grothendieck Zgadzam się z merlin2011 i knowah - najlepiej byłoby, gdyby prawdziwy kod, który jest tutaj ważny (kod wymieniony w linku) znajduje się w odpowiedzi. Wspomnienie, że wynikowy obiekt nie musi mieć nazwy listy, może nie być złym pomysłem. To pomieszało mnie przez chwilę, zanim przeczytałem twój rzeczywisty kod. Jak wspomniano, odpowiedź mówi, że musisz uruchomić kod w linku, ale większość ludzi nie będzie od razu czytać tego kodu, chyba że jest on bezpośrednio w odpowiedzi - daje to wrażenie, że ta składnia jest w bazie R.
Dason

68

Jakoś natknąłem się na ten sprytny hack w Internecie ... Nie jestem pewien, czy jest to paskudne czy piękne, ale pozwala ci stworzyć „magiczny” operator, który pozwala rozpakować wiele zwracanych wartości do ich własnych zmiennych. :=Funkcja zdefiniowana jest tutaj , a zawarte poniżej dla potomnych:

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

Mając to w ręku, możesz robić to, czego szukasz:

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

Nie wiem jak się z tym czuję. Być może może Ci się to przydać w interaktywnym obszarze roboczym. Wykorzystanie go do budowy (ponownie) użytecznych bibliotek (do masowego użytku) może nie być najlepszym pomysłem, ale myślę, że to zależy od ciebie.

... wiesz, co mówią o odpowiedzialności i władzy ...


12
Odradzam również o wiele bardziej niż wtedy, gdy pierwotnie opublikowałem tę odpowiedź, ponieważ pakiet data.table korzysta z :=operatora mucho w znacznie bardziej
wygodny

47

Zwykle zawijam dane wyjściowe do listy, która jest bardzo elastyczna (możesz mieć dowolną kombinację liczb, ciągów, wektorów, macierzy, tablic, list, obiektów, w których dane wyjściowe)

tak jak:

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

Co jeśli zamiast wyniku <-func2 (5) chcę otrzymać wynik w dwóch obiektach? Próbowałem z list („a”, „b”) <-func2 (5), ale to nie działa.
skan

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

Myślę, że to działa.


11

Złożyłem pakiet Zeallot R, aby rozwiązać ten problem. zeallot zawiera wiele zadanie lub rozpakowywanie operatora przypisania %<-%. LHS operatora to dowolna liczba zmiennych do przypisania, zbudowana za pomocą wywołań do c(). RHS operatora to wektor, lista, ramka danych, obiekt daty lub dowolny obiekt niestandardowy z zaimplementowaną destructuremetodą (patrz ?zeallot::destructure).

Oto garść przykładów opartych na oryginalnym poście,

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

Sprawdź winietę opakowania, aby uzyskać więcej informacji i przykładów.


Czy istnieje specjalna składnia do przechowywania kilku wykresów jako wyników przy użyciu tej metody?
mrpargeter,

2
Nie jest wymagana specjalna składnia, można przypisać listę obiektów wykresu tak, jak listę liczb.
nteetor,

10

Nie ma właściwej odpowiedzi na to pytanie. Naprawdę zależy od tego, co robisz z danymi. W prostym powyższym przykładzie zdecydowanie sugerowałbym:

  1. Utrzymuj rzeczy tak proste, jak to możliwe.
  2. Tam, gdzie to możliwe, najlepszą praktyką jest utrzymanie wektoryzacji funkcji. Zapewnia to największą elastyczność i szybkość na dłuższą metę.

Czy ważne jest, aby powyższe wartości 1 i 2 miały nazwy? Innymi słowy, dlaczego w tym przykładzie ważne jest, aby 1 i 2 były nazywane aib, a nie tylko r [1] ir [2]? Jedną ważną rzeczą, którą należy zrozumieć w tym kontekście, jest to, że aib są jednocześnie wektorami o długości 1. Więc tak naprawdę nic nie zmieniasz w trakcie wykonywania tego przypisania, oprócz posiadania 2 nowych wektorów, które nie wymagają indeksów dolnych, aby odnosić się do:

> 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

Możesz także przypisać nazwy oryginalnemu wektorowi, jeśli wolisz odwoływać się do litery niż do indeksu:

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

[Edytuj] Biorąc pod uwagę, że zastosujesz min i max do każdego wektora osobno, sugerowałbym użycie macierzy (jeśli aib będą tej samej długości i tego samego typu danych) lub ramki danych (jeśli aib będą tej samej długości, ale mogą mieć różne typy danych) lub użyj listy jak w poprzednim przykładzie (jeśli mogą mieć różne długości i typy danych).

> 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

zredagował to pytanie, aby uwzględnić Twoje uwagi. dzięki. nadawanie nazw rzeczom takim jak r[1]może pomóc w wyjaśnieniu (w porządku, nie jeśli imiona takie asą na swoim miejscu).
mariotomo

5

Listy wydają się idealne do tego celu. Na przykład w ramach funkcji, którą miałbyś

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

główny program

x = returnlist[[1]]

y = returnlist[[2]]

4
Jak przypisać obie zmienne w jednym poleceniu, takim jak list („x”, „y”) <-returnlist ()? Mówię to, ponieważ jeśli masz wiele elementów na liście, musisz uruchomić całą funkcję kilka razy, a to kosztuje czas.
skan

4

Tak, na twoje drugie i trzecie pytanie - właśnie to musisz zrobić, ponieważ nie możesz mieć wielu „wartości” po lewej stronie zadania.


3

Co powiesz na użycie przypisania?

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

Możesz przekazać nazwy zmiennej, którą chcesz przekazać przez referencję.

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

Jeśli potrzebujesz dostępu do istniejących wartości, odwrotnie assignjest get.


... ale to wymaga znajomości nazw zmiennych odbiorczych w tym środowisku
smci

@smci Tak. Dlatego metoda „nazwanej listy” w pytaniu jest ogólnie lepsza: r <- function() { return(list(first=1, second=2)) }i odwołuje się do wyników za pomocą r$firsti r$second.
Steve Pitchers

2
Kiedy masz już swoją funkcję, jak możesz przypisać obie zmienne w jednym poleceniu, na przykład list („x”, „y”) <- functionReturningTwoValues ​​('a', 'b')? Mówię to, ponieważ jeśli masz wiele elementów na liście, musisz kilkakrotnie uruchomić całą funkcję, a to kosztuje czas
skan

3

Jeśli chcesz zwrócić dane wyjściowe swojej funkcji do środowiska globalnego, możesz użyć list2env, podobnie jak w tym przykładzie:

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)

Ta funkcja utworzy trzy obiekty w globalnym środowisku:

> 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] Jeśli każdy z foo i taktów jest pojedynczą liczbą, to nie ma nic złego z c (foo, takt); i możesz również nazwać komponenty: c (Foo = foo, Bar = bar). Abyś mógł uzyskać dostęp do składników wyniku „res” jako res [1], res [2]; lub, w podanym przypadku, jako res [„Foo”], res [„BAR”].

[B] Jeśli foo i słupek są wektorami tego samego typu i długości, to znowu nie ma nic złego w zwracaniu cbind (foo, bar) lub rbind (foo, bar); podobnie jak nazwa. W przypadku „cbind” można uzyskać dostęp do foo i bar jako res [, 1], res [, 2] lub jako res [, „Foo”], res [, „Bar”]. Możesz także chcieć zwrócić ramkę danych niż macierz:

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

i uzyskaj do nich dostęp jako res $ Foo, res $ Bar. Działałoby to również dobrze, gdyby foo i słupek były tej samej długości, ale nie tego samego typu (np. Foo jest wektorem liczb, a wektorem ciąg znaków).

[C] Jeśli foo i bar są wystarczająco różne, aby nie łączyć się wygodnie jak wyżej, to zdecydowanie powinieneś zwrócić listę.

Na przykład twoja funkcja może pasować do modelu liniowego, a także obliczać przewidywane wartości, abyś mógł mieć

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

a następnie uzyskasz return list(Foo=foo,Bar=bar)dostęp do podsumowania jako res $ Foo, przewidywane wartości jako res $ Bar

źródło: http://r.789695.n4.nabble.com/How-to-return-multiple-values-in-a-function-td858528.html


-1

Aby uzyskać wiele danych wyjściowych z funkcji i zachować je w żądanym formacie, możesz zapisać dane wyjściowe na dysku twardym (w katalogu roboczym) z poziomu funkcji, a następnie załadować je spoza funkcji:

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

-1

Z R 3.6.1 mogę wykonać następujące czynności

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.