Assegna più nuove variabili su LHS in una singola riga


89

Voglio assegnare più variabili in una singola riga in R. È possibile fare qualcosa di simile?

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'

In genere desidero assegnare circa 5-6 variabili in una singola riga, invece di avere più righe. C'è un'alternativa?


vuoi dire qualcosa come in PHP list($a, $b) = array(1, 2)? Sarebbe carino! +1.
TMS

@Tomas T - Penso che il mio vassignsuggerimento qui sotto si avvicini ... :)
Tommy

Nota: i punti e virgola non sono necessari per questo pezzo di R.
Iterator

1
Se provassi questo in un ambiente appropriato, sarebbe facile quanto X <- list();X[c('a','b')] <- values[c(2,4)]. OK, non li assegni nell'area di lavoro, ma tienili bene insieme in un elenco. Preferisco farlo in questo modo.
Joris Meys

7
mi piace python, solo a, b = 1,2. tutte le risposte seguenti sono 100 volte più difficili
appleLover

Risposte:


39

C'è un'ottima risposta sul blog Lottare attraverso i problemi

Questo è preso da lì, con modifiche molto minori.

UTILIZZANDO LE SEGUENTI TRE FUNZIONI (Più una per consentire elenchi di diverse dimensioni)

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


Quindi per eseguire:

Raggruppa il lato sinistro usando la nuova funzione g() Il lato destro dovrebbe essere un vettore o una lista Usa l'operatore binario appena creato%=%

# 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


Esempio utilizzando elenchi di diverse dimensioni:

Lato sinistro più lungo

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"

Lato destro più lungo

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

Ho messo insieme un pacchetto R zeallot per affrontare proprio questo problema. zeallot include un operatore ( %<-%) per il disimballaggio, l'assegnazione multipla e la destrutturazione. Il LHS dell'espressione di assegnazione viene creato utilizzando le chiamate a c(). L'RHS dell'espressione di assegnazione può essere qualsiasi espressione che restituisce o è un vettore, un elenco, un elenco nidificato, un data frame, una stringa di caratteri, un oggetto data o oggetti personalizzati (supponendo che vi sia destructureun'implementazione).

Ecco la domanda iniziale rielaborata utilizzando zeallot (ultima versione, 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

Per ulteriori esempi e informazioni si può controllare la vignetta del pacchetto .


questo è esattamente quello che speravo di trovare, qualcosa che
abiliti

1
Che ne dici di assegnare una matrice a ogni nome di variabile?
StatsSorceress

33

Prendi in considerazione l'utilizzo della funzionalità inclusa nella base R.

Ad esempio, crea un dataframe di 1 riga (diciamo V) e inizializza le tue variabili al suo interno. Ora puoi assegnare a più variabili contemporaneamente V[,c("a", "b")] <- values[c(2, 4)], chiamare ciascuna per nome ( V$a) o usarne molte contemporaneamente ( values[c(5, 6)] <- V[,c("a", "b")]).

Se diventi pigro e non vuoi andare in giro a chiamare variabili dal dataframe, potresti attach(V)(anche se personalmente non lo faccio mai).

# 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 se potessi. Mi chiedo perché le persone si rifiutino di usare gli elenchi in casi così ovvi, ma piuttosto sporcano lo spazio di lavoro con tonnellate di variabili senza senso. (usi elenchi, dato che data.frame è un tipo speciale di elenco. Ne userei solo uno più generale.)
Joris Meys

ma non puoi avere diversi tipi di elementi nella stessa colonna, né puoi memorizzare dataframe o elenchi all'interno del tuo dataframe
skan

1
In realtà, puoi memorizzare elenchi in un data frame - "colonna elenco" di Google.

Non è un cattivo approccio, ha alcune comodità, ma non è nemmeno così difficile immaginare perché molti utenti non vorrebbero avere a che fare con la sintassi data.frame ogni volta che tentano di utilizzare o accedere a variabili assegnate in questo modo.
Brandon

13

ecco la mia idea. Probabilmente la sintassi è abbastanza semplice:

`%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à in questo modo:

> 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

questo però non è ben testato.


2
Koshke, mi sembra molto carino :-) Ma sono un po 'preoccupato per la precedenza degli operatori: gli operatori% something% sono piuttosto in alto, quindi il comportamento di eg c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, restituisce numerico ( 0)) può essere considerato sorprendente.
cbeleites insoddisfatto di SX

10

assignUn'opzione potenzialmente pericolosa (in quanto l'utilizzo è rischioso) sarebbe 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

Oppure suppongo che tu possa vettorializzarlo manualmente con la tua funzione usando mapplyche forse usa un valore predefinito ragionevole per l' envirargomento. Ad esempio, Vectorizerestituirà una funzione con le stesse proprietà di ambiente di assign, che in questo caso è namespace:base, oppure potresti semplicemente impostare envir = parent.env(environment(assignVec)).


8

Come altri hanno spiegato, non sembra esserci nulla di integrato ... ma potresti progettare una vassignfunzione come segue:

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

Una cosa da considerare però è come gestire i casi in cui ad esempio specifichi 3 variabili e 5 valori o viceversa. Qui ripeto semplicemente (o tronco) i valori in modo che abbiano la stessa lunghezza delle variabili. Forse un avvertimento sarebbe prudente. Ma consente quanto segue:

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

Mi piace, ma mi preoccuperei che potrebbe rompersi in alcuni casi in cui è stato chiamato dall'interno di una funzione (sebbene un semplice test di questo abbia funzionato, con mia leggera sorpresa). Mi puoi spiegare ...(), che mi sembra magia nera ...?
Ben Bolker

1
@Ben Bolker - Sì, ...()è magia nera estrema ;-). Accade così che quando la "chiamata di funzione" ...()viene sostituita, diventa un pairlist che può essere passato as.charactere voilà, hai gli argomenti come stringhe ...
Tommy

1
@Ben Bolker - E dovrebbe funzionare correttamente anche se chiamato dall'interno di una funzione poiché utilizza envir=parent.frame()- E puoi specificare, ad esempio, envir=globalenv()se vuoi.
Tommy

Ancora più interessante sarebbe avere questo come funzione di sostituzione: `vassign<-` <- function (..., envir = parent.frame (), value)e così via. Tuttavia, sembra che il primo oggetto da assegnare dovrebbe già esistere. Qualche idea?
cbeleites insoddisfatto di SX

@cbeleites - Sì, sarebbe più interessante ma non penso che tu possa aggirare la limitazione che il primo argomento deve esistere - ecco perché si chiama funzione di sostituzione :) ... ma fammi sapere se scopri il contrario !
Tommy

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

Serviva al mio scopo, cioè assegnare cinque 2 nelle prime cinque lettere.



4

Recentemente ho avuto un problema simile ed ecco il mio tentativo di utilizzo purrr::walk2

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

3

Se il tuo unico requisito è avere una singola riga di codice, allora che ne dici di:

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

2
stavo cercando una dichiarazione succinta ma immagino che non ce ne sia nessuna
user236215

Sono sulla stessa barca di @ user236215. quando il lato destro è un'espressione complicata che restituisce un vettore, la ripetizione del codice sembra molto sbagliata ...
attacco aereo

1

Temo che l'elegante soluzione che stai cercando (come c(a, b) = c(2, 4)) purtroppo non esista. Ma non arrenderti, non ne sono sicuro! La soluzione più vicina a cui riesco a pensare è questa:

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

o se sei disturbato dagli avvisi, disattivali:

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

Ma suppongo che tu non sia soddisfatto di questa soluzione, non sarei neanche io ...


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

Un'altra versione con ricorsione:

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

esempio:

> 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

La mia versione:

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

esempio:

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

0

Combinando alcune delle risposte fornite qui + un po 'di sale, che ne dici di questa soluzione:

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

Ho usato questo per aggiungere la sezione R qui: http://rosettacode.org/wiki/Sort_three_variables#R

Avvertenza: funziona solo per l'assegnazione di variabili globali (come <<-). Se esiste una soluzione migliore e più generale, pls. dimmelo nei commenti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.