Làm thế nào để gán từ một hàm trả về nhiều hơn một giá trị?


223

Vẫn đang cố gắng đi vào logic R ... cách "tốt nhất" để giải nén (trên LHS) kết quả từ một hàm trả về nhiều giá trị là gì?

Tôi không thể làm điều này rõ ràng:

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

Tôi thực sự phải làm như sau?

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

hoặc lập trình viên R sẽ viết một cái gì đó giống như thế này:

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

--- chỉnh sửa để trả lời câu hỏi của Shane ---

Tôi thực sự không cần đặt tên cho các phần giá trị kết quả. Tôi đang áp dụng một hàm tổng hợp cho thành phần đầu tiên và một chức năng khác cho thành phần thứ hai ( minmax. Nếu đó là cùng một chức năng cho cả hai thành phần tôi sẽ không cần phải tách chúng).


7
FYI, một cách khác để trả về nhiều giá trị là đặt attrgiá trị trả về của bạn.
Jonathan Chang

Điều này tương đương với việc giải nén tuple của Python.
smci 20/03/2015

Câu trả lời:


186

(1) danh sách [...] <- Tôi đã đăng bài này hơn một thập kỷ trước trên r-help . Kể từ đó, nó đã được thêm vào gói gsubfn. Nó không yêu cầu một toán tử đặc biệt nhưng yêu cầu phía bên trái được viết bằng cách sử dụng list[...]như sau:

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

Nếu bạn chỉ cần thành phần thứ nhất hoặc thứ hai thì tất cả đều hoạt động:

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

(Tất nhiên, nếu bạn chỉ cần một giá trị sau đó functionReturningTwoValues()[[1]]hoặc functionReturningTwoValues()[[2]]sẽ là đủ.)

Xem chủ đề r-help trích dẫn để biết thêm ví dụ.

(2) với Nếu mục đích chỉ đơn thuần là kết hợp nhiều giá trị sau đó và các giá trị trả về được đặt tên thì một cách thay thế đơn giản là sử dụng with:

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

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

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

(3) đính kèm Một cách khác là đính kèm:

attach(myfun())
a + b

THÊM: withattach


25
Tôi chấp nhận câu trả lời của bạn vì sự "với", nhưng tôi không thể tái tạo những gì bạn mô tả cho việc sử dụng ở phía bên tay trái của "danh sách", tất cả tôi nhận được là "đối tượng 'a' không tìm thấy"
mariotomo

4
Nó làm việc cho tôi. Bạn đã thử gì? Bạn đã đọc bài viết liên kết và theo dõi nó? Bạn đã xác định list[<-.resultnhư thể hiện ở đó?
G. Grothendieck

12
@ G.Grothendieck, bạn có phiền nếu tôi đưa nội dung liên kết của bạn vào câu trả lời của bạn không? Tôi nghĩ nó sẽ giúp mọi người sử dụng nó dễ dàng hơn.
merlin2011

12
Tôi đồng ý với @ merlin2011; như đã viết, có vẻ như cú pháp này được nhúng vào cơ sở R.
biết

6
@ G.Grothendieck Tôi đồng ý với merlin2011 và knowah - sẽ tốt nhất nếu mã thực tế quan trọng ở đây (mã được tham chiếu trong liên kết) có trong câu trả lời. Nó có thể không phải là một ý tưởng tồi để đề cập rằng đối tượng kết quả không cần phải được đặt tên danh sách. Điều đó làm tôi bối rối một chút trước khi đọc mã thực tế của bạn. Như đã đề cập, câu trả lời nói rằng bạn cần chạy mã trong liên kết nhưng hầu hết mọi người sẽ không đọc mã đó ngay lập tức trừ khi đó là câu trả lời trực tiếp - điều này mang lại ấn tượng rằng cú pháp này nằm trong cơ sở R.
Dason

68

Tôi bằng cách nào đó đã vấp phải sự hack thông minh này trên internet ... Tôi không chắc nó khó chịu hay đẹp, nhưng nó cho phép bạn tạo một toán tử "ma thuật" cho phép bạn giải nén nhiều giá trị trả về vào biến của riêng họ. Các :=chức năng được định nghĩa ở đây , và bao gồm dưới đây cho hậu thế:

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

Với điều đó trong tay, bạn có thể làm những gì bạn đang theo đuổi:

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

Tôi không biết tôi cảm thấy thế nào về điều đó. Có lẽ bạn có thể thấy nó hữu ích trong không gian làm việc tương tác của bạn. Sử dụng nó để xây dựng (tái) các thư viện có thể sử dụng (để tiêu thụ hàng loạt) có thể không phải là ý tưởng tốt nhất, nhưng tôi đoán điều đó tùy thuộc vào bạn.

... bạn biết họ nói gì về trách nhiệm và quyền lực ...


12
Ngoài ra, tôi không khuyến khích nó nhiều hơn bây giờ so với khi tôi đăng câu trả lời này ban đầu vì gói data.table sử dụng :=toán tử mucho theo cách dễ dàng hơn nhiều :-)
Steve Lianoglou

47

Thông thường tôi gói đầu ra thành một danh sách, rất linh hoạt (bạn có thể có bất kỳ sự kết hợp nào của số, chuỗi, vectơ, ma trận, mảng, danh sách, đối tượng int anh ấy xuất)

rất thích

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

Điều gì xảy ra nếu thay vì đầu ra <-func2 (5) Tôi muốn có kết quả trong hai đối tượng? Tôi đã thử với danh sách ("a", "b") <-func2 (5) nhưng không được.
skan

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

Tôi nghĩ rằng điều này làm việc.


11

Tôi kết hợp một gói zeallot R để giải quyết vấn đề này. zeallot bao gồm một toán tử gán nhiều lần hoặc giải nén , %<-%. LHS của toán tử là bất kỳ số lượng biến nào để gán, được xây dựng bằng các lệnh gọi đến c(). RHS của toán tử là một vectơ, danh sách, khung dữ liệu, đối tượng ngày hoặc bất kỳ đối tượng tùy chỉnh nào có destructurephương thức được thực hiện (xem ?zeallot::destructure).

Dưới đây là một số ví dụ dựa trên bài viết gốc,

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

Kiểm tra họa tiết gói để biết thêm thông tin và ví dụ.


Có một cú pháp đặc biệt để lưu trữ một số lô làm đầu ra bằng phương pháp này không?
mrpargeter

2
Không cần cú pháp đặc biệt, bạn có thể chỉ định một danh sách các đối tượng cốt truyện như bạn sẽ liệt kê danh sách các số.
nteetor

10

Không có câu trả lời đúng cho câu hỏi này. Tôi thực sự phụ thuộc vào những gì bạn đang làm với dữ liệu. Trong ví dụ đơn giản ở trên, tôi rất muốn đề xuất:

  1. Giữ mọi thứ đơn giản nhất có thể.
  2. Bất cứ nơi nào có thể, đó là một cách thực hành tốt nhất để giữ cho các chức năng của bạn được vector hóa. Điều đó cung cấp lượng linh hoạt và tốc độ lớn nhất trong thời gian dài.

Điều quan trọng là các giá trị 1 và 2 ở trên có tên không? Nói cách khác, tại sao điều quan trọng trong ví dụ này là 1 và 2 được đặt tên là a và b, thay vì chỉ r [1] và r [2]? Một điều quan trọng cần hiểu trong ngữ cảnh này là a và b cũng là cả hai vectơ có độ dài 1. Vì vậy, bạn không thực sự thay đổi bất cứ điều gì trong quá trình thực hiện nhiệm vụ đó, ngoài việc có 2 vectơ mới không cần đăng ký được tham khảo:

> 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

Bạn cũng có thể gán tên cho vectơ gốc nếu bạn muốn tham chiếu chữ cái hơn chỉ mục:

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

[Chỉnh sửa] Cho rằng bạn sẽ áp dụng riêng tối thiểu và tối đa cho từng vectơ, tôi sẽ đề nghị sử dụng ma trận (nếu a và b sẽ có cùng độ dài và cùng loại dữ liệu) hoặc khung dữ liệu (nếu a và b sẽ là có cùng độ dài nhưng có thể là các loại dữ liệu khác nhau) hoặc người khác sử dụng một danh sách như trong ví dụ trước của bạn (nếu chúng có thể có độ dài và loại dữ liệu khác nhau).

> 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

chỉnh sửa câu hỏi để bao gồm nhận xét của bạn. cảm ơn. đặt tên cho những thứ như r[1]có thể giúp làm cho mọi thứ rõ ràng hơn (được rồi, không phải nếu tên như ađến ở vị trí của họ).
mariotomo

5

Danh sách có vẻ hoàn hảo cho mục đích này. Ví dụ trong hàm bạn sẽ có

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

chương trình chính

x = returnlist[[1]]

y = returnlist[[2]]

4
Làm thế nào bạn có thể gán cả hai biến trong một comand duy nhất, chẳng hạn như danh sách ("x", "y") <-returnlist ()? Tôi nói điều đó bởi vì nếu bạn có nhiều yếu tố trong danh sách, bạn sẽ cần phải chạy toàn bộ chức năng nhiều lần và điều đó sẽ tốn thời gian.
skan

4

Có cho câu hỏi thứ hai và thứ ba của bạn - đó là những gì bạn cần làm vì bạn không thể có nhiều "giá trị" ở bên trái của bài tập.


3

Làm thế nào về việc sử dụng gán?

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

Bạn có thể truyền tên của biến bạn muốn được truyền bằng tham chiếu.

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

Nếu bạn cần truy cập các giá trị hiện có, ngược lại assignget.


... nhưng điều này đòi hỏi bạn phải biết tên của các biến nhận trong môi trường đó
smci 20/03/2015

@smci Vâng. Đó là lý do tại sao phương pháp "danh sách có tên" trong câu hỏi thường tốt hơn: r <- function() { return(list(first=1, second=2)) }và tham chiếu các kết quả bằng cách sử dụng r$firstr$second.
Steve Pitchers

2
Khi bạn có chức năng của mình, làm thế nào bạn có thể gán cả hai biến trong một lần kết hợp, chẳng hạn như danh sách ("x", "y") <- functionReturnTwoValues ​​('a', 'b')? Tôi nói điều đó bởi vì nếu bạn có nhiều yếu tố trong danh sách, bạn sẽ cần phải chạy toàn bộ chức năng nhiều lần và điều đó sẽ tốn thời gian
skan

3

Nếu bạn muốn trả lại đầu ra của hàm cho Môi trường toàn cầu, bạn có thể sử dụng list2env, như trong ví dụ này:

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)

Hàm này sẽ tạo ba đối tượng trong Môi trường toàn cầu của bạn:

> 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] Nếu mỗi foo và bar là một số duy nhất, thì không có gì sai với c (foo, bar); và bạn cũng có thể đặt tên cho các thành phần: c (Foo = foo, Bar = bar). Vì vậy, bạn có thể truy cập các thành phần của kết quả 'res' là res [1], res [2]; hoặc, trong trường hợp được đặt tên, như res ["Foo"], res ["BAR"].

[B] Nếu foo và bar là các vectơ cùng loại và độ dài, thì một lần nữa, không có gì sai khi trả về cbind (foo, bar) hoặc rbind (foo, bar); tương tự như vậy có thể đặt tên. Trong trường hợp 'cbind', bạn sẽ truy cập foo và bar dưới dạng res [, 1], res [, 2] hoặc dưới dạng res [, "Foo"], res [, "Bar"]. Bạn cũng có thể muốn trả về một khung dữ liệu hơn là một ma trận:

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

và truy cập chúng dưới dạng res $ Foo, res $ Bar. Điều này cũng sẽ hoạt động tốt nếu foo và thanh có cùng độ dài nhưng không cùng loại (ví dụ: foo là một vectơ của các số, thanh một vectơ của các chuỗi ký tự).

[C] Nếu foo và thanh đủ khác nhau để không kết hợp thuận tiện như trên, thì bạn chắc chắn sẽ trả về một danh sách.

Ví dụ: hàm của bạn có thể phù hợp với mô hình tuyến tính và cũng tính toán các giá trị dự đoán, do đó bạn có thể có

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

và sau đó bạn sẽ return list(Foo=foo,Bar=bar)và sau đó truy cập vào bản tóm tắt dưới dạng res $ Foo, các giá trị được dự đoán là res $ Bar

nguồn: http://r.789695.n4.nabble.com/How-to-return-multipl-values-in-a-feft-td858528.html


-1

Để có được nhiều đầu ra từ một hàm và giữ chúng ở định dạng mong muốn, bạn có thể lưu các đầu ra vào đĩa cứng (trong thư mục làm việc) từ bên trong hàm và sau đó tải chúng từ bên ngoài hàm:

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

-1

Với R 3.6.1, tôi có thể làm như sau

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.