Weisen Sie LHS mehrere neue Variablen in einer einzigen Zeile zu


87

Ich möchte in R mehrere Variablen in einer einzigen Zeile zuweisen. Ist es möglich, so etwas zu tun?

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'

Normalerweise möchte ich ungefähr 5-6 Variablen in einer einzelnen Zeile zuweisen, anstatt mehrere Zeilen zu haben. Gibt es eine Alternative?


du meinst so etwas wie in PHP list($a, $b) = array(1, 2)? Das wäre nett! +1.
TMS

@ Thomas T - Ich denke, mein vassignVorschlag unten kommt nahe ... :)
Tommy

Hinweis: Semikolons werden für dieses Bit von R. nicht benötigt
Iterator

1
Wenn Sie dies in einer geeigneten Umgebung versuchen würden, wäre dies so einfach wie X <- list();X[c('a','b')] <- values[c(2,4)]. OK, Sie weisen sie nicht im Arbeitsbereich zu, sondern halten sie in einer Liste gut zusammen. Ich würde es lieber so machen.
Joris Meys

7
Ich mag Python, nur a, b = 1,2. Alle Antworten unten sind 100x schwerer
AppleLover

Antworten:


38

Auf dem Blog "Durch Probleme kämpfen" gibt es eine großartige Antwort

Dies wird von dort mit sehr geringfügigen Änderungen übernommen.

VERWENDUNG DER FOLGENDEN DREI FUNKTIONEN (Plus eine zum Zulassen von Listen unterschiedlicher Größe)

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


Dann ausführen:

Gruppieren Sie die linke Seite mit der neuen Funktion. g() Die rechte Seite sollte ein Vektor oder eine Liste sein. Verwenden Sie den neu erstellten Binäroperator%=%

# 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


Beispiel mit Listen unterschiedlicher Größe:

Längere linke Seite

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"

Längere rechte Seite

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"

33

Ich habe einen Zeallot für ein R-Paket zusammengestellt , um genau dieses Problem anzugehen. zeallot enthält einen Operator ( %<-%) zum Entpacken, Mehrfach- und Destrukturieren. Die LHS des Zuweisungsausdrucks wird mithilfe von Aufrufen an erstellt c(). Die RHS des Zuweisungsausdrucks kann ein beliebiger Ausdruck sein, der einen Vektor, eine Liste, eine verschachtelte Liste, einen Datenrahmen, eine Zeichenfolge, ein Datumsobjekt oder benutzerdefinierte Objekte zurückgibt oder ist (vorausgesetzt, es gibt eine destructureImplementierung).

Hier ist die erste Frage, die mit zeallot (neueste Version, 0.0.5) überarbeitet wurde.

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

Weitere Beispiele und Informationen finden Sie in der Paketvignette .


Dies ist genau das, was ich mir erhofft hatte, etwas, das die Python-ähnliche Syntax ermöglicht, nach der das OP gefragt hat und die in einem R-Paket implementiert ist
jafelds

1
Wie wäre es, jedem Variablennamen eine Matrix zuzuweisen?
StatsSorceress

32

Erwägen Sie die Verwendung der in Basis R enthaltenen Funktionen.

Erstellen Sie beispielsweise einen 1-zeiligen Datenrahmen (z. B. V) und initialisieren Sie Ihre Variablen darin. Jetzt können Sie mehreren Variablen gleichzeitig zuweisen V[,c("a", "b")] <- values[c(2, 4)], jede mit name ( V$a) aufrufen oder viele gleichzeitig verwenden ( values[c(5, 6)] <- V[,c("a", "b")]).

Wenn Sie faul werden und keine Variablen aus dem Datenrahmen aufrufen möchten, können Sie dies attach(V)(obwohl ich es persönlich nie tue).

# 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 wenn ich könnte. Ich frage mich, warum die Leute sich in so offensichtlichen Fällen weigern, Listen zu verwenden, sondern den Arbeitsbereich mit Tonnen bedeutungsloser Variablen übersäen. (Sie verwenden Listen, da ein data.frame eine spezielle Art von Liste ist. Ich würde nur eine allgemeinere verwenden.)
Joris Meys

Sie können jedoch weder verschiedene Arten von Elementen in derselben Spalte haben, noch Datenrahmen oder Listen in Ihrem Datenrahmen speichern
Skan

1
Tatsächlich können Sie Listen in einem Datenrahmen speichern - Google "Listenspalte".

Es ist kein schlechter Ansatz, es hat einige Vorteile, aber es ist auch nicht schwer vorstellbar, warum viele Benutzer nicht jedes Mal mit der Syntax von data.frame umgehen müssen, wenn sie versuchen, auf diese Weise zugewiesene Variablen zu verwenden oder darauf zuzugreifen.
Brandon

13

Hier ist meine Idee. Wahrscheinlich ist die Syntax recht einfach:

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

gibt so:

> 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

Dies ist jedoch nicht gut getestet.


2
Koshke, sieht für mich sehr gut aus :-) Aber ich mache mir ein bisschen Sorgen um die Priorität der Operatoren: Die% etwas% Operatoren sind ziemlich hoch, so dass das Verhalten von zB c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, numerisch () zurückgibt. 0)) kann als überraschend angesehen werden.
cbeleites unglücklich mit SX

10

Eine potenziell gefährliche assignOption (sofern die Verwendung riskant ist) wäre 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

Oder ich nehme an, Sie könnten es selbst manuell mit Ihrer eigenen Funktion vektorisieren, indem Sie mapplymöglicherweise einen sinnvollen Standard für das envirArgument verwenden. Gibt beispielsweise Vectorizeeine Funktion mit denselben Umgebungseigenschaften zurück assign, wie in diesem Fall namespace:base, oder Sie können sie einfach festlegen envir = parent.env(environment(assignVec)).


8

Wie andere erklärt haben, scheint nichts eingebaut zu sein. ... aber Sie können eine vassignFunktion wie folgt entwerfen :

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

Eine zu berücksichtigende Sache ist jedoch, wie mit den Fällen umgegangen werden soll, in denen Sie z. B. 3 Variablen und 5 Werte angeben oder umgekehrt. Hier wiederhole (oder schneide) ich einfach die Werte, um die gleiche Länge wie die Variablen zu haben. Vielleicht wäre eine Warnung umsichtig. Aber es erlaubt Folgendes:

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

Ich mag das, aber ich würde mir Sorgen machen, dass es in einigen Fällen, in denen es aus einer Funktion heraus aufgerufen wurde, kaputt gehen könnte (obwohl ein einfacher Test davon zu meiner leichten Überraschung funktioniert hat). Kannst du erklären ...(), was für mich wie schwarze Magie aussieht ...?
Ben Bolker

1
@ Ben Bolker - Ja, ...()ist extreme schwarze Magie ;-). Es kommt also vor, dass wenn der "Funktionsaufruf" ersetzt ...()wird, er zu einer Paarliste wird, an die übergeben werden kann, as.characterund voila, Sie haben die Argumente als Zeichenfolgen erhalten ...
Tommy

1
@ Ben Bolker - Und es sollte korrekt funktionieren, auch wenn es aus einer Funktion heraus aufgerufen wird, da es verwendet envir=parent.frame()- Und Sie können zB angeben, envir=globalenv()ob Sie möchten.
Tommy

Noch cooler wäre dies als Ersatzfunktion: `vassign<-` <- function (..., envir = parent.frame (), value)und so weiter. Es scheint jedoch, dass das erste zuzuweisende Objekt bereits vorhanden sein muss. Irgendwelche Ideen?
cbeleites unglücklich mit SX

@cbeleites - Ja, das wäre cooler, aber ich glaube nicht, dass Sie die Einschränkung umgehen können, dass das erste Argument existieren muss - deshalb wird es als Ersatzfunktion bezeichnet :) ... aber lassen Sie es mich wissen, wenn Sie etwas anderes herausfinden !
Tommy

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

Erfüllte meinen Zweck, dh fünf Zwei in die ersten fünf Buchstaben zuzuweisen.



4

Hatte kürzlich ein ähnliches Problem und hier war mein Versuch mit purrr::walk2

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

3

Wenn Sie nur eine einzige Codezeile benötigen, wie wäre es dann mit:

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

2
suchte nach einer prägnanten Aussage, aber ich denke, es gibt keine
user236215

Ich bin auf dem gleichen Boot wie @ user236215. Wenn die rechte Seite ein komplizierter Ausdruck ist, der einen Vektor zurückgibt, scheint das Wiederholen des Codes sehr falsch zu sein ...
Luftangriff

1

Ich befürchte, dass es c(a, b) = c(2, 4)leider keine elegante Lösung gibt, nach der Sie (wie ) suchen . Aber gib nicht auf, ich bin mir nicht sicher! Die nächste Lösung, die ich mir vorstellen kann, ist diese:

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

oder wenn Sie mit Warnungen belästigt sind, schalten Sie sie aus:

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

Aber ich nehme an, Sie sind mit dieser Lösung nicht zufrieden, ich wäre es auch nicht ...


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

Eine andere Version mit Rekursion:

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

Beispiel:

> 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

Meine Version:

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

Beispiel:

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

0

Wenn Sie einige der hier gegebenen Antworten + ein wenig Salz kombinieren, wie wäre es mit dieser Lösung:

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

Ich habe dies verwendet, um den R-Abschnitt hier hinzuzufügen: http://rosettacode.org/wiki/Sort_three_variables#R

Vorsichtsmaßnahme: Es funktioniert nur zum Zuweisen globaler Variablen (wie <<-). Wenn es eine bessere, allgemeinere Lösung gibt, pls. Sag es mir in den Kommentaren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.