Як призначити функцію, яка повертає більше одного значення?


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

--- відредаговано, щоб відповісти на запитання Шейна ---

Мені дійсно не потрібно давати імена частинам результатів із значенням. Я застосовую одну сукупну функцію до першого компонента, а іншу - до другого компонента ( minі, maxякщо це була однакова функція для обох компонентів, я б не потребував їх поділу).


7
FYI, ще один спосіб повернути кілька значень - це встановити attrзначення, що повертається.
Джонатан Чанг

Це еквівалент розпакування кортежу Python.
smci

Відповіді:


186

(1) список [...] <- Я публікував це більше десяти років тому на r-help . З тих пір він був доданий до пакету 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]]буде достатньо.)

Дивіться цитовану нитку довідки для додаткової інформації.

(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 :-)
Стів Ліаноглоу

47

Зазвичай я загортаю вихід у список, який є дуже гнучким (ви можете мати будь-яку комбінацію чисел, рядків, векторів, матриць, масивів, списків, об'єктів, що виводяться)

так, як:

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), але він не працює.
скан

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

Я думаю, це працює.


11

Я вирішив вирішити цю проблему. 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]? Одна важлива річ , щоб зрозуміти в цьому контексті є те , що а і Ь також як вектори довжини 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 до кожного вектора окремо, я б запропонував або використовувати матрицю (якщо a і b будуть однакової довжини та одного типу даних) або кадр даних (якщо 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 ()? Я говорю це тому, що якщо у вас багато елементів у списку, вам потрібно було б запустити всю функцію кілька разів, і це коштує часу.
скан

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

Якщо вам необхідно отримати доступ існуючих значень, зворотні assignє get.


... але це вимагає, щоб ви знали імена приймаючих змінних у цьому середовищі
smci

@smci Так. Ось чому метод "названого списку" у питанні, як правило, краще: r <- function() { return(list(first=1, second=2)) }і посилатися на результати, використовуючи r$firstіr$second .
Стів Пітчерс

2
Як тільки ви отримаєте свою функцію, як ви можете призначити обидві змінні в одній команді, такі як список ("x", "y") <- functionReturningTwoValues ​​('a', 'b')? Я говорю про те, що якщо у вас є багато елементів у списку, вам потрібно було б запустити всю функцію кілька разів, і це коштує часу
скан

3

Якщо ви хочете повернути результат своєї функції у глобальне середовище, ви можете використовувати 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")

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.