วิธีการกำหนดจากฟังก์ชั่นที่ส่งกลับมากกว่าหนึ่งค่า?


223

ยังคงพยายามที่จะเข้าสู่ตรรกะ R ... วิธี "ที่ดีที่สุด" ในการแกะ (บน LHS) ผลลัพธ์จากฟังก์ชันที่คืนค่าหลายค่าคืออะไร

เห็นได้ชัดว่าฉันทำสิ่งนี้ไม่ได้:

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

ฉันต้องทำสิ่งต่อไปนี้จริงๆเหรอ?

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

หรือโปรแกรมเมอร์ R จะเขียนอะไรบางอย่างมากกว่านี้:

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

--- แก้ไขเพื่อตอบคำถามของ Shane ---

ฉันไม่ต้องการให้ชื่อกับส่วนของค่าผลลัพธ์ ฉันใช้ฟังก์ชันการรวมหนึ่งฟังก์ชันกับองค์ประกอบแรกและอีกฟังก์ชันหนึ่งกับองค์ประกอบที่สอง ( minและmax. หากเป็นฟังก์ชันเดียวกันสำหรับส่วนประกอบทั้งสองฉันไม่จำเป็นต้องแยกมันออก)


7
FYI, อีกวิธีในการคืนค่าหลายค่าคือการตั้งค่าการattrส่งคืนของคุณ
Jonathan Chang

นี่คือสิ่งที่เทียบเท่ากับการปลด tuple ของ Python
smci

คำตอบ:


186

(1) รายการ [ ... ] <-ผมได้โพสต์นี้ในช่วงทศวรรษที่ผ่านมาในR-ช่วยเหลือ ตั้งแต่นั้นมามันถูกเพิ่มไปยังแพ็คเกจ gsubfn ไม่ต้องการตัวดำเนินการพิเศษ แต่ไม่ต้องการให้เขียนทางด้านซ้ายโดยใช้list[...]ดังนี้:

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

หากคุณต้องการเพียงแค่องค์ประกอบแรกหรือองค์ประกอบที่สองสิ่งเหล่านี้ก็ใช้ได้เช่นกัน:

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

(แน่นอนถ้าคุณต้องการเพียงหนึ่งค่าแล้วfunctionReturningTwoValues()[[1]]หรือfunctionReturningTwoValues()[[2]]จะเพียงพอ.)

ดูตัวอย่างหัวข้อเพิ่มเติมที่อ้างถึง r-help

(2) ด้วย หากเจตนาเพียงเพื่อรวมค่าหลายค่าในภายหลังและชื่อค่าตอบแทนนั้นเป็นทางเลือกง่าย ๆ คือใช้with:

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

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

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

(3) แนบอีกทางเลือกหนึ่งคือแนบ:

attach(myfun())
a + b

เพิ่ม: withและattach


25
ฉันยอมรับคำตอบของคุณเนื่องจาก "กับ" แต่ฉันไม่สามารถทำซ้ำสิ่งที่คุณอธิบายสำหรับการใช้งานด้านซ้ายมือของ "รายการ" สิ่งที่ฉันได้รับคือ "วัตถุ 'a' ไม่พบ"
mariotomo

4
มันใช้งานได้สำหรับฉัน คุณลองทำอะไร คุณอ่านโพสต์ที่เชื่อมโยงแล้วติดตามหรือไม่ คุณกำหนดlistและ[<-.resultตามที่แสดงไว้ที่นั่นหรือไม่?
G. Grothendieck

12
@ G.Grothendieck คุณจะรังเกียจไหมถ้าฉันใส่เนื้อหาของลิงก์ลงในคำตอบของคุณ? ฉันคิดว่ามันจะทำให้คนอื่นใช้งานได้ง่ายขึ้น
merlin2011

12
ฉันเห็นด้วยกับ @ merlin2011; ตามที่เขียนดูเหมือนว่าไวยากรณ์นี้จะถูกฝังลงในฐาน R
Knowah

6
@ G.Grothendieck ฉันเห็นด้วยกับ merlin2011 และ Knowah - จะดีที่สุดถ้ารหัสจริงที่สำคัญที่นี่ (รหัสอ้างอิงในลิงค์) อยู่ในคำตอบ อาจไม่ใช่ความคิดที่ดีที่จะพูดถึงว่าวัตถุผลลัพธ์ไม่จำเป็นต้องมีชื่อรายการ ที่ทำให้ฉันสับสนเล็กน้อยก่อนที่จะอ่านรหัสที่แท้จริงของคุณ ตามที่ระบุไว้คำตอบบอกว่าคุณจำเป็นต้องรันโค้ดในลิงค์ แต่คนส่วนใหญ่จะไม่อ่านโค้ดนั้นทันทีเว้นแต่จะอยู่ในคำตอบโดยตรง - สิ่งนี้จะให้ความรู้สึกว่าไวยากรณ์นี้อยู่ในฐาน R
Dason

68

ฉันสะดุดแฮ็คที่ฉลาดคนนี้บนอินเทอร์เน็ต ... ฉันไม่แน่ใจว่ามันน่ารังเกียจหรือสวยงาม แต่มันช่วยให้คุณสร้างโอเปอเรเตอร์ "เวทมนต์" ที่ช่วยให้คุณสามารถแกะคืนค่าหลายค่าลงในตัวแปรของตัวเอง :=ฟังก์ชั่นที่มีการกำหนดที่นี่และรวมอยู่ด้านล่างสำหรับลูกหลาน:

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

ด้วยสิ่งที่อยู่ในมือคุณสามารถทำสิ่งที่คุณตามมา:

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

ฉันไม่รู้ว่าฉันรู้สึกอย่างไร บางทีคุณอาจพบว่ามีประโยชน์ในพื้นที่ทำงานแบบโต้ตอบของคุณ การใช้มันเพื่อสร้าง (อีกครั้ง) ห้องสมุดที่ใช้งานได้ (สำหรับการบริโภคจำนวนมาก) อาจไม่ใช่ความคิดที่ดีที่สุด แต่ฉันคิดว่ามันขึ้นอยู่กับคุณ

... คุณรู้ว่าพวกเขาพูดอะไรเกี่ยวกับความรับผิดชอบและพลัง ...


12
นอกจากนี้ผมต้องการกีดกันมันมากขึ้นกว่านี้เมื่อฉันเดิมโพสต์คำตอบนี้ตั้งแต่data.tableแพคเกจใช้:=mucho ผู้ประกอบการในลักษณะที่เป็นการง่ายกว่ามาก :-)
สตีฟ Lianoglou

47

ฉันมักจะห่อผลลัพธ์ลงในรายการซึ่งมีความยืดหยุ่นมาก (คุณสามารถรวมกันของตัวเลข, สตริง, เวกเตอร์, เมทริกซ์, อาร์เรย์, รายการ, วัตถุ int เขาเอาท์พุท)

ชอบมาก:

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) ฉันต้องการให้ผลลัพธ์เป็นวัตถุสองชิ้น? ฉันได้ลองใช้รายการ ("a", "b") <-func2 (5) แต่มันไม่ทำงาน
skan

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

ฉันคิดว่ามันใช้งานได้


11

ฉันรวบรวมzeallot ของแพ็คเกจ R เพื่อจัดการปัญหานี้ zeallot %<-%รวมถึงการกำหนดหลายหรือดำเนินการกำหนดเอาออก, LHS ของผู้ประกอบการเป็นจำนวนของตัวแปรที่จะกำหนดใด ๆ c()สร้างขึ้นโดยใช้การโทรไปยัง RHS ของผู้ประกอบการเป็นเวกเตอร์, รายการ, กรอบข้อมูล, วัตถุวันที่หรือวัตถุที่กำหนดเองใด ๆ ที่มีdestructureวิธีการดำเนินการ (ดู?zeallot::destructure )

นี่คือตัวอย่างจำนวนหนึ่งที่ยึดตามโพสต์ต้นฉบับ

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

ตรวจสอบบทความของแพคเกจสำหรับข้อมูลเพิ่มเติมและตัวอย่าง


มีไวยากรณ์พิเศษเพื่อเก็บหลายแปลงเป็นผลลัพธ์โดยใช้วิธีนี้หรือไม่
mrpargeter

2
ไม่จำเป็นต้องใช้ไวยากรณ์พิเศษคุณสามารถกำหนดรายการของวัตถุพล็อตได้เช่นเดียวกับรายการตัวเลข
nteetor

10

ไม่มีคำตอบที่ถูกต้องสำหรับคำถามนี้ ฉันขึ้นอยู่กับสิ่งที่คุณทำกับข้อมูลจริงๆ ในตัวอย่างง่ายๆข้างต้นฉันขอแนะนำอย่างยิ่ง:

  1. ทำสิ่งต่าง ๆ ให้ง่ายที่สุดเท่าที่จะทำได้
  2. หากเป็นไปได้มันเป็นวิธีปฏิบัติที่ดีที่สุดในการทำให้ฟังก์ชั่นของคุณเป็นแบบเวกเตอร์ ที่ให้ความยืดหยุ่นและความเร็วสูงสุดในระยะยาว

เป็นสิ่งสำคัญหรือไม่ที่ค่า 1 และ 2 ข้างต้นมีชื่อ? ทำไมมันจึงมีความสำคัญในตัวอย่างนี้ที่ 1 และ 2 ชื่อ a และ b มากกว่าแค่ r [1] และ r [2] สิ่งหนึ่งที่สำคัญที่จะเข้าใจในบริบทนี้คือ a และ b เป็นยังทั้งพาหะของความยาว 1. เพื่อให้คุณไม่ได้จริงๆการเปลี่ยนแปลงอะไรในกระบวนการของการมอบหมายงานที่นอกเหนือจากการมี 2 เวกเตอร์ใหม่ที่ไม่จำเป็นต้องห้อยไป อ้างอิง:

> 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

คุณยังสามารถกำหนดชื่อให้กับเวกเตอร์ดั้งเดิมหากคุณต้องการอ้างอิงจดหมายมากกว่าดัชนี:

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

[แก้ไข]ระบุว่าคุณจะใช้ min และ max กับแต่ละเวกเตอร์แยกกันฉันขอแนะนำให้ใช้ matrix (ถ้า a และ b จะมีความยาวเท่ากันและชนิดข้อมูลเดียวกัน) หรือ data frame (ถ้า a และ b จะเป็น ความยาวเท่ากัน แต่อาจเป็นประเภทข้อมูลที่แตกต่างกัน) หรือใช้รายการเช่นในตัวอย่างสุดท้ายของคุณ (หากมีความยาวแตกต่างกันและชนิดข้อมูล)

> 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

แก้ไขคำถามเพื่อรวมข้อสังเกตของคุณ ขอบคุณ การให้ชื่อกับสิ่งต่าง ๆ เช่นr[1]สามารถช่วยทำให้สิ่งต่าง ๆ ชัดเจนขึ้น (ไม่เป็นไรถ้าชื่อaนั้นเข้ามาแทนที่)
mariotomo

5

รายการดูเหมือนจะสมบูรณ์แบบสำหรับจุดประสงค์นี้ ตัวอย่างเช่นภายในฟังก์ชั่นที่คุณจะมี

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

โปรแกรมหลัก

x = returnlist[[1]]

y = returnlist[[2]]

4
คุณจะกำหนดตัวแปรทั้งสองให้อยู่ในคำสั่งเดียวเช่น list ("x", "y") <-returnlist () ได้อย่างไร ฉันบอกว่าเพราะถ้าคุณมีองค์ประกอบหลายอย่างในรายการคุณจะต้องเรียกใช้ฟังก์ชั่นทั้งหมดหลายครั้งและเสียเวลา
skan

4

ใช่สำหรับคำถามที่สองและสามของคุณ - นั่นคือสิ่งที่คุณต้องทำเนื่องจากคุณไม่สามารถมี 'ค่า' หลายตัวทางด้านซ้ายของการมอบหมาย


3

วิธีใช้งานมอบหมาย?

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

คุณสามารถส่งชื่อตัวแปรที่คุณต้องการส่งผ่านโดยการอ้างอิง

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

ถ้าคุณต้องการเข้าถึงค่าที่มีอยู่ในการสนทนาของมีassignget


... แต่สิ่งนี้ต้องการให้คุณรู้ชื่อของตัวแปรที่รับในสภาพแวดล้อมนั้น
smci

@smci ใช่ นั่นคือเหตุผลที่ "ชื่อรายการ" วิธีการในคำถามโดยทั่วไปดีขึ้น: r <- function() { return(list(first=1, second=2)) }และการอ้างอิงผลการใช้และr$first r$second
Steve Pitchers

2
เมื่อคุณมีฟังก์ชั่นของคุณคุณจะกำหนดตัวแปรทั้งสองให้อยู่ในคำสั่งเดียวได้อย่างไรเช่น list ("x", "y") <- functionReturningTwoValues ​​('a', 'b') ฉันบอกว่าเพราะถ้าคุณมีองค์ประกอบหลายอย่างในรายการคุณจะต้องเรียกใช้ฟังก์ชั่นทั้งหมดหลายครั้งและเสียค่าใช้จ่ายครั้งหนึ่ง
Skan

3

หากคุณต้องการส่งคืนเอาต์พุตของฟังก์ชันของคุณไปยัง Global Environment คุณสามารถใช้list2envเช่นในตัวอย่างนี้:

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)

ฟังก์ชั่นนี้จะสร้างวัตถุสามชิ้นในสภาพแวดล้อมโลกของคุณ:

> 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] ถ้า foo และ bar แต่ละตัวเป็นตัวเลขเดียวนั่นหมายความว่า c (foo, bar) นั้นไม่มีอะไรผิดปกติ และคุณยังสามารถตั้งชื่อส่วนประกอบ: c (Foo = foo, Bar = bar) ดังนั้นคุณสามารถเข้าถึงส่วนประกอบของผลลัพธ์ 'res' ตาม res [1], res [2]; หรือในกรณีที่มีชื่อเช่น res ["Foo"], res ["BAR"]

[B] ถ้า foo และ bar เป็นเวกเตอร์ที่มีประเภทและความยาวเท่ากันก็ไม่มีอะไรผิดปกติกับการคืนค่า cbind (foo, bar) หรือ rbind (foo, bar); เช่นเดียวกันชื่อ ในกรณี 'cbind' คุณจะเข้าถึง foo และ bar เช่น res [, 1], res [, 2] หรือ res [, "Foo"], res [, "Bar"] คุณอาจต้องการส่งคืนชื่อไฟล์ไม่ใช่เมทริกซ์:

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

และเข้าถึงพวกเขาในฐานะ res $ Foo res $ Bar สิ่งนี้จะใช้ได้ดีหาก foo และ bar มีความยาวเท่ากัน แต่ไม่ใช่ประเภทเดียวกัน (เช่น foo เป็นเวกเตอร์ของตัวเลข, bar เป็นเวกเตอร์ของสตริงอักขระ)

[C] ถ้า foo และ bar แตกต่างกันพอที่จะไม่รวมกันอย่างสะดวกดังที่กล่าวมาแล้วคุณจะต้องส่งคืนรายการอย่างแน่นอน

ตัวอย่างเช่นฟังก์ชั่นของคุณอาจพอดีกับตัวแบบเชิงเส้นและคำนวณค่าที่ทำนายไว้ดังนั้นคุณอาจมี

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

แล้วคุณจะ return list(Foo=foo,Bar=bar)สามารถเข้าถึงการสรุปได้เช่น res $ Foo ค่าที่ทำนายไว้เป็น res $ Bar

แหล่งที่มา: http://r.789695.n4.nabble.com/How-to-return-multiple-values-in-a-function-td858528.html


-1

ในการรับเอาท์พุทจำนวนมากจากฟังก์ชั่นและเก็บไว้ในรูปแบบที่ต้องการคุณสามารถบันทึกเอาท์พุทลงในฮาร์ดดิสก์ของคุณ (ในไดเรกทอรีทำงาน) จากภายในฟังก์ชั่นแล้วโหลดจากนอกฟังก์ชั่น:

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

-1

ด้วย R 3.6.1 ฉันสามารถทำสิ่งต่อไปนี้

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.