Atribuir várias novas variáveis ​​no LHS em uma única linha


90

Quero atribuir várias variáveis ​​em uma única linha em R. É possível fazer algo assim?

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, desejo atribuir cerca de 5-6 variáveis ​​em uma única linha, em vez de ter várias linhas. Existe uma alternativa?


você quer dizer algo como em PHP list($a, $b) = array(1, 2)? Isso seria legal! +1.
TMS de

@Tomas T - Acho que minha vassignsugestão abaixo chega perto ... :)
Tommy

Observação: ponto-e-vírgulas não são necessários para este pedaço de R.
Iterador

1
Se você tentar isso em um ambiente apropriado, será tão fácil quanto X <- list();X[c('a','b')] <- values[c(2,4)]. OK, você não os atribui na área de trabalho, mas os mantém juntos em uma lista. Eu prefiro fazer assim.
Joris Meys

7
eu gosto de python, apenas a, b = 1,2. todas as respostas abaixo são 100x mais difíceis
appleLover

Respostas:


40

Há uma ótima resposta no blog Lutando com os Problemas

Isso é obtido a partir daí, com modificações muito pequenas.

USANDO AS SEGUINTES TRÊS FUNÇÕES (Mais uma para permitir listas de tamanhos diferentes)

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


Então, para executar:

Agrupe o lado esquerdo usando a nova função g() O lado direito deve ser um vetor ou uma lista Use o operador binário recém-criado%=%

# 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


Exemplo de uso de listas de tamanhos diferentes:

Lado Esquerdo Mais Longo

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 Direito Mais Longo

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

Considere usar a funcionalidade incluída na base R.

Por exemplo, crie um dataframe de 1 linha (digamos V) e inicialize suas variáveis ​​nele. Agora você pode atribuir a várias variáveis ​​de uma vez V[,c("a", "b")] <- values[c(2, 4)], chamar cada uma por nome ( V$a) ou usar muitas delas ao mesmo tempo ( values[c(5, 6)] <- V[,c("a", "b")]).

Se você ficar preguiçoso e não quiser sair chamando variáveis ​​do dataframe, você pode attach(V)(embora eu pessoalmente nunca faça isso).

# 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

5
+10 se eu pudesse. Eu me pergunto por que as pessoas se recusam a usar listas em casos tão óbvios, mas sim desarrumam o espaço de trabalho com toneladas de variáveis ​​sem sentido. (você usa listas, já que data.frame é um tipo especial de lista. Eu usaria apenas uma mais geral.)
Joris Meys

mas você não pode ter diferentes tipos de elementos na mesma coluna, nem pode armazenar dataframes ou listas dentro de seu dataframe
skan

1
Na verdade, você pode armazenar listas em um quadro de dados - google "coluna de lista".

Não é uma abordagem ruim, tem algumas conveniências, mas também não é tão difícil imaginar por que muitos usuários não gostariam de ter que lidar com a sintaxe data.frame toda vez que tentassem usar ou acessar variáveis ​​atribuídas dessa forma.
Brandon

34

Eu montei um zeallot de pacote R para resolver esse problema. zeallot inclui um operador ( %<-%) para descompactação, atribuição múltipla e desestruturação. O LHS da expressão de atribuição é construído usando chamadas para c(). O RHS da expressão de atribuição pode ser qualquer expressão que retorna ou é um vetor, lista, lista aninhada, quadro de dados, cadeia de caracteres, objeto de data ou objetos personalizados (assumindo que haja uma destructureimplementação).

Aqui está a pergunta inicial retrabalhada usando zeallot (versão mais recente, 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 mais exemplos e informações, pode-se verificar a vinheta do pacote .


isso é exatamente o que eu esperava encontrar, algo que habilita a sintaxe do tipo python que o OP estava pedindo, implementada em um pacote R
jafelds

1
Que tal atribuir uma matriz para cada nome de variável?
StatsSorceress

14

aqui está minha ideia. Provavelmente, a sintaxe é bastante simples:

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

dá assim:

> 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

isso não foi bem testado.


3
Koshke, parece muito bom para mim :-) Mas estou um pouco preocupado com a precedência do operador: os operadores% something% estão bem altos, então o comportamento de eg c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, retorna numérico ( 0)) pode ser considerado surpreendente.
cbeleites insatisfeito com SX de

10

Uma opção potencialmente perigosa (na medida em que usar assigné arriscado) seria 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

Ou suponho que você mesmo possa vetorizá-lo manualmente com sua própria função usando mapplyisso talvez use um padrão razoável para o envirargumento. Por exemplo, Vectorizeretornará uma função com as mesmas propriedades de ambiente de assign, que neste caso é namespace:base, ou você poderia apenas definir envir = parent.env(environment(assignVec)).


9

Como outros explicaram, não parece haver nada integrado. ... mas você pode projetar uma vassignfunção da seguinte maneira:

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

Porém, uma coisa a se considerar é como lidar com os casos em que você, por exemplo, especifica 3 variáveis ​​e 5 valores ou o contrário. Aqui, simplesmente repito (ou trunco) os valores para que tenham o mesmo comprimento que as variáveis. Talvez um aviso seja prudente. Mas permite o seguinte:

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

Eu gosto disso, mas temo que possa quebrar em algum caso em que seja chamado de dentro de uma função (embora um simples teste funcione, para minha leve surpresa). Você pode explicar ...(), o que parece magia negra para mim ...?
Ben Bolker

1
@Ben Bolker - Sim, ...()é magia negra extrema ;-). Acontece que quando a "chamada de função" ...()é substituída, ela se torna um pairlist que pode ser passado para as.charactere voila, você tem os argumentos como strings ...
Tommy

1
@Ben Bolker - E deve funcionar corretamente, mesmo quando chamado de dentro de uma função, uma vez que usa envir=parent.frame()- E você pode especificar, por exemplo, envir=globalenv()se quiser.
Tommy

Ainda mais legal seria ter isso como função de substituição: `vassign<-` <- function (..., envir = parent.frame (), value)e assim por diante. No entanto, parece que o primeiro objeto a ser atribuído já precisaria existir. Alguma ideia?
cbeleites insatisfeito com SX de

@cbeleites - Sim, seria mais legal, mas não acho que você possa contornar a limitação de que o primeiro argumento tem de existir - é por isso que é chamado de função de substituição :) ... mas me avise se descobrir o contrário !
Tommy

6
list2env(setNames(as.list(rep(2,5)), letters[1:5]), .GlobalEnv)

Serviu ao meu propósito, ou seja, atribuir cinco 2s nas primeiras cinco letras.



5

Tive um problema semelhante recentemente e aqui estava minha tentativa de usar purrr::walk2

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

3

Se seu único requisito é ter uma única linha de código, que tal:

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

2
estava procurando uma declaração sucinta, mas acho que não há nenhuma
user236215

Estou no mesmo barco que @ user236215. quando o lado direito é uma expressão complicada que retorna um vetor, repetir o código parece muito errado ...
ataque aéreo em

1

Infelizmente, essa solução elegante que você está procurando (como c(a, b) = c(2, 4)) não existe. Mas não desista, não tenho certeza! A solução mais próxima que consigo pensar é esta:

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

ou se você se incomodar com avisos, desligue-os:

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

Mas suponho que você não esteja satisfeito com esta solução, eu também não ...


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

Outra versão com recursão:

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

exemplo:

> 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

Minha versão:

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

exemplo:

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

0

Combinando algumas das respostas dadas aqui + um pouco de sal, que tal esta solução:

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

Usei isso para adicionar a seção R aqui: http://rosettacode.org/wiki/Sort_three_variables#R

Advertência: só funciona para atribuir variáveis ​​globais (como <<-). Se houver uma solução melhor e mais geral, pls. diga-me nos comentários.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.