Как назначить из функции, которая возвращает более одного значения?
Все еще пытаясь понять логику 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
, если бы это была одна и та же функция для обоих компонентов, мне не нужно было бы их разбивать).
17 ответов
(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
Я как-то наткнулся на этот хитрый взлом в интернете... Я не уверен, насколько он неприятен или красив, но он позволяет вам создать "магический" оператор, который позволяет вам распаковать несколько возвращаемых значений в их собственную переменную. :=
функция определена здесь и включена ниже для потомков:
':=' <- 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
Я собрал пакет 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, ..)
Проверьте виньетка пакета для получения дополнительной информации и примеров.
functionReturningTwoValues <- function() {
results <- list()
results$first <- 1
results$second <-2
return(results)
}
a <- functionReturningTwoValues()
Я думаю, что это работает.
Там нет правильного ответа на этот вопрос. Я действительно зависит от того, что вы делаете с данными. В приведенном выше простом примере я настоятельно рекомендую:
- Держите вещи как можно проще.
- Везде, где это возможно, рекомендуется сохранять векторизацию ваших функций. Это обеспечивает наибольшую гибкость и скорость в долгосрочной перспективе.
Важно ли, чтобы значения 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
Если вы хотите вернуть выходные данные своей функции в глобальную среду, вы можете использовать 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
Списки кажутся идеальными для этой цели. Например, в функции, которую вы бы имели
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
,
Год 2021, и это то, что я часто использую.
tidyverse
в пакете есть функция, называемая
lst
который присваивает имя элементам списка при создании списка. Сообщение, которое я использую
list2env()
назначить переменную или использовать список напрямую
library(tidyverse)
fun <- function(){
a<-1
b<-2
lst(a,b)
}
list2env(fun(), envir=.GlobalEnv)#unpacks list key-values to variable-values into the current environment
[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
Это только для полноты, а не потому, что я лично предпочитаю это. Вы можете трубить
%>%
результат, оцените его с помощью фигурных скобок
{}
и записывайте переменные в родительскую среду, используя двойную стрелку
<<-
.
library(tidyverse)
functionReturningTwoValues() %>% {a <<- .[1]; b <<- .[2]}
ОБНОВЛЕНИЕ: вы также можете использовать оператор множественного присваивания из
zeallot
упаковка::
%<-%
c(a, b) %<-% list(0, 1)
Я опубликую функцию, которая возвращает несколько объектов в виде векторов:
Median <- function(X){
X_Sort <- sort(X)
if (length(X)%%2==0){
Median <- (X_Sort[(length(X)/2)]+X_Sort[(length(X)/2)+1])/2
} else{
Median <- X_Sort[(length(X)+1)/2]
}
return(Median)
}
Это была функция, которую я создал для вычисления медианы. Я знаю, что в R есть встроенная функция, которая называется
median()
но, тем не менее, я запрограммировал его на создание другой функции для вычисления квартилей числового набора данных с помощью функции, которую я только что запрограммировал. В
Median()
функция работает так:
- Если числовой вектор имеет четное количество элементов (т. Е.
length(X)%%2==0
) медиана рассчитывается путем усреднения элементовsort(X)[length(X)/2]
а такжеsort(X)[(length(X)/2+1)]
. - Если не имеет четного числа элементов, медиана равна
sort(X)[(length(X)+1)/2]
.
На :
QuartilesFunction <- function(X){
X_Sort <- sort(X) # Data is sorted in ascending order
if (length(X)%%2==0){
# Data number is even
HalfDN <- X_Sort[1:(length(X)/2)]
HalfUP <- X_Sort[((length(X)/2)+1):length(X)]
QL <- Median(HalfDN)
QU <- Median(HalfUP)
QL1 <- QL
QL2 <- QL
QU1 <- QU
QU2 <- QU
QL3 <- QL
QU3 <- QU
Quartiles <- c(QL1,QU1,QL2,QU2,QL3,QU3)
names(Quartiles) = c("QL (1)", "QU (1)", "QL (2)", "QU (2)","QL (3)", "QU (3)")
} else{ # Data number is odd
# Including the median
Half1DN <- X_Sort[1:((length(X)+1)/2)]
Half1UP <- X_Sort[(((length(X)+1)/2)):length(X)]
QL1 <- Median(Half1DN)
QU1 <- Median(Half1UP)
# Not including the median
Half2DN <- X_Sort[1:(((length(X)+1)/2)-1)]
Half2UP <- X_Sort[(((length(X)+1)/2)+1):length(X)]
QL2 <- Median(Half2DN)
QU2 <- Median(Half2UP)
# Methods (1) and (2) averaged
QL3 <- (QL1+QL2)/2
QU3 <- (QU1+QU2)/2
Quartiles <- c(QL1,QU1,QL2,QU2,QL3,QU3)
names(Quartiles) = c("QL (1)", "QU (1)", "QL (2)", "QU (2)","QL (3)", "QU (3)")
}
return(Quartiles)
}
Эта функция возвращает квартили числового вектора с помощью трех методов:
- Отказ от медианы для вычисления квартилей, когда количество элементов числового вектора нечетное.
- Сохранение медианы для расчета квартилей, когда количество элементов числового вектора нечетное.
- Усреднение результатов, полученных с помощью методов 1 и 2.
Когда количество элементов в числовом векторе
X
четно, эти три метода совпадают.
Результат
QuartilesFunction()
- вектор, отображающий первый и третий квартили, рассчитанные с использованием трех описанных методов.
Библиотека keas имеет схожую с zeallot функциональность :
library(keras)
functionReturningTwoValues <- function() { return(c(1, 2)) }
c(a, b) %<-% functionReturningTwoValues()
С R 3.6.1 я могу сделать следующее
fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"
Чтобы получить несколько выходных данных из функции и сохранить их в нужном формате, вы можете сохранить выходные данные на жестком диске (в рабочем каталоге) изнутри функции, а затем загрузить их извне функции:
myfun <- function(x) {
df1 <- ...
df2 <- ...
save(df1, file = "myfile1")
save(df2, file = "myfile2")
}
load("myfile1")
load("myfile2")