Birden fazla değer döndüren bir işlevden nasıl atama yapılır?


223

Hala R mantığına girmeye çalışıyoruz ... birden fazla değer döndüren bir fonksiyondan sonuçları (LHS'de) paketlemenin "en iyi" yolu nedir?

Görünüşe göre bunu yapamam:

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

Gerçekten aşağıdakileri yapmalıyım?

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

ya da R programcısı böyle bir şey yazar mı:

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

--- Shane'nin sorularını cevaplamak için düzenlendi ---

Sonuç değer kısımlarına gerçekten isim vermeme gerek yok. Birinci bileşene bir toplama işlevi, ikinci bileşene diğeri ( minve maxher iki bileşen için aynı işlev olsaydı, bunları bölmeye gerek yoktur) uyguluyorum .


7
FYI, birden çok değer döndürmenin başka bir yolu da dönüş değerinizi ayarlamaktır attr.
Jonathan Chang

Bu, Python'un demet açma işlemine eşdeğerdir.
smci

Yanıtlar:


186

(1) list [...] <- Bunu on yıldan uzun bir süre önce r- help'de yayınlamıştım . O zamandan beri gsubfn paketine eklendi. Özel bir operatör gerektirmez, ancak sol tarafın aşağıdaki list[...]gibi kullanılarak yazılmasını gerektirir :

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

Yalnızca birinci veya ikinci bileşene ihtiyacınız varsa, bunların hepsi de çalışır:

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

(Tabii ki, sadece bir değere ihtiyacınız varsa functionReturningTwoValues()[[1]]veya functionReturningTwoValues()[[2]]yeterli olacaktır.)

Daha fazla örnek için belirtilen r-yardım başlığına bakın.

(2) ile niyet yalnızca birden fazla değeri daha sonra birleştirmekse ve dönüş değerleri adlandırılmışsa, basit bir alternatif kullanmaktır with:

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

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

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

(3) eklemek Başka bir alternatif eklemek:

attach(myfun())
a + b

EKLENDİ: withveattach


25
Cevabınızı "ile" nedeniyle kabul ettim, ancak "liste" nin sol tarafındaki kullanımı için tanımladığınız şeyi çoğaltılamıyorum, tüm aldığım "nesne" a "bulunamadı"
mariotomo

4
Benim için çalışıyor. Ne denedin? Bağlantılı yayını okudunuz ve takip ettiniz mi? Tanımlamak mı listve [<-.resultorada gösterildiği gibi?
G. Grothendieck

12
@ G.Grothendieck, Bağlantınızın içeriğini cevabınıza koymamın bir sakıncası var mı? İnsanların bunu kullanmasını kolaylaştıracağını düşünüyorum.
merlin2011

12
@ Merlin2011; yazıldığı gibi, bu sözdizimi R tabanına gömülü gibi görünüyor.
knowah

6
@ G.Grothendieck Merlin2011 ve knowah ile hemfikirim - burada önemli olan gerçek kodun (linkte referans verilen kod) cevapta olması en iyisi olacaktır. Sonuç nesnesinin listeye adlandırılmasına gerek olmadığını belirtmek kötü bir fikir olmayabilir. Gerçek kodunuzu okumadan önce bu beni biraz karıştırdı. Belirtildiği gibi, cevap kodu bağlantıda çalıştırmanız gerektiğini söylüyor, ancak çoğu kişi doğrudan cevapta olmadıkça bu kodu hemen okuyamayacak - bu sözdiziminin R tabanında olduğu izlenimini veriyor.
Dason

68

Bir şekilde internetteki bu akıllı kesmek için tökezledim ... Kötü ya da güzel olup olmadığından emin değilim, ancak kendi değişkenlerine birden fazla dönüş değerini açmanıza izin veren "büyülü" bir operatör oluşturmanıza izin verir. :=Fonksiyonu burada tanımlanır ve gelecek kuşaklar için aşağıda bulunmaktadır:

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

Elinizdeyken, peşinde olduğunuz şeyi yapabilirsiniz:

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

Bu konuda nasıl hissettiğimi bilmiyorum. Belki de interaktif çalışma alanınızda faydalı bulabilirsiniz. Kullanılabilir kitaplıklar oluşturmak için (toplu tüketim için) kullanmak en iyi fikir olmayabilir, ama sanırım bu size kalmış.

... sorumluluk ve güç hakkında ne söylediklerini biliyorsun ...


12
Ayrıca, data.table paketi :=operatör mucho'yu çok daha kullanışlı bir şekilde kullandığından beri bu cevabı ilk gönderdiğimden çok daha fazla cesaret kırdım :-)
Steve Lianoglou

47

Genellikle çıktıyı çok esnek bir listeye sararım (çıktı, sayı, dize, vektör, matris, diziler, listeler, çıktıdaki nesnelerin herhangi bir kombinasyonuna sahip olabilirsiniz)

şöyle:

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

<-Func2 (5) çıktısı yerine iki nesnede sonuç elde etmek istersem ne olur? Liste ("a", "b") <-func2 (5) ile denedim ama çalışmıyor.
skan

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

Bence bu işe yarıyor.


11

Bu sorunu çözmek için bir R paketi zeallotu bir araya getirdim . zeallot çoklu atama veya ambalajdan çıkarma atama operatörü içerir %<-%,. Operatörün LHS'si, çağrılar kullanılarak oluşturulan atanacak herhangi bir sayıda değişkendir c(). Operatörün RHS'si bir vektör, liste, veri çerçevesi, tarih nesnesi veya uygulanan bir destructureyönteme sahip herhangi bir özel nesnedir (bkz.?zeallot::destructure ).

İşte orijinal yazıyı temel alan birkaç örnek,

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

Daha fazla bilgi ve örnek için paket vinyetini inceleyin .


Bu yöntemi kullanarak birkaç grafiği çıktı olarak saklamak için özel bir sözdizimi var mı?
mrpargeter

2
Özel bir sözdizimi gerekmez, bir sayı listesi gibi çizim nesnelerinin bir listesini atayabilirsiniz.
nteetor

10

Bu sorunun doğru cevabı yok. Verilerle ne yaptığınıza gerçekten bağlıyım. Yukarıdaki basit örnekte şunu şiddetle öneririm:

  1. İşleri mümkün olduğunca basit tutun.
  2. Mümkün olan her yerde, işlevlerinizi vektörize tutmak en iyi uygulamadır. Bu, uzun vadede en yüksek esneklik ve hızı sağlar.

Yukarıdaki 1 ve 2 değerlerinin isimlere sahip olması önemli mi? Başka bir deyişle, bu örnekte 1 ve 2'nin sadece r [1] ve r [2] yerine a ve b olarak adlandırılması neden önemlidir? Bu bağlamda anlaşılması gereken önemli bir şey, a ve b'nin de her iki uzunluk vektörü olduğudur. Bu nedenle, bu atamayı yapma sürecinde, referans alınacak:

> 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

Dizinden daha çok harfe başvurmayı tercih ederseniz, adları orijinal vektöre atayabilirsiniz:

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

[Düzenle] ayrı ayrı her bir vektör için min ve max tatbik edilecektir göz önüne alındığında, a ve b olması durumunda (ya da veri çerçevesi (a ve b, aynı uzunluğa ve aynı veri türü olduğu takdirde) bir matris kullanılarak, ya tavsiye ediyor aynı uzunlukta ancak farklı veri türleri olabilir) veya son örneğinizdeki gibi bir liste kullanın (farklı uzunluklarda ve veri türlerinde olabilirler).

> 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

Yorumlarınızı eklemek için soruyu düzenledi. Teşekkürler. gibi şeylere isim vermek r[1], işleri daha net hale getirmeye yardımcı olabilir (tamam, aonların yerine isimler gelirse değil ).
mariotomo

5

Listeler bu amaç için mükemmel görünüyor. Örneğin;

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

ana program

x = returnlist[[1]]

y = returnlist[[2]]

4
List ("x", "y") <-returnlist () gibi her iki değişkeni tek bir kodda nasıl atayabilirsiniz? Diyorum ki, eğer listede çok fazla eleman varsa, tüm fonksiyonu birkaç kez çalıştırmanız gerekir ve bu bir zamana mal olur.
skan

4

İkinci ve üçüncü sorularınıza evet - bir ödevin solunda birden fazla 'değer' bulunamayacağınız için yapmanız gereken budur.


3

Atamayı kullanmaya ne dersiniz?

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

İletmek istediğiniz değişkenin adlarını referans olarak iletebilirsiniz.

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

Mevcut değerlere erişmeniz gerekiyorsa, bunun tersi assignde geçerlidir get.


... ancak bu, o ortamda alıcı değişkenlerin adlarını bilmenizi gerektirir
smci

@smci Evet. : Söz konusu "adlı liste" yöntemi genellikle daha iyi olmasının nedeni budur r <- function() { return(list(first=1, second=2)) }ve kullanarak sonuçları referans r$firstve r$second.
Steve Pitchers

2
Bir kez fonksiyonunuzu edindiğinizde list ("x", "y") <- functionReturningTwoValues ​​('a', 'b') gibi her iki değişkeni tek bir kodda nasıl atayabilirsiniz? Listede çok sayıda öğeniz varsa, tüm işlevi birkaç kez çalıştırmanız gerekeceğini ve bunun bir zamana mal olması gerektiğini söylüyorum
skan

3

İşlevinizin çıktısını Global Ortama geri döndürmek istiyorsanız list2env, bu örnekte olduğu gibi kullanabilirsiniz :

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)

Bu işlev Global Ortamınızda üç nesne oluşturur:

> 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] Eğer her foo ve bar tek bir sayı ise, c (foo, bar) ile ilgili yanlış bir şey yoktur; ve ayrıca bileşenleri adlandırabilirsiniz: c (Foo = foo, Bar = bar). Böylece sonuç 'res' bileşenlerine res [1], res [2] olarak erişebilirsiniz; veya belirtilen durumda, res ["Foo"] olarak res ["BAR"].

[B] Eğer foo ve bar aynı tip ve uzunlukta vektörler ise, tekrar cbind (foo, bar) veya rbind (foo, bar) döndürmekle ilgili yanlış bir şey yoktur; aynı şekilde adlandırılabilir. 'Cbind' durumunda, foo ve çubuğa res [, 1], res [, 2] veya res [, "Foo"], res [, "Bar"] olarak erişirsiniz. Ayrıca bir matris yerine bir veri çerçevesi döndürmeyi de tercih edebilirsiniz:

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

ve res $ Foo, res $ Bar olarak erişebilirsiniz. Eğer foo ve çubuk aynı uzunluktaysa, ancak aynı tipte değilse, bu da işe yarar (örneğin foo bir sayı vektörü, karakter dizgilerinin bir vektörüdür).

[C] Foo ve bar, yukarıdaki gibi uygun bir şekilde birleştirilmeyecek kadar farklıysa, kesinlikle bir liste döndürmelisiniz.

Örneğin, işleviniz doğrusal bir modele sığabilir ve öngörülen değerleri hesaplayabilir, böylece

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

ve sonra sen return list(Foo=foo,Bar=bar) özete res $ Foo, tahmin edilen değerler res $ Bar olarak erişirsiniz.

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


-1

Bir işlevden birden çok çıktı almak ve bunları istenen biçimde tutmak için, çıktıları işlevin içinden sabit diskinize (çalışma dizinine) kaydedebilir ve daha sonra bunları işlevin dışından yükleyebilirsiniz:

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

-1

R 3.6.1 ile aşağıdakileri yapabilirim

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.