How to assign from a function which returns more than one value?

你说的曾经没有我的故事 提交于 2019-12-16 23:01:15

问题


Still trying to get into the R logic... what is the "best" way to unpack (on LHS) the results from a function returning multiple values?

I can't do this apparently:

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

must I really do the following?

R> r <- functionReturningTwoValues()
R> a <- r[1]; b <- r[2]

or would the R programmer write something more like this:

R> functionReturningTwoValues <- function() {return(list(first=1, second=2))}
R> r <- functionReturningTwoValues()
R> r$first
[1] 1
R> r$second
[1] 2

--- edited to answer Shane's questions ---

I don't really need giving names to the result value parts. I am applying one aggregate function to the first component and an other to the second component (min and max. if it was the same function for both components I would not need splitting them).


回答1:


(1) list[...]<- I had posted this over a decade ago on r-help. Since then it has been added to the gsubfn package. It does not require a special operator but does require that the left hand side be written using list[...] like this:

library(gsubfn)  # need 0.7-0 or later
list[a, b] <- functionReturningTwoValues()

If you only need the first or second component these all work too:

list[a] <- functionReturningTwoValues()
list[a, ] <- functionReturningTwoValues()
list[, b] <- functionReturningTwoValues()

(Of course, if you only needed one value then functionReturningTwoValues()[[1]] or functionReturningTwoValues()[[2]] would be sufficient.)

See the cited r-help thread for more examples.

(2) with If the intent is merely to combine the multiple values subsequently and the return values are named then a simple alternative is to use with :

myfun <- function() list(a = 1, b = 2)

list[a, b] <- myfun()
a + b

# same
with(myfun(), a + b)

(3) attach Another alternative is attach:

attach(myfun())
a + b

ADDED: with and attach




回答2:


I somehow stumbled on this clever hack on the internet ... I'm not sure if it's nasty or beautiful, but it lets you create a "magical" operator that allows you to unpack multiple return values into their own variable. The := function is defined here, and included below for posterity:

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

With that in hand, you can do what you're after:

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

I don't know how I feel about that. Perhaps you might find it helpful in your interactive workspace. Using it to build (re-)usable libraries (for mass consumption) might not be the best idea, but I guess that's up to you.

... you know what they say about responsibility and power ...




回答3:


Usually I wrap the output into a list, which is very flexible (you can have any combination of numbers, strings, vectors, matrices, arrays, lists, objects int he output)

so like:

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



回答4:


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

I think this works.




回答5:


I put together an R package zeallot to tackle this problem. zeallot includes a multiple assignment or unpacking assignment operator, %<-%. The LHS of the operator is any number of variables to assign, built using calls to c(). The RHS of the operator is a vector, list, data frame, date object, or any custom object with an implemented destructure method (see ?zeallot::destructure).

Here are a handful of examples based on the original post,

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

Check out the package vignette for more information and examples.




回答6:


There's no right answer to this question. I really depends on what you're doing with the data. In the simple example above, I would strongly suggest:

  1. Keep things as simple as possible.
  2. Wherever possible, it's a best practice to keep your functions vectorized. That provides the greatest amount of flexibility and speed in the long run.

Is it important that the values 1 and 2 above have names? In other words, why is it important in this example that 1 and 2 be named a and b, rather than just r[1] and r[2]? One important thing to understand in this context is that a and b are also both vectors of length 1. So you're not really changing anything in the process of making that assignment, other than having 2 new vectors that don't need subscripts to be referenced:

> 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

You can also assign the names to the original vector if you would rather reference the letter than the index:

> names(r) <- c("a","b")
> names(r)
[1] "a" "b"
> r["a"]
a 
1 

[Edit] Given that you will be applying min and max to each vector separately, I would suggest either using a matrix (if a and b will be the same length and the same data type) or data frame (if a and b will be the same length but can be different data types) or else use a list like in your last example (if they can be of differing lengths and data types).

> 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



回答7:


Lists seem perfect for this purpose. For example within the function you would have

x = desired_return_value_1 # (vector, matrix, etc)

y = desired_return_value_2 # (vector, matrix, etc)

returnlist = list(x,y...)

}  # end of function

main program

x = returnlist[[1]]

y = returnlist[[2]]



回答8:


Yes to your second and third questions -- that's what you need to do as you cannot have multiple 'lvalues' on the left of an assignment.




回答9:


How about using assign?

functionReturningTwoValues <- function(a, b) {
  assign(a, 1, pos=1)
  assign(b, 2, pos=1)
}

You can pass the names of the variable you want to be passed by reference.

> functionReturningTwoValues('a', 'b')
> a
[1] 1
> b
[1] 2

If you need to access the existing values, the converse of assign is get.




回答10:


[A] If each of foo and bar is a single number, then there's nothing wrong with c(foo,bar); and you can also name the components: c(Foo=foo,Bar=bar). So you could access the components of the result 'res' as res[1], res[2]; or, in the named case, as res["Foo"], res["BAR"].

[B] If foo and bar are vectors of the same type and length, then again there's nothing wrong with returning cbind(foo,bar) or rbind(foo,bar); likewise nameable. In the 'cbind' case, you would access foo and bar as res[,1], res[,2] or as res[,"Foo"], res[,"Bar"]. You might also prefer to return a dataframe rather than a matrix:

data.frame(Foo=foo,Bar=bar)

and access them as res$Foo, res$Bar. This would also work well if foo and bar were of the same length but not of the same type (e.g. foo is a vector of numbers, bar a vector of character strings).

[C] If foo and bar are sufficiently different not to combine conveniently as above, then you shuld definitely return a list.

For example, your function might fit a linear model and also calculate predicted values, so you could have

LM<-lm(....) ; foo<-summary(LM); bar<-LM$fit

and then you would return list(Foo=foo,Bar=bar) and then access the summary as res$Foo, the predicted values as res$Bar

source: http://r.789695.n4.nabble.com/How-to-return-multiple-values-in-a-function-td858528.html




回答11:


If you want to return the output of your function to the Global Environment, you can use list2env, like in this example:

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)

This function will create three objects in your Global Environment:

> my_obj1
  [1] 1 2 3

> my_obj2
  [1] 5 4 3

> myDF
    a b
  1 1 5
  2 2 4
  3 3 3



回答12:


To obtain multiple outputs from a function and keep them in the desired format you can save the outputs to your hard disk (in the working directory) from within the function and then load them from outside the function:

myfun <- function(x) {
                      df1 <- ...
                      df2 <- ...
                      save(df1, file = "myfile1")
                      save(df2, file = "myfile2")
}
load("myfile1")
load("myfile2")



回答13:


With R 3.6.1, I can do the following

fr2v <- function() { c(5,3) }
a_b <- fr2v()
(a_b[[1]]) # prints "5"
(a_b[[2]]) # prints "3"


来源:https://stackoverflow.com/questions/1826519/how-to-assign-from-a-function-which-returns-more-than-one-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!