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?


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"

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 .


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

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.


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


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

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.



4

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

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

3

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

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

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


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

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] 

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.