如何从一个返回多个值的函数中赋值?


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
仅供参考,返回多个值的另一种方法是在返回值上设置attr

这等效于Python的元组拆包。
smci 2015年

Answers:


185

(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

添加:withattach


25
我接受了您的回答,因为使用了“ with”,但是我无法复制您对“ list”左侧使用的描述,我得到的只是“找不到对象'a'”
mariotomo

4
这个对我有用。你尝试了什么?您是否阅读了链接的帖子并关注它?您是否定义list[<-.result如图所示?
G. Grothendieck

12
@ G.Grothendieck,您介意我将您链接的内容放入您的答案吗?我认为这将使人们更容易使用它。
merlin2011

12
我同意@ merlin2011; 如所写,该语法似乎已嵌入到R base中。
Knowah 2014年

6
@ G.Grothendieck我同意merlin2011和Knowah-最好是在这里重要的实际代码(链接中引用的代码)在答案中。提及结果对象不需要命名为list可能不是一个坏主意。在阅读您的实际代码之前,这让我有些困惑。如前所述回答说,你需要在链路上运行的代码,但大多数人都不会去阅读代码的时候了,除非它在直接的答案-这给人的感觉,这种语法是基础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 2013年

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)怎么办?我已经尝试过使用list(“ a”,“ b”)<-func2(5),但是它不起作用。
skan,

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

我认为这可行。


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

请查看包装插图,以获取更多信息和示例。


是否有一种特殊的语法使用此方法将多个图存储为输出?
mrpargeter '18 -10-10

2
不需要特殊的语法,您可以像分配数字列表一样分配打印对象列表。
nteetor '18 -10-10

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 

[编辑]假设您将分别对每个向量应用最小值和最大值,那么我建议您使用矩阵(如果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 2009年

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,


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

如果您需要访问现有的价值观,相反的assignget


...但是这需要您知道该环境中的接收变量的名称
smci 2015年

@smci是的。这就是为什么问题中的“命名列表”方法通常更好的原因:r <- function() { return(list(first=1, second=2)) }并使用r$first和引用结果r$second
Steve Pitchers 2015年

2
一旦有了函数,如何在一个命令中分配两个变量,例如list(“ x”,“ y”)<-functionReturningTwoValues('a','b')?我说这是因为,如果列表中有很多元素,则需要多次运行整个函数,而且要花费一些时间
skan,2017年

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'情况下,您可以以res [,1],res [,2]或res [,“ Foo”],res [,“ Bar”]的形式访问foo和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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.