Chọn / gán cho data.table khi tên biến được lưu trữ trong một vectơ ký tự


91

Làm thế nào để bạn tham chiếu đến các biến trong một data.tablenếu tên biến được lưu trữ trong một vectơ ký tự? Ví dụ, điều này hoạt động cho data.frame:

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

Làm cách nào để thực hiện thao tác tương tự này cho data.table, có hoặc không có :=ký hiệu? Điều hiển nhiên là dt[ , list(colname)]không hoạt động (tôi cũng không mong đợi điều đó).

Câu trả lời:


132

Hai cách để chọn (các) biến theo chương trình :

  1. with = FALSE:

    DT = data.table(col1 = 1:3)
    colname = "col1"
    DT[, colname, with = FALSE] 
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
    
  2. ..tiền tố 'dot dot' ( ):

    DT[, ..colname]    
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
    

Để biết thêm mô tả về ..ký hiệu 'dấu chấm' ( ), hãy xem Các tính năng mới trong 1.10.2 (nó hiện không được mô tả trong văn bản trợ giúp).

Để gán cho (các) biến, hãy đặt LHS của :=trong dấu ngoặc đơn:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

Cái sau được gọi là plonk cột , vì bạn thay thế toàn bộ vectơ cột bằng tham chiếu. Nếu có một tập hợp con i, nó sẽ gán lại bằng tham chiếu. Parens around (colname)là một cách viết tắt được giới thiệu trong phiên bản v1.9.4 vào CRAN tháng 10 năm 2014. Đây là mục tin tức :

Việc sử dụng with = FALSEwith :=hiện không được chấp nhận trong mọi trường hợp, do việc bao bọc LHS của :=bằng dấu ngoặc đơn đã được ưu tiên trong một thời gian.

colVar = "col1"
DT[, colVar := 1, with = FALSE]                 # deprecated, still works silently
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b)]  # no change
DT[, `:=`(...), by = ...]                       # no change

Xem thêm phần Chi tiết tại ?`:=`:

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

Và để trả lời câu hỏi khác trong nhận xét, đây là một cách (như thường lệ, có nhiều cách):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

hoặc, bạn có thể thấy dễ dàng hơn khi đọc, viết và gỡ lỗi chỉ với evalmột paste, tương tự như việc xây dựng một câu lệnh SQL động để gửi đến máy chủ:

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

Nếu bạn làm điều đó nhiều, bạn có thể xác định một hàm trợ giúp EVAL:

EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

Bây giờ data.table1.8.2 tự động tối ưu hóa jđể tăng hiệu quả, có thể bạn nên sử dụng evalphương pháp này. Các get()trong jngăn ngừa một số tối ưu, ví dụ.

Hoặc, có set(). Một hình thức chức năng, chi phí thấp :=, sẽ ổn ở đây. Thấy chưa ?set.

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66

1
Cảm ơn Matthew đã trả lời. Với = FALSE chắc chắn giải quyết được một phần vấn đề của tôi. Tuy nhiên, trong thực tế, tôi muốn thay thế cột bằng cumsum của cột. Tôi có thể tham chiếu tên cột theo biến ở phía bên phải của phép gán bằng cách nào đó không?
Frankc

Về cơ bản, tôi chỉ sắp xếp thứ tự bên ngoài cumsum bằng một tên khác không tồn tại bên trong dt và điều đó hoạt động tốt.
Frankc 12/09/12

1
Nhưng đó sẽ là toàn bộ dòng phụ! Không phải là rất thanh lịch :) Nhưng ok đôi khi nó hữu ích. Trong những trường hợp đó, tốt nhất nên bắt đầu tên biến bằng .hoặc ..để tránh bất kỳ dấu hiệu tiềm ẩn nào nếu DTđã từng chứa ký hiệu đó dưới dạng tên cột trong tương lai (và tuân theo quy ước tên cột không bắt đầu bằng .). Có một số yêu cầu tính năng để làm cho nó mạnh mẽ hơn đối với các vấn đề về phạm vi như vậy, chẳng hạn như thêm .()..().
Matt Dowle

Tôi đã trả lời trước khi tôi nhận thấy bạn đã chỉnh sửa câu trả lời của mình. Suy nghĩ đầu tiên của tôi là eval (phân tích cú pháp ()) nhưng vì lý do nào đó, tôi gặp khó khăn khi làm việc, khi nó nhận ra tôi chỉ làm điều đó bên ngoài. Đây là một câu trả lời tuyệt vời với rất nhiều điều tôi không nghĩ đến. Cảm ơn data.table nói chung, đó là một gói tuyệt vời.
Frankc

2
Lưu ý rằng bạn có thể sử dụng các loại chuỗi bán perl nội suy fn$từ các gói gsubfn để cải thiện khả năng đọc của giải pháp EVAL: library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" ).
G. Grothendieck

8

* Đây không phải là một câu trả lời thực sự, nhưng tôi không có đủ tín dụng để đăng bình luận: /

Dù sao, đối với bất kỳ ai có thể đang muốn thực sự tạo một cột mới trong bảng dữ liệu với tên được lưu trữ trong một biến, tôi có những điều sau đây để làm việc. Tôi không có manh mối nào về hiệu suất của nó. Bất kỳ đề xuất để cải thiện? Có an toàn không khi giả sử một cột mới không tên sẽ luôn được đặt tên V1?

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

Lưu ý rằng tôi có thể tham chiếu nó tốt trong sum () nhưng dường như không thể làm cho nó để gán trong cùng một bước. BTW, lý do tôi cần làm điều này là colname sẽ dựa trên đầu vào của người dùng trong một ứng dụng Shiny.


+1 để chỉ làm việc: Tôi đồng ý rằng đây không phải là "cách" để làm điều này, nhưng chỉ dành 45 phút cho mỗi bài đăng SO về chủ đề này, đây là giải pháp duy nhất mà tôi thực sự có thể đạt được làm việc - cảm ơn bạn đã dành thời gian để chỉ ra nó!
neuropsych,

Mừng vì tôi có thể giúp! Thật không may, tôi chưa bao giờ tìm thấy một giải pháp thanh lịch hơn trực tiếp bằng cách sử dụng data.tables, mặc dù 3 lớp lót này không khủng khiếp. Trong kịch bản của tôi, tôi đã nhận ra rằng một giải pháp thay thế đơn giản hơn sẽ là sử dụng ngăn nắp để chỉ làm cho dữ liệu của tôi "dài" thay vì "rộng", vì dựa trên đầu vào của người dùng, tôi luôn có thể lọc trên một cột duy nhất thay vì chọn từ một tập hợp trong số các cột.
efh0888

2
Không an toàn khi cho rằng đó V1là tên mới. Ví dụ: nếu bạn đọc csv với freadvà có một cột chưa được đặt tên, nó sẽ có V1tên (và read.csvsẽ cung cấp X). Vì vậy, có thể bảng của bạn đã có một V1. Có thể chỉ cần lấy tên bằngnames(DT)[length(names(DT))]
dracodoc

2

Đối với nhiều cột và một hàm được áp dụng trên các giá trị cột.

Khi cập nhật các giá trị từ một hàm, RHS phải là một đối tượng danh sách, do đó, sử dụng vòng lặp trên .SDvớilapply sẽ thực hiện được mẹo.

Ví dụ dưới đây chuyển đổi cột số nguyên thành cột số

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 

2

Truy xuất nhiều cột từ data.table thông qua biến hoặc hàm:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

mà tất cả đều mang lại

   that whatever
1:    1        1
2:    2        2

Tôi tìm .SDcolscách thanh lịch nhất.


1

Bạn có thể thử cái này

colname <- as.name ("COL_NAME")

DT2 <- DT [, list (COL_SUM = sum (eval (colname, .SD))), by = c (group)]


1
Bạn nên thêm phần giải thích vào mã của mình thay vì chỉ đăng mã.
MBorg
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.