在一行中为 LHS 分配多个新变量

我想在 R 中的一行中赋多个变量,有可能做到这样吗?

values # initialize some vector of values
(a, b) = values[c(2,4)] # assign a and b to values at 2 and 4 indices of 'values'

通常我希望在一行中分配大约5-6个变量,而不是多行。还有别的选择吗?

75038 次浏览

If your only requirement is to have a single line of code, then how about:

> a<-values[2]; b<-values[4]

A potentially dangerous (in as much as using assign is risky) option would be to Vectorize assign:

assignVec <- Vectorize("assign",c("x","value"))
#.GlobalEnv is probably not what one wants in general; see below.
assignVec(c('a','b'),c(0,4),envir = .GlobalEnv)
a b
0 4
> b
[1] 4
> a
[1] 0

Or I suppose you could vectorize it yourself manually with your own function using mapply that maybe uses a sensible default for the envir argument. For instance, Vectorize will return a function with the same environment properties of assign, which in this case is namespace:base, or you could just set envir = parent.env(environment(assignVec)).

I'm afraid that elegent solution you are looking for (like c(a, b) = c(2, 4)) unfortunatelly does not exist. But don't give up, I'm not sure! The nearest solution I can think of is this one:

attach(data.frame(a = 2, b = 4))

or if you are bothered with warnings, switch them off:

attach(data.frame(a = 2, b = 4), warn = F)

But I suppose you're not satisfied with this solution, I wouldn't be either...

As others explained, there doesn't seem to be anything built in. ...but you could design a vassign function as follows:

vassign <- function(..., values, envir=parent.frame()) {
vars <- as.character(substitute(...()))
values <- rep(values, length.out=length(vars))
for(i in seq_along(vars)) {
assign(vars[[i]], values[[i]], envir)
}
}


# Then test it
vals <- 11:14
vassign(aa,bb,cc,dd, values=vals)
cc # 13

One thing to consider though is how to handle the cases where you e.g. specify 3 variables and 5 values or the other way around. Here I simply repeat (or truncate) the values to be of the same length as the variables. Maybe a warning would be prudent. But it allows the following:

vassign(aa,bb,cc,dd, values=0)
cc # 0

here is my idea. Probably the syntax is quite simple:

`%tin%` <- function(x, y) {
mapply(assign, as.character(substitute(x)[-1]), y,
MoreArgs = list(envir = parent.frame()))
invisible()
}


c(a, b) %tin% c(1, 2)

gives like this:

> a
Error: object 'a' not found
> b
Error: object 'b' not found
> c(a, b) %tin% c(1, 2)
> a
[1] 1
> b
[1] 2

this is not well tested though.

There is a great answer on the Struggling Through Problems Blog

This is taken from there, with very minor modifications.

USING THE FOLLOWING THREE FUNCTIONS (Plus one for allowing for lists of different sizes)

# Generic form
'%=%' = function(l, r, ...) UseMethod('%=%')


# Binary Operator
'%=%.lbunch' = function(l, r, ...) {
Envir = as.environment(-1)


if (length(r) > length(l))
warning("RHS has more args than LHS. Only first", length(l), "used.")


if (length(l) > length(r))  {
warning("LHS has more args than RHS. RHS will be repeated.")
r <- extendToMatch(r, l)
}


for (II in 1:length(l)) {
do.call('<-', list(l[[II]], r[[II]]), envir=Envir)
}
}


# Used if LHS is larger than RHS
extendToMatch <- function(source, destin) {
s <- length(source)
d <- length(destin)


# Assume that destin is a length when it is a single number and source is not
if(d==1 && s>1 && !is.null(as.numeric(destin)))
d <- destin


dif <- d - s
if (dif > 0) {
source <- rep(source, ceiling(d/s))[1:d]
}
return (source)
}


# Grouping the left hand side
g = function(...) {
List = as.list(substitute(list(...)))[-1L]
class(List) = 'lbunch'
return(List)
}


Then to execute:

Group the left hand side using the new function g() The right hand side should be a vector or a list Use the newly-created binary operator %=%

# Example Call;  Note the use of g()  AND  `%=%`
#     Right-hand side can be a list or vector
g(a, b, c)  %=%  list("hello", 123, list("apples, oranges"))


g(d, e, f) %=%  101:103


# Results:
> a
[1] "hello"
> b
[1] 123
> c
[[1]]
[1] "apples, oranges"


> d
[1] 101
> e
[1] 102
> f
[1] 103


Example using lists of different sizes:

Longer Left Hand Side

g(x, y, z) %=% list("first", "second")
#   Warning message:
#   In `%=%.lbunch`(g(x, y, z), list("first", "second")) :
#     LHS has more args than RHS. RHS will be repeated.
> x
[1] "first"
> y
[1] "second"
> z
[1] "first"

Longer Right Hand Side

g(j, k) %=% list("first", "second", "third")
#   Warning message:
#   In `%=%.lbunch`(g(j, k), list("first", "second", "third")) :
#     RHS has more args than LHS. Only first2used.
> j
[1] "first"
> k
[1] "second"

Consider using functionality included in base R.

For instance, create a 1 row dataframe (say V) and initialize your variables in it. Now you can assign to multiple variables at once V[,c("a", "b")] <- values[c(2, 4)], call each one by name (V$a), or use many of them at the same time (values[c(5, 6)] <- V[,c("a", "b")]).

If you get lazy and don't want to go around calling variables from the dataframe, you could attach(V) (though I personally don't ever do it).

# Initialize values
values <- 1:100


# V for variables
V <- data.frame(a=NA, b=NA, c=NA, d=NA, e=NA)


# Assign elements from a vector
V[, c("a", "b", "e")] = values[c(2,4, 8)]


# Also other class
V[, "d"] <- "R"


# Use your variables
V$a
V$b
V$c  # OOps, NA
V$d
V$e
R> values = c(1,2,3,4)
R> a <- values[2]; b <- values[3]; c <- values[4]
R> a
[1] 2
R> b
[1] 3
R> c
[1] 4

https://stat.ethz.ch/R-manual/R-devel/library/base/html/list2env.html:

list2env(
list(
a=1,
b=2:4,
c=rpois(10,10),
d=gl(3,4,LETTERS[9:11])
),
envir=.GlobalEnv
)
list2env(setNames(as.list(rep(2,5)), letters[1:5]), .GlobalEnv)

Served my purpose, i.e., assigning five 2s into first five letters.

Another version with recursion:

let <- function(..., env = parent.frame()) {
f <- function(x, ..., i = 1) {
if(is.null(substitute(...))){
if(length(x) == 1)
x <- rep(x, i - 1);
stopifnot(length(x) == i - 1)
return(x);
}
val <- f(..., i = i + 1);
assign(deparse(substitute(x)), val[[i]], env = env);
return(val)
}
f(...)
}

example:

> let(a, b, 4:10)
[1]  4  5  6  7  8  9 10
> a
[1] 4
> b
[1] 5
> let(c, d, e, f, c(4, 3, 2, 1))
[1] 4 3 2 1
> c
[1] 4
> f
[1] 1

My version:

let <- function(x, value) {
mapply(
assign,
as.character(substitute(x)[-1]),
value,
MoreArgs = list(envir = parent.frame()))
invisible()
}

example:

> let(c(x, y), 1:2 + 3)
> x
[1] 4
> y
[1]

I put together an R package zeallot to tackle this very problem. zeallot includes an operator (%<-%) for unpacking, multiple, and destructuring assignment. The LHS of the assignment expression is built using calls to c(). The RHS of the assignment expression may be any expression which returns or is a vector, list, nested list, data frame, character string, date object, or custom objects (assuming there is a destructure implementation).

Here is the initial question reworked using zeallot (latest version, 0.0.5).

library(zeallot)


values <- c(1, 2, 3, 4)     # initialize a vector of values
c(a, b) %<-% values[c(2, 4)]  # assign `a` and `b`
a
#[1] 2
b
#[1] 4

For more examples and information one can check out the package vignette.

Had a similar problem recently and here was my try using purrr::walk2

purrr::walk2(letters,1:26,assign,envir =parent.frame())

Combining some of the answers given here + a little bit of salt, how about this solution:

assignVec <- Vectorize("assign", c("x", "value"))
`%<<-%` <- function(x, value) invisible(assignVec(x, value, envir = .GlobalEnv))


c("a", "b") %<<-% c(2, 4)
a
## [1] 2
b
## [1] 4

I used this to add the R section here: http://rosettacode.org/wiki/Sort_three_variables#R

Caveat: It only works for assigning global variables (like <<-). If there is a better, more general solution, pls. tell me in the comments.

For a named list, use

list2env(mylist, environment())

For instance:

mylist <- list(foo = 1, bar = 2)
list2env(mylist, environment())

will add foo = 1, bar = 2 to the current environement, and override any object with those names. This is equivalent to

mylist <- list(foo = 1, bar = 2)
foo <- mylist$foo
bar <- mylist$bar

This works in a function, too:

f <- function(mylist) {
list2env(mylist, environment())
foo * bar
}
mylist <- list(foo = 1, bar = 2)
f(mylist)

However, it is good practice to name the elements you want to include in the current environment, lest you override another object... and so write preferrably

list2env(mylist[c("foo", "bar")], environment())

Finally, if you want different names for the new imported objects, write:

list2env(`names<-`(mylist[c"foo", "bar"]), c("foo2", "bar2")), environment())

which is equivalent to

foo2 <- mylist$foo
bar2 <- mylist$bar