Przypisz wiele nowych zmiennych do LHS w jednej linii

89

Chcę przypisać wiele zmiennych w jednym wierszu w R. Czy można zrobić coś takiego?

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'

Zwykle chcę przypisać około 5-6 zmiennych w jednym wierszu, zamiast mieć wiele wierszy. Czy jest alternatywa?

user236215
źródło
masz na myśli coś jak w PHP list($a, $b) = array(1, 2)? To byłoby miłe! +1.
TMS
@Tomas T - Myślę, że moja vassignsugestia poniżej jest bliska ... :)
Tommy
Uwaga: średniki nie są potrzebne w tym kawałku R.
Iterator,
1
Jeśli spróbujesz tego w odpowiednim środowisku, byłoby to tak proste, jak X <- list();X[c('a','b')] <- values[c(2,4)]. OK, nie przypisujesz ich w obszarze roboczym, ale ładnie przechowujesz je razem na liście. Wolałbym to zrobić w ten sposób.
Joris Meys,
7
lubię Pythona, po prostu a, b = 1,2. wszystkie poniższe odpowiedzi są 100x trudniejsze
appleLover

Odpowiedzi:

39

Świetna odpowiedź na blogu Walcząc z problemami

Jest to zaczerpnięte stamtąd, z bardzo niewielkimi modyfikacjami.

KORZYSTANIE Z NASTĘPUJĄCYCH TRZECH FUNKCJI (plus jedna pozwalająca na tworzenie list o różnych rozmiarach)

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


Następnie do wykonania:

Zgrupuj lewą stronę używając nowej funkcji g() Prawa strona powinna być wektorem lub listą Użyj nowo utworzonego operatora binarnego%=%

# 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


Przykład korzystania z list o różnych rozmiarach:

Dłuższa lewa strona

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"

Dłuższa prawa strona

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
źródło
34

Złożyłem zeallota pakietu R, aby rozwiązać ten właśnie problem. zeallot zawiera operator ( %<-%) do rozpakowywania, wielokrotnego i niszczenia przypisania. LHS wyrażenia przypisania jest budowany przy użyciu wywołań funkcji c(). RHS wyrażenia przypisania może być dowolnym wyrażeniem, które zwraca lub jest wektorem, listą, listą zagnieżdżoną, ramką danych, ciągiem znaków, obiektem daty lub obiektami niestandardowymi (zakładając, że istnieje destructureimplementacja).

Oto pierwsze pytanie przerobione przy użyciu zeallota (najnowsza wersja, 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

Więcej przykładów i informacji można znaleźć w winiecie na pakiet .

nteetor
źródło
to jest dokładnie to, co miałem nadzieję znaleźć, coś, co włącza składnię podobną do Pythona, o którą prosił OP, zaimplementowaną w pakiecie R
jafelds
1
A co z przypisaniem macierzy do każdej nazwy zmiennej?
StatsSorceress
33

Rozważ użycie funkcji zawartych w podstawowym R.

Na przykład utwórz 1-wierszową ramkę danych (powiedzmy V) i zainicjuj w niej swoje zmienne. Teraz możesz przypisać do wielu zmiennych naraz V[,c("a", "b")] <- values[c(2, 4)], wywołać każdą z nich po nazwie ( V$a) lub używać wielu z nich jednocześnie ( values[c(5, 6)] <- V[,c("a", "b")]).

Jeśli zrobisz się leniwy i nie będziesz chciał wywoływać zmiennych z ramki danych, możesz attach(V)(chociaż ja osobiście nigdy tego nie robię).

# 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
źródło
4
+10, gdybym mógł. Zastanawiam się, dlaczego ludzie odmawiają używania list w tak oczywistych przypadkach, a raczej zaśmiecają przestrzeń roboczą tonami bezsensownych zmiennych. (robisz list jako metodę data.frame jest to szczególny rodzaj listy bym po prostu użyć bardziej ogólny.).
Joris Meys
ale nie możesz mieć różnych rodzajów elementów w tej samej kolumnie, ani nie możesz przechowywać ramek danych ani list w swojej ramce danych
skanuj
1
Właściwie możesz przechowywać listy w ramce danych - „kolumna listy” google.
Nie jest to złe podejście, ma pewne udogodnienia, ale nie jest też tak trudno wyobrazić sobie, dlaczego wielu użytkowników nie chciałoby mieć do czynienia ze składnią data.frame za każdym razem, gdy próbowali użyć lub uzyskać dostęp do przypisanych w ten sposób zmiennych.
Brandon
13

oto mój pomysł. Prawdopodobnie składnia jest dość prosta:

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

daje tak:

> 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

nie jest to jednak dobrze przetestowane.

kohske
źródło
2
Koshke, wygląda mi bardzo ładnie :-) Ale trochę martwię się o pierwszeństwo operatorów: operatory% coś% są dość wysoko, więc zachowanie np. c(c, d) %tin% c(1, 2) + 3(=> C = 1, d = 1, zwraca numeryczne ( 0)) można uznać za zaskakujące.
cbeleites niezadowolony z SX
10

Potencjalnie niebezpieczną assignopcją (o ile używanie jest ryzykowne) byłoby 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

Albo przypuszczam, że możesz ręcznie wektoryzować go ręcznie za pomocą własnej funkcji, używając mapplyprawdopodobnie rozsądnej wartości domyślnej dla envirargumentu. Na przykład Vectorizezwróci funkcję o takich samych właściwościach środowiska assign, co w tym przypadku namespace:base, lub możesz po prostu ustawić envir = parent.env(environment(assignVec)).

joran
źródło
8

Jak wyjaśnili inni, wygląda na to, że nic nie jest wbudowane. ... ale możesz zaprojektować vassignfunkcję w następujący sposób:

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

Jedną rzeczą do rozważenia jest jednak to, jak postępować w przypadkach, w których podajesz np. 3 zmienne i 5 wartości lub odwrotnie. Tutaj po prostu powtarzam (lub skracam) wartości, aby miały taką samą długość jak zmienne. Może ostrzeżenie byłoby rozsądne. Ale pozwala na:

vassign(aa,bb,cc,dd, values=0)
cc # 0
Tommy
źródło
Podoba mi się to, ale martwiłbym się, że może się zepsuć w pewnym przypadku, gdy zostanie wywołany z funkcji (chociaż prosty test zadziałał, ku mojemu łagodnemu zaskoczeniu). Czy możesz wyjaśnić ...(), co dla mnie wygląda jak czarna magia ...?
Ben Bolker,
1
@Ben Bolker - Tak, ...()to ekstremalna czarna magia ;-). Tak się składa, że ​​kiedy "wywołanie funkcji" ...()zostaje zastąpione, staje się listą par, do której można przekazać as.characteri voila, argumenty otrzymano jako łańcuchy ...
Tommy
1
@Ben Bolker - I powinien działać poprawnie, nawet gdy jest wywoływany z funkcji, ponieważ używa envir=parent.frame()- I możesz określić, na przykład, envir=globalenv()jeśli chcesz.
Tommy
Jeszcze fajniej byłoby mieć to jako funkcję zastępczą: `vassign<-` <- function (..., envir = parent.frame (), value)i tak dalej. Wydaje się jednak, że pierwszy obiekt do przypisania musiałby już istnieć. Jakieś pomysły?
cbeleites niezadowolony z SX
@cbeleites - Tak, byłoby fajniej, ale nie sądzę, aby można było obejść ograniczenie, które musi istnieć pierwszy argument - dlatego nazywa się to funkcją zastępczą :) ... ale daj mi znać, jeśli dowiesz się inaczej !
Tommy
6
list2env(setNames(as.list(rep(2,5)), letters[1:5]), .GlobalEnv)

Spełniło moje zadanie, tj. Przypisanie pięciu dwójek do pierwszych pięciu liter.

Rafiqul Haider
źródło
4

Miałem ostatnio podobny problem i oto moja próba użycia purrr::walk2

purrr::walk2(letters,1:26,assign,envir =parent.frame()) 
Lerong
źródło
3

Jeśli Twoim jedynym wymaganiem jest posiadanie jednej linii kodu, co powiesz na:

> a<-values[2]; b<-values[4]
NPE
źródło
2
szukałem zwięzłego oświadczenia, ale chyba go nie ma
user236215
Jestem na tej samej łodzi co @ user236215. kiedy po prawej stronie znajduje się skomplikowane wyrażenie zwracające wektor, powtarzający się kod wydaje się bardzo zły ...
nalot
1

Obawiam się, że eleganckie rozwiązanie, którego szukasz (lubisz c(a, b) = c(2, 4)), niestety nie istnieje. Ale nie poddawaj się, nie jestem pewien! Najbliższe rozwiązanie, jakie przychodzi mi do głowy, to:

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

lub jeśli niepokoją Cię ostrzeżenia, wyłącz je:

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

Ale przypuszczam, że nie jesteś zadowolony z tego rozwiązania, ja też nie byłbym ...

TMS
źródł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
naught101
źródło
0

Inna wersja z rekurencją:

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

przykład:

> 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

Moja wersja:

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

przykład:

> let(c(x, y), 1:2 + 3)
> x
[1] 4
> y
[1] 
Fedor Goncharov
źródło
0

Łącząc niektóre odpowiedzi podane tutaj + trochę soli, co powiesz na to rozwiązanie:

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

Użyłem tego, aby dodać sekcję R tutaj: http://rosettacode.org/wiki/Sort_three_variables#R

Uwaga: działa tylko w przypadku przypisywania zmiennych globalnych (takich jak <<-). Jeśli istnieje lepsze, bardziej ogólne rozwiązanie, proszę. powiedz mi w komentarzach.

vonjd
źródło