Gán nhiều biến mới trên LHS trong một dòng


89

Tôi muốn gán nhiều biến trong một dòng duy nhất trong R. Có thể làm điều gì đó như thế này không?

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'

Thông thường, tôi muốn gán khoảng 5-6 biến trong một dòng, thay vì có nhiều dòng. Có giải pháp thay thế không?


bạn có nghĩa là một cái gì đó như trong PHP list($a, $b) = array(1, 2)? Thật là tốt! +1.
TMS

@Tomas T - Tôi nghĩ vassignđề xuất của tôi bên dưới đã kết thúc ... :)
Tommy

Lưu ý: Dấu chấm phẩy không cần thiết cho bit này của R.
Lặp lại

1
Nếu bạn thử điều này trong một môi trường thích hợp, điều đó sẽ dễ dàng như vậy X <- list();X[c('a','b')] <- values[c(2,4)]. OK, bạn không chỉ định chúng trong không gian làm việc, nhưng hãy giữ chúng độc đáo với nhau trong một danh sách. Tôi muốn làm theo cách đó.
Joris Meys

7
tôi thích python, chỉ cần a, b = 1,2. tất cả các câu trả lời bên dưới khó hơn 100 lần
appleLover

Câu trả lời:


39

Có một câu trả lời tuyệt vời trên Blog Đấu tranh Qua các Vấn đề

Điều này được lấy từ đó, với những sửa đổi rất nhỏ.

SỬ DỤNG BA CHỨC NĂNG SAU ĐÂY (Thêm một CHỨC NĂNG để cho phép danh sách các kích thước khác nhau)

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


Sau đó để thực hiện:

Nhóm bên tay trái bằng cách sử dụng hàm mới g() Phía bên phải phải là vectơ hoặc danh sách Sử dụng toán tử nhị phân mới tạo%=%

# 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


Ví dụ sử dụng danh sách các kích thước khác nhau:

Bên tay trái dài hơn

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ài hơn bên tay phải

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

Tôi đã đặt cùng một gói zeallot R để giải quyết vấn đề này. zeallot bao gồm một toán tử ( %<-%) để giải nén, nhiều và phân công hủy cấu trúc. LHS của biểu thức gán được xây dựng bằng cách sử dụng các lệnh gọi tới c(). RHS của biểu thức gán có thể là bất kỳ biểu thức nào trả về hoặc là vectơ, danh sách, danh sách lồng nhau, khung dữ liệu, chuỗi ký tự, đối tượng ngày tháng hoặc các đối tượng tùy chỉnh (giả sử có một destructuretriển khai).

Đây là câu hỏi ban đầu được làm lại bằng zeallot (phiên bản mới nhất, 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

Để biết thêm các ví dụ và thông tin, người ta có thể kiểm tra họa tiết gói .


đây chính xác là những gì tôi đã hy vọng để tìm thấy, cái gì mà cho phép trăn giống như cú pháp OP được yêu cầu, thực hiện trong một gói phần mềm R
jafelds

1
Còn việc gán một ma trận cho mỗi tên biến thì sao?
Số liệu thống kêSorceress

33

Cân nhắc sử dụng chức năng có trong cơ sở R.

Ví dụ: tạo khung dữ liệu 1 hàng (giả sử V) và khởi tạo các biến của bạn trong đó. Giờ đây, bạn có thể gán cho nhiều biến cùng một lúc V[,c("a", "b")] <- values[c(2, 4)], gọi từng biến bằng tên ( V$a) hoặc sử dụng nhiều biến trong số chúng cùng một lúc ( values[c(5, 6)] <- V[,c("a", "b")]).

Nếu bạn lười biếng và không muốn lặp lại việc gọi các biến từ khung dữ liệu, bạn có thể attach(V)(mặc dù cá nhân tôi chưa bao giờ làm điều đó).

# 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 nếu tôi có thể. Tôi tự hỏi tại sao mọi người lại từ chối sử dụng danh sách trong những trường hợp rõ ràng như vậy, mà thay vào đó là rải rác không gian làm việc với hàng tấn biến vô nghĩa. (bạn sử dụng danh sách, như một dữ liệu. khung là một loại danh sách đặc biệt. Tôi chỉ muốn sử dụng một danh sách tổng quát hơn.)
Joris Meys

nhưng bạn không thể có loại khác nhau của các yếu tố trong cùng một cột, cũng không phải bạn có thể lưu trữ dataframes hoặc danh sách bên dataframe của bạn
SKAN

1
Trên thực tế, bạn có thể lưu trữ danh sách trong một khung dữ liệu - google "cột danh sách".

Đây không phải là một cách tiếp cận tồi, nó có một số tiện lợi, nhưng cũng không khó để tưởng tượng tại sao nhiều người dùng sẽ không muốn phải xử lý cú pháp data.frame mỗi khi họ cố gắng sử dụng hoặc truy cập các biến được chỉ định theo cách này.
Brandon

13

đây là ý tưởng của tôi. Có lẽ cú pháp khá đơn giản:

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

đưa ra như thế này:

> 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

điều này không được thử nghiệm tốt mặc dù.


2
Koshke, trông rất đẹp đối với tôi :-) Nhưng tôi hơi lo lắng về mức độ ưu tiên của toán tử: các toán tử% something% khá cao, vì vậy hành vi của ví dụ: c(c, d) %tin% c(1, 2) + 3(=> c = 1, d = 1, trả về số ( 0)) có thể được coi là đáng ngạc nhiên.
cbeleites không hài lòng với SX

10

Một assigntùy chọn tiềm ẩn nguy hiểm (càng nhiều càng nguy hiểm khi sử dụng ) sẽ là 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

Hoặc tôi cho rằng bạn có thể tự vector hóa nó theo cách thủ công với hàm của riêng bạn bằng cách sử dụng hàm mapplyđó có thể sử dụng một mặc định hợp lý cho envirđối số. Ví dụ: Vectorizesẽ trả về một hàm có cùng thuộc tính môi trường assign, trong trường hợp này là namespace:base, hoặc bạn có thể chỉ cần đặt envir = parent.env(environment(assignVec)).


8

Như những người khác đã giải thích, dường như không có bất kỳ thứ gì được tích hợp sẵn. ... nhưng bạn có thể thiết kế một vassignchức năng như sau:

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

Tuy nhiên, một điều cần xem xét là làm thế nào để xử lý các trường hợp ví dụ như bạn chỉ định 3 biến và 5 giá trị hoặc ngược lại. Ở đây tôi chỉ cần lặp lại (hoặc cắt bớt) các giá trị có cùng độ dài với các biến. Có lẽ một cảnh báo sẽ là thận trọng. Nhưng nó cho phép những điều sau:

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

Tôi thích điều này, nhưng tôi sẽ lo lắng rằng nó có thể bị hỏng trong một số trường hợp khi nó được gọi từ bên trong một hàm (mặc dù một thử nghiệm đơn giản về điều này đã hoạt động, trước sự ngạc nhiên nhẹ của tôi). Bạn có thể giải thích ...(), trông giống như ma thuật đen với tôi ...?
Ben Bolker

1
@Ben Bolker - Đúng, ...()là ma thuật đen cực độ ;-). Nó như vậy sẽ xảy ra rằng khi các "chức năng gọi là" ...()bị thế, nó trở thành một pairlist mà có thể được chuyển tới as.charactervà thì đấy, bạn có những lập luận như dây đàn ...
Tommy

1
@Ben Bolker - Và nó sẽ hoạt động chính xác ngay cả khi được gọi từ bên trong một hàm vì nó sử dụng envir=parent.frame()- Và bạn có thể chỉ định ví dụ envir=globalenv()nếu bạn muốn.
Tommy

Thậm chí mát hơn sẽ có chức năng này làm chức năng thay thế: `vassign<-` <- function (..., envir = parent.frame (), value)và như vậy. Tuy nhiên, có vẻ như đối tượng đầu tiên được gán sẽ cần phải tồn tại. Có ý kiến ​​gì không?
cbeleites không hài lòng với SX

@cbeleites - Vâng, điều đó sẽ hay hơn nhưng tôi không nghĩ rằng bạn có thể khắc phục hạn chế mà đối số đầu tiên phải tồn tại - đó là lý do tại sao nó được gọi là hàm thay thế :) ... nhưng hãy cho tôi biết nếu bạn tìm ra cách khác !
Tommy

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

Phục vụ mục đích của tôi, tức là, gán năm số 2 thành năm chữ cái đầu tiên.



4

Gần đây đã gặp sự cố tương tự và đây là tôi đã thử sử dụng purrr::walk2

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

3

Nếu yêu cầu duy nhất của bạn là có một dòng mã, thì làm thế nào về:

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

2
đang tìm kiếm một tuyên bố ngắn gọn nhưng tôi đoán chẳng có ai
user236215

Tôi ở cùng thuyền với @ user236215. khi phía bên tay phải là một biểu thức phức tạp trở về một vector, lặp đi lặp lại mã dường như rất sai ...
không kích

1

Tôi e rằng giải pháp thanh lịch mà bạn đang tìm kiếm (như c(a, b) = c(2, 4)) không may không tồn tại. Nhưng đừng bỏ cuộc, tôi không chắc! Giải pháp gần nhất mà tôi có thể nghĩ đến là:

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

hoặc nếu bạn thấy phiền với các cảnh báo, hãy tắt chúng đi:

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

Nhưng tôi cho rằng bạn không hài lòng với giải pháp này, tôi cũng sẽ không ...


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

Một phiên bản khác với đệ quy:

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

thí dụ:

> 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

Phiên bản của tôi:

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

thí dụ:

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

0

Kết hợp một số câu trả lời được đưa ra ở đây + một chút muối, còn giải pháp này:

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

Tôi đã sử dụng cái này để thêm phần R tại đây: http://rosettacode.org/wiki/Sort_three_variables#R

Lưu ý: Nó chỉ hoạt động để gán các biến toàn cục (như <<-). Nếu có một giải pháp tốt hơn, tổng quát hơn, xin vui lòng. Nói cho tôi biết trong các ý kiến.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.