Как назначить из функции, которая возвращает более одного значения?

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. Если бы это была одна и та же функция для обоих компонентов, мне не нужно было бы их разбивать).

mariotomo
источник
7
К вашему сведению, еще один способ вернуть несколько значений - установить attrвозвращаемое значение.
Джонатан Чанг
Это эквивалент распаковки кортежей в Python.
smci

Ответы:

186

(1) list [...] <- я разместил это более десяти лет назад на 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]]будет достаточно.)

Смотрите процитированную ветку 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как показано там?
Г. Гротендик,
12
@ G.Grothendieck, вы не возражаете, если я добавлю содержание вашей ссылки в ваш ответ? Я думаю, что людям было бы легче его использовать.
merlin2011
12
Я согласен с @ merlin2011; как написано, кажется, что этот синтаксис встроен в R базу.
ноу
6
@ G.Grothendieck Я согласен с merlin2011 и ноу - было бы лучше, если бы фактический код, который важен здесь (код, указанный в ссылке), был в ответе. Не может быть плохой идеей упомянуть, что объекту результата не нужно называть список. Это немного смутило меня, прежде чем читать твой настоящий код. Как уже упоминалось, в ответе говорится, что вам нужно запустить код в ссылке, но большинство людей не собираются читать этот код сразу, если только он не находится в ответе напрямую - это создает впечатление, что этот синтаксис находится в базе R.
Дейсон
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 более удобным способом :-)
Steve Lianoglou
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
Федерико Георгий
источник
Что если вместо output <-func2 (5) я хочу получить результат в двух объектах? Я пробовал со списком ("a", "b") <-func2 (5), но он не работает.
Скан
13
functionReturningTwoValues <- function() { 
  results <- list()
  results$first <- 1
  results$second <-2
  return(results) 
}
a <- functionReturningTwoValues()

Я думаю, что это работает.

Jojo
источник
11

Я собрал пакет R Zeallot для решения этой проблемы. 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, ..)

Проверьте виньетка пакета для получения дополнительной информации и примеров.

nteetor
источник
Существует ли специальный синтаксис для хранения нескольких графиков в качестве выходных данных с использованием этого метода?
mrpargeter
2
Никакого специального синтаксиса не требуется, вы можете назначить список объектов графика так же, как список чисел.
nteetor
10

Там нет правильного ответа на этот вопрос. Я действительно зависит от того, что вы делаете с данными. В приведенном выше простом примере я настоятельно рекомендую:

  1. Сохраняйте вещи максимально простыми.
  2. Везде, где это возможно, рекомендуется сохранять векторизацию ваших функций. Это обеспечивает наибольшую гибкость и скорость в долгосрочной перспективе.

Важно ли, чтобы значения 1 и 2 выше имели имена? Другими словами, почему в этом примере важно, чтобы 1 и 2 были названы a и b, а не просто r [1] и r [2]? В этом контексте важно понимать, что a и b также являются векторами длины 1. Таким образом, вы ничего не меняете в процессе выполнения этого назначения, кроме двух новых векторов, которым не нужны индексы для быть ссылки:

> 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 

[Править] Учитывая, что вы будете применять минимальное и максимальное значения к каждому вектору в отдельности, я бы предложил использовать либо матрицу (если 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приходят на их место).
Мариотомо
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

Да, на ваш второй и третий вопросы - это то, что вам нужно сделать, так как вы не можете иметь несколько «lvalues» слева от задания.

Дирк Эддельбюттель
источник
3

Как насчет использования assign?

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
Если у вас есть функция, как вы можете назначить обе переменные в одной команде, например list ("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
rafa.pereira
источник
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"
radumanolescu
источник