Asignar múltiples variables nuevas en LHS en una sola línea

89

Quiero asignar múltiples variables en una sola línea en R. ¿Es posible hacer algo como esto?

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'

Normalmente quiero asignar alrededor de 5-6 variables en una sola línea, en lugar de tener varias líneas. ¿Existe alguna alternativa?

user236215
fuente
¿Te refieres a algo como en PHP list($a, $b) = array(1, 2)? ¡Eso estaría bien! +1.
TMS
@Tomas T - Creo que mi vassignsugerencia a continuación se acerca ... :)
Tommy
Nota: No se necesitan puntos y comas para esta parte de R.
Iterador
1
Si intentara esto en un entorno apropiado, sería tan fácil como X <- list();X[c('a','b')] <- values[c(2,4)]. De acuerdo, no los asigna en el espacio de trabajo, pero manténgalos juntos en una lista. Preferiría hacerlo de esa manera.
Joris Meys
7
me gusta Python, solo a, b = 1,2. todas las respuestas a continuación son 100 veces más difíciles
appleLover

Respuestas:

39

Hay una gran respuesta en el blog Luchando por los problemas

Esto se toma a partir de ahí, con modificaciones muy pequeñas.

USANDO LAS SIGUIENTES TRES FUNCIONES (Más una para permitir listas de diferentes tamaños)

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


Luego para ejecutar:

Agrupe el lado izquierdo usando la nueva función g() El lado derecho debe ser un vector o una lista Use el operador binario recién creado%=%

# 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


Ejemplo usando listas de diferentes tamaños:

Lado izquierdo más largo

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"

Lado derecho más largo

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"
Ricardo Saporta
fuente
34

Reuní un paquete R zeallot para abordar este mismo problema. zeallot incluye un operador ( %<-%) para desempaquetar, asignaciones múltiples y desestructuraciones. El LHS de la expresión de asignación se crea mediante llamadas a c(). El RHS de la expresión de asignación puede ser cualquier expresión que devuelva o sea un vector, lista, lista anidada, marco de datos, cadena de caracteres, objeto de fecha u objetos personalizados (suponiendo que haya una destructureimplementación).

Aquí está la pregunta inicial reelaborada usando zeallot (última versión, 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

Para obtener más ejemplos e información, puede consultar la viñeta del paquete .

nteetor
fuente
esto es exactamente lo que esperaba encontrar, algo que habilite la sintaxis similar a Python que el OP estaba solicitando, implementada en un paquete R
jafelds
1
¿Qué hay de asignar una matriz a cada nombre de variable?
StatsSorceress
33

Considere usar la funcionalidad incluida en la base R.

Por ejemplo, cree un marco de datos de 1 fila (digamos V) e inicialice sus variables en él. Ahora puede asignar múltiples variables a la vez V[,c("a", "b")] <- values[c(2, 4)], llamar a cada una por su nombre ( V$a) o usar muchas de ellas al mismo tiempo ( values[c(5, 6)] <- V[,c("a", "b")]).

Si se vuelve perezoso y no quiere andar llamando variables desde el marco de datos, podría attach(V)hacerlo (aunque yo personalmente nunca lo hago).

# 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
Oscar de León
fuente
4
+10 si pudiera. Me pregunto por qué la gente se niega a usar listas en casos tan obvios, sino que ensucia el espacio de trabajo con toneladas de variables sin sentido. (Usas listas, ya que un data.frame es un tipo especial de lista. Yo solo usaría una más general.)
Joris Meys
pero no puede tener diferentes tipos de elementos en la misma columna, ni puede almacenar marcos de datos o listas dentro de su marco de datos
skan
1
En realidad, puede almacenar listas en un marco de datos: "columna de lista" de Google.
No es un mal enfoque, tiene algunas comodidades, pero tampoco es tan difícil imaginar por qué muchos usuarios no querrían tener que lidiar con la sintaxis de data.frame cada vez que intentaron usar o acceder a variables asignadas de esta manera.
Brandon
13

aquí está mi idea. Probablemente la sintaxis sea bastante 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)

da así:

> 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

Sin embargo, esto no está bien probado.

Kohske
fuente
2
Koshke, me parece muy agradable :-) Pero estoy un poco preocupado por la precedencia de los operadores: los operadores% something% son bastante altos, por lo que el comportamiento de, por ejemplo, c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, devuelve numérico ( 0)) puede considerarse sorprendente.
Cbeleites descontentos con SX
10

Una assignopción potencialmente peligrosa (en la medida en que su uso es riesgoso) sería 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

O supongo que podría vectorizarlo usted mismo manualmente con su propia función usando mapplyque tal vez use un valor predeterminado razonable para el envirargumento. Por ejemplo, Vectorizedevolverá una función con las mismas propiedades de entorno de assign, que en este caso es namespace:base, o simplemente podría establecer envir = parent.env(environment(assignVec)).

joran
fuente
8

Como explicaron otros, no parece haber nada integrado ... pero puede diseñar una vassignfunción de la siguiente manera:

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

Sin embargo, una cosa a considerar es cómo manejar los casos en los que, por ejemplo, especifica 3 variables y 5 valores o al revés. Aquí simplemente repito (o trunco) los valores para que tengan la misma longitud que las variables. Quizás sería prudente una advertencia. Pero permite lo siguiente:

vassign(aa,bb,cc,dd, values=0)
cc # 0
Tommy
fuente
Me gusta esto, pero me preocuparía que pudiera romperse en algún caso donde se llama desde dentro de una función (aunque una simple prueba de esto funcionó, para mi leve sorpresa). ¿Puedes explicarme ...(), lo que me parece magia negra ...?
Ben Bolker
1
@Ben Bolker - Sí, ...()es magia negra extrema ;-). Sucede que cuando ...()se sustituye la "llamada de función" , se convierte en una lista de pares que se puede pasar as.charactery listo, tienes los argumentos como cadenas ...
Tommy
1
@Ben Bolker - Y debería funcionar correctamente incluso cuando se llama desde dentro de una función, ya que usa envir=parent.frame()- Y puede especificar, por ejemplo, envir=globalenv()si lo desea.
Tommy
Aún más genial sería tener esto como función de reemplazo: `vassign<-` <- function (..., envir = parent.frame (), value)y así sucesivamente. Sin embargo, parece que el primer objeto que se asignará ya debería existir. ¿Algunas ideas?
Cbeleites descontentos con SX
@cbeleites: sí, eso sería mejor, pero no creo que pueda evitar la limitación de que el primer argumento debe existir; por eso se llama función de reemplazo :) ... pero avíseme si descubre lo contrario !
Tommy
6
list2env(setNames(as.list(rep(2,5)), letters[1:5]), .GlobalEnv)

Cumplió mi propósito, es decir, asignar cinco 2 en las primeras cinco letras.

Rafiqul Haider
fuente
4

Tuve un problema similar recientemente y aquí estaba mi intento de usar purrr::walk2

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

Si su único requisito es tener una sola línea de código, entonces qué tal:

> a<-values[2]; b<-values[4]
NPE
fuente
2
estaba buscando una declaración sucinta, pero supongo que no hay ninguna
user236215
Estoy en el mismo barco que @ user236215. cuando el lado derecho es una expresión complicada que devuelve un vector, repetir el código parece muy incorrecto ...
airstrike
1

Me temo que esa solución elegante que está buscando (como c(a, b) = c(2, 4)) lamentablemente no existe. ¡Pero no te rindas, no estoy seguro! La solución más cercana que puedo pensar es esta:

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

o si le molestan las advertencias, apáguelas:

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

Pero supongo que no estás satisfecho con esta solución, yo tampoco lo estaría ...

TMS
fuente
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
nada101
fuente
0

Otra versión con recursividad:

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

ejemplo:

> 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

Mi version:

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

ejemplo:

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

Combinando algunas de las respuestas dadas aquí + un poco de sal, ¿qué tal esta solución?

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

Usé esto para agregar la sección R aquí: http://rosettacode.org/wiki/Sort_three_variables#R

Advertencia: solo funciona para asignar variables globales (como <<-). Si hay una solución mejor y más general, por favor. Dime en los comentarios.

vonjd
fuente