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?

user236215
fonte
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"
Ricardo Saporta
fonte
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
Oscar de León
fonte
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 .

nteetor
fonte
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.

kohske
fonte
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)).

Joran
fonte
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
Tommy
fonte
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.

Rafiqul Haider
fonte
5

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

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

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

> a<-values[2]; b<-values[4]
NPE
fonte
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 ...

TMS
fonte
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
naught101
fonte
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] 
Fedor Goncharov
fonte
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.

Vonjd
fonte