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


89

我想在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个变量,而不是多行。还有其他选择吗?


您的意思是类似PHP list($a, $b) = array(1, 2)?那样就好了!+1。
TMS

@Tomas T-我认为我的vassign建议接近... :)
Tommy

注意:R的该位不需要分号。–
Iterator

1
如果您在适当的环境中尝试此操作,那将和一样简单X <- list();X[c('a','b')] <- values[c(2,4)]。好的,您不必在工作区中分配它们,而是将它们很好地放在列表中。我宁愿那样做。
Joris Meys 2013年

7
我喜欢python,只有a,b = 1,2。下面的所有答案都硬了100倍
appleLover 2013年

Answers:


39

在“通过问题奋斗”博客上有一个很好的答案

取自此处,仅作了很小的修改。

使用以下三个功能(加上一个允许使用不同大小的列表)

# 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)
}


然后执行:

使用新功能对左侧进行分组g() 右侧应为向量或列表使用新创建的二进制运算符%=%

# 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


使用不同大小的列表的示例:

左手边较长

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"

右手边较长

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"

34

我整理了一个R包zeallot来解决这个问题。zeallot包括一个%<-%用于解压缩,多个和解构分配的运算符()。赋值表达式的LHS使用对的调用来构建c()。赋值表达式的RHS可以是任何返回或为向量,列表,嵌套列表,数据框,字符串,日期对象或自定义对象的表达式(假设有一个destructure实现)的。

这是使用zeallot(最新版本,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

有关更多示例和信息,请查看包装插图


这正是我希望找到的东西,它使OP要求的类似于Python的语法在R包中实现
jafelds,

1
为每个变量名称分配矩阵呢?
StatsSorceress

33

考虑使用基本R中包含的功能。

例如,创建一个1行数据框(例如V)并在其中初始化变量。现在,您可以一次分配多个变量,V[,c("a", "b")] <- values[c(2, 4)]通过名称(V$a)调用每个变量,或同时使用多个变量(values[c(5, 6)] <- V[,c("a", "b")])。

如果您懒惰并且不想从数据帧中调用变量,则可以attach(V)(尽管我个人从未这样做)。

# 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

4
如果可以的话,+ 10。我不知道为什么人们在这种明显的情况下拒绝使用列表,而是用大量无意义的变量来填充工作区。(您确实使用列表,因为data.frame是一种特殊的列表。我只使用一个更普通的列表。)
Joris Meys

但是您不能在同一列中使用不同类型的元素,也不能在数据
框中

1
实际上,您可以将列表存储在数据框中-谷歌“列表列”。

这不是一个坏方法,它具有一些便利,但是也很难想象为什么许多用户在每次尝试使用或访问以这种方式分配的变量时都不必处理data.frame语法。
布兰登

13

这是我的主意。语法可能很简单:

`%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)

给出这样的:

> 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

虽然这还没有很好的测试。


2
Koshke,对我来说非常好:-)但是我有点担心运算符的优先级:%something%运算符的位置很高,因此eg c(c, d) %tin% c(1, 2) + 3(=> c = 1,d = 1的行为返回数字( 0))可能令人惊讶。
cbeleites对SX不满意,2011年

10

一个潜在的危险(尽管使用起来assign很有风险)选项是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

或者我想您可以使用自己的函数手动将其向量化,因为它mapply可能对envir参数使用了合理的默认值。例如,Vectorize将返回具有与相同的环境属性的函数assign,在本例中为namespace:base,或者您可以直接设置envir = parent.env(environment(assignVec))


8

正如其他人解释的那样,似乎没有内置任何内容。...但是您可以vassign如下设计一个函数:

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

但是,要考虑的一件事是如何处理例如指定3个变量和5个值或相反的情况。在这里,我只是简单地重复(或截断)这些值,使其具有与变量相同的长度。也许警告是谨慎的。但是它允许以下内容:

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

我喜欢这个,但是我担心在某些情况下从函数内部调用它可能会中断(尽管对此做一个简单的测试很令我惊讶)。您能解释一下...(),对我来说,这看起来像是黑魔法……?
本·博克

1
@Ben Bolker-是的,...()是极端的黑魔法;-)。碰巧的是,当“函数调用”...()被替换时,它变成了一个可以传递给as.character和voila的成对列表,您将参数作为字符串使用了
Tommy

1
@Ben Bolker-并且它即使在函数中调用也应该正常工作,因为它使用envir=parent.frame()-您可以指定例如envir=globalenv()是否需要。
汤米

甚至更酷的将具有此作为替换功能:`vassign<-` <- function (..., envir = parent.frame (), value)等等。但是,似乎要分配的第一个对象必须已经存在。有任何想法吗?
cbeleites对SX不满意,2011年

@cbeleites-是的,这会更酷,但是我认为您不能解决必须存在第一个参数的限制-这就是为什么它被称为替换函数:) ...但是如果您发现其他情况,请告诉我!
汤米



4

最近有类似的问题,这是我尝试使用 purrr::walk2

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

3

如果您唯一的要求是只有一行代码,那么如何:

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

2
一直在寻找简洁的陈述,但我想没有一个陈述
user236215 2011年

我和@ user236215在同一条船上。当右侧是返回向量的复杂表达式时,重复代码似乎非常错误……
空袭

1

恐怕您正在寻找(例如c(a, b) = c(2, 4))不幸的解决方案并不存在。但是请不要放弃,我不确定!我能想到的最接近的解决方案是:

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

或者如果您对警告感到困扰,请将其关闭:

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

但是我想您对这种解决方案不满意,我也不会...


1
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

0

具有递归的另一个版本:

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(...)
}

例:

> 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

我的版本:

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

例:

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

0

结合这里给出的一些答案和一些盐,该解决方案如何:

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

我用它在这里添加了R部分:http : //rosettacode.org/wiki/Sort_three_variables#R

注意:仅适用于分配全局变量(如<<-)。如果有更好,更通用的解决方案,请。在评论中告诉我。

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.