2014-09-20 19 views
8

Chức năng set hoặc biểu thức := bên trong [.data.table có nghĩa là dữ liệu có thể được cập nhật theo tham chiếu. Những gì tôi không hiểu rất rõ là cách hành vi này khác với việc phân công lại kết quả của một thao tác với data.frame ban đầu."cập nhật theo tham chiếu" so với bản sao nông

keepcols<-function(DF,cols){ 
    eval.parent(substitute(DF<-DF[,cols,with=FALSE])) 
} 
keeprows<-function(DF,i){ 
    eval.parent(substitute(DF<-DF[i,])) 
} 

Vì RHS trong biểu thức <- là một bản sao cạn của dataframe ban đầu trong các phiên bản gần đây của R, các chức năng này có vẻ khá hiệu quả. Phương thức R cơ bản này khác với phương thức data.table tương đương như thế nào? Sự khác biệt chỉ liên quan đến tốc độ hay sử dụng bộ nhớ? Khi nào sự khác biệt lớn nhất?

Một số điểm chuẩn (tốc độ). Dường như sự khác biệt về tốc độ là không đáng kể khi tập dữ liệu chỉ có hai biến và trở nên lớn hơn với nhiều biến hơn.

library(data.table) 

# Long dataset 
N=1e7; K=100 
DT <- data.table(
    id1 = sample(sprintf("id%03d",1:K), N, TRUE),  
    v1 = sample(5, N, TRUE)           
) 
system.time(DT[,a_inplace:=mean(v1)]) 
user system elapsed 
0.060 0.013 0.077 
system.time(DT[,a_inplace:=NULL]) 
user system elapsed 
0.044 0.010 0.060 


system.time(DT <- DT[,c(.SD,a_usual=mean(v1)),.SDcols=names(DT)]) 
user system elapsed 
0.132 0.025 0.161 
system.time(DT <- DT[,list(id1,v1)]) 
user system elapsed 
0.124 0.026 0.153 


# Wide dataset 
N=1e7; K=100 
DT <- data.table(
    id1 = sample(sprintf("id%03d",1:K), N, TRUE),  
    id2 = sample(sprintf("id%03d",1:K), N, TRUE),  
    id3 = sample(sprintf("id%010d",1:(N/K)), N, TRUE), 
    v1 = sample(5, N, TRUE),       
    v2 = sample(1e6, N, TRUE),       
    v3 = sample(round(runif(100,max=100),4), N, TRUE)      
) 
system.time(DT[,a_inplace:=mean(v1)]) 
user system elapsed 
    0.057 0.014 0.089 
system.time(DT[,a_inplace:=NULL]) 
user system elapsed 
    0.038 0.009 0.061 

system.time(DT <- DT[,c(.SD,a_usual=mean(v1)),.SDcols=names(DT)]) 
user system elapsed 
2.483 0.146 2.602 
system.time(DT <- DT[,list(id1,id2,id3,v1,v2,v3)]) 
user system elapsed 
1.143 0.088 1.220 

Bây giờ tôi hiểu rằng setkey hoặc X[Y,:=] không thể được thể hiện trong nhiệm kỳ bản sao cạn - vì vậy tôi thực sự chỉ là hỏi về tạo/xóa cột mới hoặc các hàng.

Trả lời

12

Trong các hàm data.table, :=tất cảset* cập nhật đối tượng bằng tham chiếu. Điều này đã được giới thiệu vào khoảng năm 2012 IIRC. Và tại thời điểm này, cơ sở R không bản sao nông, nhưng sâu được sao chép. Shallow bản sao đã được giới thiệu kể từ 3.1.0.


Đó là một câu trả lời dài dòng/dài, nhưng tôi nghĩ rằng đây trả lời hai câu hỏi đầu tiên của bạn:

thế nào là cơ sở này phương pháp R khác nhau từ tương đương data.table? Sự khác biệt chỉ liên quan đến tốc độ hay sử dụng bộ nhớ?

Trong cơ sở R v3.1.0 + khi chúng ta làm:

DF1 = data.frame(x=1:5, y=6:10, z=11:15) 
DF2 = DF1[, c("x", "y")] 
DF3 = transform(DF2, y = ifelse(y>=8L, 1L, y)) 
DF4 = transform(DF2, y = 2L) 
  1. Từ DF1-DF2, cả hai cột chỉ nông sao chép.
  2. Từ DF2-DF3 cột y mình phải được sao chép/tái phân bổ, nhưng x được nông sao chép lại.
  3. Từ DF2 đến DF4, tương tự như (2).

Tức là, các cột được sao chép miễn là cột vẫn không thay đổi - theo cách này, bản sao bị trì hoãn trừ khi thực sự cần thiết.

Trong data.table, chúng tôi sửa đổi tại chỗ. Có nghĩa là ngay cả trong các tháng DF3DF4 cột y không được sao chép.

DT2[y >= 8L, y := 1L] ## (a) 
DT2[, y := 2L] 

Ở đây, kể từ khi y đã là một cột số nguyên, và chúng tôi đang sửa đổi nó bằng số nguyên, bằng cách tham khảo, không có phân bổ bộ nhớ mới thực hiện ở đây cả.

Điều này cũng đặc biệt hữu ích khi bạn muốn phân bổ phụ theo tham chiếu (được đánh dấu là (a) ở trên). Đây là một tính năng tiện dụng mà chúng tôi thực sự thích trong data.table.

Một lợi thế khác được cung cấp miễn phí (mà tôi đã biết từ các tương tác của chúng tôi), khi chúng tôi chuyển đổi tất cả các cột của dữ liệu thành kiểu numeric, từ loại, character loại:

DT[, (cols) := lapply(.SD, as.numeric), .SDcols = cols] 

Ở đây, vì chúng tôi đang cập nhật theo tham chiếu, mỗi cột ký tự được thay thế bằng cách tham chiếu với đối tác số của nó. Và sau đó thay thế, cột nhân vật trước đó không còn cần thiết nữa và được lấy để lấy rác. Nhưng nếu bạn đã làm điều này sử dụng cơ sở R:

DF[] = lapply(DF, as.numeric) 

Tất cả các cột sẽ phải được chuyển đổi sang số, và đó sẽ phải được tổ chức tại một tạm thời biến, và sau đó cuối cùng sẽ được chỉ định quay lại DF. Điều đó có nghĩa, nếu bạn đã 10 cột với 100 triệu hàng, mỗi loại nhân vật, sau đó bạn DF mất một khoảng:

10 * 100e6 * 4/1024^3 = ~ 3.7GB 

Và kể từ numeric loại là gấp đôi về kích thước, chúng tôi sẽ cần tổng số 7.4GB + 3.7GB không gian để chúng tôi thực hiện chuyển đổi bằng cơ sở R.

Nhưng lưu ý rằng data.table bản sao trong DF1 đến DF2. Đó là:

DT2 = DT1[, c("x", "y"), with=FALSE] 

kết quả trong một bản sao, bởi vì chúng ta không thể phụ gán bằng cách tham khảo trên nông bản sao. Nó sẽ cập nhật tất cả các bản sao. Điều tuyệt vời là nếu chúng ta có thể tích hợp hoàn toàn tính năng sao chép nông, nhưng theo dõi xem cột của một đối tượng cụ thể có nhiều tham chiếu và cập nhật theo tham chiếu bất cứ khi nào có thể. Tính năng đếm tham chiếu được nâng cấp của R có thể rất hữu ích trong vấn đề này. Trong mọi trường hợp, chúng tôi đang làm việc hướng tới nó.


Đối với câu hỏi cuối cùng của bạn: "Khi là sự khác biệt đáng kể nhất"

  1. Hiện vẫn còn những người phải sử dụng phiên bản cũ của R, nơi bản sâu không thể tránh khỏi.

  2. Tùy thuộc vào số lượng cột đang được sao chép bởi vì các thao tác bạn thực hiện trên đó. Kịch bản trường hợp xấu nhất sẽ là bạn đã sao chép tất cả các cột, tất nhiên.

  3. Có các trường hợp như this nơi sao chép cạn sẽ không được hưởng lợi.

  4. Khi bạn muốn cập nhật các cột của dữ liệu.frame cho mỗi nhóm và có quá nhiều nhóm.

  5. Khi bạn muốn cập nhật một cột nói, data.table DT1 dựa trên tham gia với một data.table DT2 - điều này có thể được thực hiện như:

    DT1[DT2, col := i.val] 
    

    nơi i. đề cập đến giá trị từ val cột của DT2 (đối số i) cho các hàng phù hợp. Cú pháp này cho phép thực hiện thao tác này rất hiệu quả, thay vì phải đầu tiên tham gia toàn bộ kết quả, và sau đó cập nhật cột được yêu cầu.

Tất cả trong tất cả, có đối số mạnh khi cập nhật theo tham chiếu sẽ tiết kiệm rất nhiều thời gian và nhanh. Nhưng đôi khi mọi người thích không cập nhật các đối tượng tại chỗ, và sẵn sàng hy sinh tốc độ/bộ nhớ cho nó. Chúng tôi đang cố gắng tìm ra cách tốt nhất để cung cấp chức năng này, ngoài bản cập nhật đã có bằng cách tham chiếu.

Hy vọng điều này sẽ hữu ích. Đây là một câu trả lời khá dài. Tôi sẽ để lại bất kỳ câu hỏi nào bạn có thể để lại cho người khác hoặc để bạn tìm ra (ngoài bất kỳ quan niệm sai lầm rõ ràng nào trong câu trả lời này).

Các vấn đề liên quan