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

StackOverflow https://stackoverflow.com/questions/1826519

Вопрос

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

Это было полезно?

Решение

(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 . Другой альтернативный вариант: attach:

attach(myfun())
a + b

ДОБАВЛЕНО: attach и <=>

Другие советы

Я как-то наткнулся на этот хитрый взлом в интернете ... Я не уверен, что он противный или красивый, но он позволяет вам создать " magic <<>> quot; Оператор, который позволяет распаковать несколько возвращаемых значений в их собственную переменную. := функция определена здесь и включена ниже для потомков:

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

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

... ты знаешь, что они говорят об ответственности и власти ...

Обычно я заключаю вывод в список, который очень гибкий (вы можете иметь любую комбинацию чисел, строк, векторов, матриц, массивов, списков, объектов в выводе)

так нравится:

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
functionReturningTwoValues <- function() { 
  results <- list()
  results$first <- 1
  results$second <-2
  return(results) 
}
a <- functionReturningTwoValues()

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

Я собрал пакет 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, ..)

Проверьте пакет виньетка для больше информации и примеров.

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

<Ол>
  • Делайте вещи максимально простыми.
  • По возможности рекомендуется сохранять векторизацию ваших функций. Это обеспечивает максимальную гибкость и скорость в долгосрочной перспективе.
  • Важно ли, чтобы значения 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 
    

    [Редактировать] Учитывая, что вы будете применять 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
    

    Списки кажутся идеальными для этой цели. Например, внутри функции у вас будет

    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]]
    

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

    Как насчет использования 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.

    [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

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

    Чтобы получить несколько выходных данных из функции и сохранить их в нужном формате, вы можете сохранить выходные данные на жестком диске (в рабочем каталоге) из функции, а затем загрузить их извне функции:

    myfun <- function(x) {
                          df1 <- ...
                          df2 <- ...
                          save(df1, file = "myfile1")
                          save(df2, file = "myfile2")
    }
    load("myfile1")
    load("myfile2")
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top