Bagaimana cara menetapkan dari fungsi yang mengembalikan lebih dari satu nilai?

223

Masih mencoba masuk ke logika R ... apa cara "terbaik" untuk membongkar (pada LHS) hasil dari fungsi yang mengembalikan beberapa nilai?

Rupanya saya tidak bisa melakukan ini:

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

haruskah saya benar-benar melakukan hal berikut?

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

atau akankah programmer R menulis sesuatu seperti ini:

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

--- diedit untuk menjawab pertanyaan Shane ---

Saya tidak benar-benar perlu memberi nama ke bagian nilai hasil. Saya menerapkan satu fungsi agregat ke komponen pertama dan yang lain ke komponen kedua ( mindan max. Jika itu adalah fungsi yang sama untuk kedua komponen saya tidak perlu membelah mereka).

mariotomo
sumber
7
FYI, cara lain untuk mengembalikan beberapa nilai adalah dengan menetapkan attrnilai pengembalian Anda.
Jonathan Chang
Ini setara dengan tuple-unpacking Python.
smci

Jawaban:

186

(1) daftar [...] <- Saya telah memposting ini lebih dari satu dekade yang lalu di r-help . Sejak itu telah ditambahkan ke paket gsubfn. Itu tidak memerlukan operator khusus tetapi mengharuskan sisi kiri ditulis menggunakan list[...]seperti ini:

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

Jika Anda hanya membutuhkan komponen pertama atau kedua ini semua berfungsi juga:

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

(Tentu saja, jika Anda hanya membutuhkan satu nilai saja functionReturningTwoValues()[[1]]ataufunctionReturningTwoValues()[[2]] akan cukup.)

Lihat utas r-bantuan yang dikutip untuk contoh lebih lanjut.

(2) dengan Jika maksudnya hanya untuk menggabungkan beberapa nilai kemudian dan nilai kembali dinamai maka alternatif sederhana adalah dengan menggunakan with:

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

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

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

(3) melampirkan Alternatif lain adalah melampirkan:

attach(myfun())
a + b

TAMBAH: withdanattach

G. Grothendieck
sumber
25
Saya menerima jawaban Anda karena "dengan", tetapi saya tidak dapat mereproduksi apa yang Anda gambarkan untuk penggunaan "daftar" di sebelah kiri, yang saya dapatkan adalah "objek 'a' tidak ditemukan"
mariotomo
4
Ini bekerja untuk saya. Apa yang kamu coba? Apakah Anda membaca posting yang ditautkan dan mengikutinya? Apakah Anda mendefinisikan listdan [<-.resultseperti yang ditunjukkan di sana?
G. Grothendieck
12
@ G.Grothendieck, Apakah Anda keberatan jika saya memasukkan konten tautan Anda ke dalam jawaban Anda? Saya pikir itu akan membuat lebih mudah bagi orang untuk menggunakannya.
merlin2011
12
Saya setuju dengan @ merlin2011; seperti yang ditulis sepertinya sintaks ini tertanam ke dalam basis R.
knowah
6
@ G.Grothendieck Saya setuju dengan merlin2011 dan knowah - akan lebih baik jika kode aktual yang penting di sini (kode yang dirujuk dalam tautan) ada dalam jawabannya. Mungkin bukan ide yang buruk untuk menyebutkan bahwa objek hasil tidak perlu dinamai daftar. Itu membingungkan saya sebentar sebelum membaca kode Anda yang sebenarnya. Seperti disebutkan, jawabannya mengatakan bahwa Anda perlu menjalankan kode di tautan tetapi kebanyakan orang tidak akan langsung membaca kode itu kecuali ada dalam jawaban langsung - ini memberi kesan bahwa sintaks ini ada di basis R.
Dason
68

Saya entah bagaimana tersandung pada peretasan pintar ini di internet ... Saya tidak yakin apakah ini jahat atau indah, tetapi ini memungkinkan Anda membuat operator "ajaib" yang memungkinkan Anda membongkar beberapa nilai balik ke dalam variabel mereka sendiri. The :=Fungsi didefinisikan di sini , dan termasuk di bawah ini untuk anak cucu:

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

Dengan itu, Anda dapat melakukan apa yang Anda cari:

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

Saya tidak tahu bagaimana perasaan saya tentang itu. Mungkin Anda mungkin merasa terbantu di ruang kerja interaktif Anda. Menggunakannya untuk membangun (dapat kembali) perpustakaan yang dapat digunakan (untuk konsumsi massal) mungkin bukan ide terbaik, tapi saya rasa itu terserah Anda.

... Anda tahu apa yang mereka katakan tentang tanggung jawab dan kekuasaan ...

Steve Lianoglou
sumber
12
Saya juga akan mengecilkan hati lebih banyak sekarang daripada ketika saya awalnya memposting jawaban ini karena paket data.table menggunakan :=operator mucho dengan cara yang jauh lebih mudah :-)
Steve Lianoglou
47

Biasanya saya membungkus output ke dalam daftar, yang sangat fleksibel (Anda dapat memiliki kombinasi angka, string, vektor, matriks, array, daftar, objek dalam output)

jadi seperti:

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
sumber
Bagaimana jika alih-alih output <-func2 (5) saya ingin memiliki hasil dalam dua objek? Saya sudah mencoba dengan list ("a", "b") <-func2 (5) tetapi tidak berhasil.
skan
13
functionReturningTwoValues <- function() { 
  results <- list()
  results$first <- 1
  results$second <-2
  return(results) 
}
a <- functionReturningTwoValues()

Saya pikir ini berhasil.

Jojo
sumber
11

Saya mengumpulkan paket R zeallot untuk mengatasi masalah ini. zeallot termasuk operator penugasan ganda atau pembongkar tugas %<-%,. LHS operator adalah sejumlah variabel untuk ditetapkan, dibuat menggunakan panggilan c(). RHS operator adalah vektor, daftar, bingkai data, objek tanggal, atau objek kustom apa pun dengan destructuremetode yang diterapkan (lihat ?zeallot::destructure).

Berikut adalah beberapa contoh berdasarkan pada pos asli,

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

Lihatlah sketsa paket untuk informasi dan contoh lebih lanjut.

nteetor
sumber
Apakah ada sintaks khusus untuk menyimpan beberapa plot sebagai output menggunakan metode ini?
mrpargeter
2
Tidak diperlukan sintaks khusus, Anda dapat menetapkan daftar objek plot seperti halnya daftar angka.
nteetor
10

Tidak ada jawaban yang tepat untuk pertanyaan ini. Saya sangat bergantung pada apa yang Anda lakukan dengan data. Dalam contoh sederhana di atas, saya akan sangat menyarankan:

  1. Usahakan sesederhana mungkin.
  2. Sedapat mungkin, ini adalah praktik terbaik untuk menjaga fungsi Anda tetap vektor. Itu memberikan fleksibilitas dan kecepatan terbesar dalam jangka panjang.

Apakah penting bahwa nilai 1 dan 2 di atas memiliki nama? Dengan kata lain, mengapa penting dalam contoh ini bahwa 1 dan 2 diberi nama a dan b, bukan hanya r [1] dan r [2]? Satu hal penting untuk dipahami dalam konteks ini adalah bahwa a dan b juga merupakan vektor dengan panjang 1. Jadi, Anda tidak benar-benar mengubah apa pun dalam proses membuat penugasan itu, selain memiliki 2 vektor baru yang tidak memerlukan subskrip untuk dirujuk:

> 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

Anda juga dapat menetapkan nama ke vektor asli jika Anda lebih suka merujuk surat daripada indeks:

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

[Sunting] Mengingat bahwa Anda akan menerapkan min dan maks ke setiap vektor secara terpisah, saya akan menyarankan menggunakan matriks (jika a dan b akan memiliki panjang dan tipe data yang sama) atau bingkai data (jika a dan b akan menjadi panjang yang sama tetapi dapat berupa tipe data yang berbeda) atau gunakan daftar seperti pada contoh terakhir Anda (jika panjangnya bisa berbeda dan tipe data).

> 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
sumber
mengedit pertanyaan untuk memasukkan komentar Anda. Terima kasih. memberi nama pada hal-hal seperti r[1]dapat membantu memperjelas hal-hal (baik-baik saja, tidak jika nama suka amenggantikannya).
mariotomo
5

Daftar tampaknya sempurna untuk tujuan ini. Misalnya dalam fungsi yang Anda miliki

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

program utama

x = returnlist[[1]]

y = returnlist[[2]]
Arnold
sumber
4
Bagaimana Anda dapat menetapkan kedua variabel dalam satu perintah, seperti daftar ("x", "y") <-returnlist ()? Saya mengatakan itu karena jika Anda banyak elemen dalam daftar, Anda perlu menjalankan seluruh fungsi beberapa kali dan itu menghabiskan waktu.
skan
4

Ya untuk pertanyaan kedua dan ketiga Anda - itulah yang perlu Anda lakukan karena Anda tidak dapat memiliki beberapa 'nilai' di sebelah kiri tugas.

Dirk Eddelbuettel
sumber
3

Bagaimana kalau menggunakan assign?

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

Anda dapat mengirimkan nama-nama variabel yang ingin Anda sampaikan dengan referensi.

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

Jika Anda perlu mengakses nilai yang ada, kebalikannya assignadalah get.

Steve Pitchers
sumber
... tetapi ini mengharuskan Anda untuk mengetahui nama-nama variabel penerima di lingkungan itu
smci
@smci Ya. Itulah mengapa metode "daftar nama" dalam pertanyaan umumnya lebih baik: r <- function() { return(list(first=1, second=2)) }dan merujuk hasil menggunakan r$firstdan r$second.
Steve Pitchers
2
Setelah Anda memiliki fungsi, bagaimana Anda dapat menetapkan kedua variabel dalam satu perintah, seperti daftar ("x", "y") <- functionReturningTwoValues ​​('a', 'b')? Saya mengatakan itu karena jika Anda memiliki banyak elemen dalam daftar, Anda perlu menjalankan seluruh fungsi beberapa kali dan itu menghabiskan waktu
skan
3

Jika Anda ingin mengembalikan output fungsi Anda ke Lingkungan Global, Anda dapat menggunakan list2env, seperti dalam contoh ini:

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)

Fungsi ini akan membuat tiga objek di Lingkungan Global Anda:

> 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
sumber
1

[A] Jika masing-masing foo dan bar adalah angka tunggal, maka tidak ada yang salah dengan c (foo, bar); dan Anda juga dapat memberi nama komponen: c (Foo = foo, Bar = bar). Jadi Anda dapat mengakses komponen hasil 'res' sebagai res [1], res [2]; atau, dalam kasus bernama, sebagai res ["Foo"], res ["BAR"].

[B] Jika foo dan bar adalah vektor dari jenis dan panjang yang sama, maka sekali lagi tidak ada yang salah dengan mengembalikan cbind (foo, bar) atau rbind (foo, bar); juga bisa disebut. Dalam kasus 'cbind', Anda akan mengakses foo dan bar sebagai res [, 1], res [, 2] atau sebagai res [, "Foo"], res [, "Bar"]. Anda mungkin juga lebih suka mengembalikan kerangka data daripada matriks:

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

dan akses mereka sebagai res $ Foo, res $ Bar. Ini juga akan bekerja dengan baik jika foo dan bilah memiliki panjang yang sama tetapi tidak dari jenis yang sama (misalnya foo adalah vektor angka, bilah vektor string karakter).

[C] Jika foo dan bar cukup berbeda untuk tidak digabungkan dengan nyaman seperti di atas, maka Anda harus pasti mengembalikan daftar.

Misalnya, fungsi Anda mungkin cocok dengan model linier dan juga menghitung nilai prediksi, sehingga Anda bisa melakukannya

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

dan kemudian Anda akan melakukannya return list(Foo=foo,Bar=bar) dan kemudian mengakses ringkasan sebagai res $ Foo, nilai yang diprediksi sebagai res $ Bar

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

Prakhar Agrawal
sumber
-1

Untuk mendapatkan beberapa output dari suatu fungsi dan menyimpannya dalam format yang diinginkan, Anda dapat menyimpan output ke hard disk Anda (di direktori kerja) dari dalam fungsi dan kemudian memuatnya dari luar fungsi:

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

Dengan R 3.6.1, saya dapat melakukan hal berikut

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