2013-06-05 6 views
25

Tôi đang cố gắng tìm ra cách sử dụng := để thay thế nhiều cột cùng lúc trong một data.table bằng cách áp dụng một hàm được chia sẻ. Việc sử dụng điển hình điều này có thể là áp dụng một hàm chuỗi (ví dụ: gsub) cho tất cả các cột ký tự trong một bảng. Nó không phải là khó khăn để mở rộng data.frame cách làm điều này để một data.table, nhưng tôi đang tìm một phương pháp phù hợp với cách data.table làm việc.Chỉ định một cách tao nhã nhiều cột trong data.table với lapply()

Ví dụ:

library(data.table) 

m <- matrix(runif(10000), nrow = 100) 
df <- df1 <- df2 <- df3 <- as.data.frame(m) 
dt <- as.data.table(df) 
head(names(df)) 
head(names(dt)) 

## replace V20-V100 with sqrt 

# data.frame approach 
# by column numbers 
df1[20:100] <- lapply(df1[20:100], sqrt) 
# by reference to column numbers 
v <- 20:100 
df2[v] <- lapply(df2[v], sqrt) 
# by reference to column names 
n <- paste0("V", 20:100) 
df3[n] <- lapply(df3[n], sqrt) 

# data.table approach 
# by reference to column names 
n <- paste0("V", 20:100) 
dt[, n] <- lapply(dt[, n, with = FALSE], sqrt) 

tôi hiểu nó là hiệu quả hơn để lặp qua một vector của các tên cột bằng := gán:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE] 

Tôi không thích điều này vì tôi don' t như tham chiếu data.table trong biểu thức j. Tôi cũng biết rằng tôi có thể sử dụng := gán với lapply cho rằng tôi biết tên cột: (. Bạn có thể mở rộng này bằng cách xây dựng một biểu thức với tên cột không rõ)

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)] 

Dưới đây là những ý tưởng Tôi đã cố gắng về điều này, nhưng tôi đã không thể làm cho họ làm việc. Tôi có mắc sai lầm hay có cách tiếp cận nào khác mà tôi đang thiếu?

# possible data.table approaches? 
# by reference to column names; assignment works, but not lapply 
n <- paste0("V", 20:100) 
dt[, n := lapply(n, sqrt), with = FALSE] 
# by (smaller for example) list; lapply works, but not assignment 
dt[, list(list(V20, V30, V40, V50, V60)) := lapply(list(V20, V30, V40, V50, V60), sqrt)] 
# by reference to list; neither assignment nor lapply work 
l <- parse(text = paste("list(", paste(paste0("V", 20:100), collapse = ", "), ")")) 
dt[, eval(l) := lapply(eval(l), sqrt)] 

Trả lời

30

Vâng, bạn nói đúng trong câu hỏi ở đây:

Tôi hiểu hiệu quả hơn khi lặp qua một vectơ tên cột bằng cách sử dụng := để chỉ định:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

Ngoài: lưu ý phương pháp mới để làm điều đó là:

for (col in paste0("V", 20:100)) 
    dt[ , (col) := sqrt(dt[[col]])] 

with = FALSE là không dễ dàng để đọc cho dù đó gọi LHS hoặc RHS của :=. Kết thúc.

Như bạn đã biết, điều đó hiệu quả bởi vì mỗi cột một lần, do đó, bộ nhớ làm việc chỉ cần thiết cho một cột tại một thời điểm.Điều đó có thể tạo ra sự khác biệt giữa nó hoạt động và nó không thành công với lỗi out-of-memory đáng sợ.

Sự cố với lapply trên RHS của := là RHS (số lapply) được đánh giá trước tiên; tức là kết quả cho 80 cột được tạo. Đó là giá trị của 80 cột bộ nhớ mới mà đã được phân bổ và dân cư. Vì vậy, bạn cần 80 cột giá trị của RAM miễn phí cho hoạt động đó để thành công. Việc sử dụng RAM đó chiếm ưu thế so với hoạt động ngay lập tức sau đó của việc gán (plonking) 80 cột mới này vào các vị trí con trỏ cột của data.table.

Như @Frank chỉ ra, nếu bạn có nhiều cột (nói 10.000 hoặc hơn) thì chi phí nhỏ của việc gửi đến phương thức [.data.table bắt đầu tăng lên). Để loại bỏ phí trên đó có data.table::set dưới ?set được mô tả là "vòng lặp" :=. Tôi sử dụng vòng lặp for cho loại thao tác này. Đó là cách nhanh nhất và khá dễ dàng để viết và đọc.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(dt[[col]])) 

Mặc dù chỉ với 80 cột, không có vấn đề gì. (Lưu ý nó có thể phổ biến hơn để lặp lại set trên một số lượng lớn hàng hơn một số lượng lớn các cột.) Tuy nhiên, vòng lặp set không giải quyết được vấn đề của tham chiếu lặp lại với tên biểu tượng dt mà bạn đã đề cập trong câu hỏi:

Tôi không thích điều này vì tôi không thích tham chiếu dữ liệu.Trong biểu thức aj.

Đồng ý. Vì vậy, tốt nhất tôi có thể làm là hoàn nguyên về vòng lặp của bạn := nhưng thay vào đó hãy sử dụng get.

for (col in paste0("V", 20:100)) 
    dt[, (col) := sqrt(get(col))] 

Tuy nhiên, tôi sợ rằng việc sử dụng get trong j có thể không hiệu quả. Điểm chuẩn cần thiết cho điều đó, #1380. Ngoài ra, có lẽ nó là khó hiểu để sử dụng get() trên RHS nhưng không phải trên LHS. Để giải quyết mà chúng tôi có thể đường LHS và cho phép get() là tốt, #1381:

for (col in paste0("V", 20:100)) 
    dt[, get(col) := sqrt(get(col))] 

Ngoài ra, có lẽ value của set có thể được chạy trong phạm vi DT, #1382.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(get(col)) 
+1

Cảm ơn bạn rất nhiều vì đã đặt dấu ngoặc đơn quanh 'col'. Cho đến khi tôi nhớ rằng, tôi đã nhận được một cột gọi là "col". – Farrel

7

Đây có phải là những gì bạn đang tìm kiếm không?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x)) , .SDcols=20:100] 

Tôi đã nghe nói rằng việc sử dụng .SD không phải là quá hiệu quả vì nó làm cho một bản sao của bảng trước, nhưng nếu bảng của bạn không phải là khổng lồ (rõ ràng đó là tương đối tùy thuộc vào thông số kỹ thuật hệ thống của bạn) tôi nghi ngờ nó sẽ tạo ra nhiều sự khác biệt.

+4

Tôi đã nói rằng [ 'set' cũng có thể tăng tốc độ hoạt động như thế này] (http://stackoverflow.com/questions/16846380/how-to-apply-same-function-to-every -specified-column-in-a-data-table/16846530 # 16846530). – Frank

+0

@Frank +1 cho câu trả lời đó và tôi đã đánh dấu sách để tham khảo trong tương lai. Tôi sẽ không nghĩ sử dụng vòng lặp 'for' ở đây. Tài giỏi. –

+0

@Frank, tôi không biết về phương thức 'for' loop +' set'. Tôi sẽ phải suy nghĩ về việc sử dụng nó trong tương lai. –

13

Những nên làm việc nếu bạn muốn tham khảo các cột theo tên chuỗi:

n = paste0("V", 20:100) 
dt[, (n) := lapply(n, function(x) {sqrt(get(x))})] 

hoặc

dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})] 
+2

Một bổ sung) là cần thiết trong dòng cuối cùng: 'dt [, (n): = lapply (n, hàm (x) {sqrt (dt [[x]])})] ' – HywelMJ

+0

@HywelMJ cảm ơn, cố định – eddi

+0

Có thể thêm một' .SDcols' tùy chọn quá, chẳng hạn như 'dt [, (n): = lapply (.SD, sqrt), .SDcols = n] '? Hmm ..trên suy nghĩ thứ hai có lẽ Simon đã làm một cái gì đó tương tự rồi. –