2014-09-20 83 views
8

[.data.table中的函數set或表達式:=意味着data.table通過引用進行更新。我不太瞭解的是,這種行爲與將操作結果重新分配給原始data.frame的方式不同。「通過引用更新」vs淺拷貝

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

由於RHS中表達<-是初始數據幀的淺表副本在最新版本的R,這些功能似乎相當高效。這種基本的R方法與data.table的等價方法有什麼不同?差異只與速度或內存使用有關嗎?什麼時候差異最大?

一些(速度)基準。當數據集只有兩個變量時,似乎速度差異可以忽略不計,而且變量越大,速度差異越小。

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 

現在我明白了setkeyX[Y,:=]不能淺拷貝的期限來表示 - 所以我真的只是要求有關創建/刪除新的列或行我。

回答

12

data.table:=全部set*函數通過引用更新對象。這是在2012年IIRC左右推出的。而在這個時候,基地R 沒有淺拷貝,但拷貝。 淺層副本自3.1.0開始引入。


這是一個羅嗦/冗長的答案,但我覺得這回答你的前兩個問題:

這是怎麼回事基礎R方法從data.table相當於不同?差異只與速度或內存使用有關嗎?

在基礎R V3.1.0 +當我們這樣做:

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. DF1DF2,兩列只有複製。
  2. DF2DF3y單獨不得不被複制/重新分配,但x得到再次複製。
  3. DF2DF4,與(2)相同。

也就是說,只要列保持不變,列就被淺拷貝 - 在某種程度上,複製被延遲,除非絕對必要。

data.table,我們修改就地。即使在DF3DF4y也不會被複制。

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

這裏,因爲y已經是一個整數列,而我們是通過整數修改它,通過引用,這裏有所有無新的內存分配。

當您想分號作爲參考(標記爲上面的(a))時,這也特別有用。這是我們在data.table中非常喜歡的一個便利功能。

附帶免費的(我來自我們的互動就知道了)是另一個優勢當我們已經,比如說,轉換data.table的所有列到numeric類型,比如從,character類型:

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

在這裏,因爲我們通過引用更新,每個字符列得到通過參考取代與它的數字對應。在更換之後,不再需要較早的字符列,並且可以用於垃圾收集。但是,如果你要做到這一點使用基礎R:

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

所有列將被轉換爲數字,而必須在臨時變量舉行,最後將被分配回到DF。這意味着,如果你已經10列有100萬行,每種性格類型的,那麼你的DF需要的空間:

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

而且由於numeric類型是兩倍大小爲多,我們需要共爲美國太空7.4GB + 3.7GB使用基本R.

進行轉換但要注意在DF1data.table副本DF2。那就是:

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

導致複製,因爲我們不能通過在拷貝參考子分配。它會更新所有的克隆。

如果我們可以無縫地集成淺拷貝功能,但會跟蹤特定對象的列是否有多個引用,並在任何可能的情況下通過引用進行更新,那將會是什麼。 R的升級引用計數功能在這方面可能非常有用。無論如何,我們正在努力。


對於你最後一個問題: 「什麼時候是最可觀的區別」

  1. 還有誰有權使用舊版本的R,在無法避免深副本的人。

  2. 這取決於有多少列正在被複制,因爲您對它執行的操作。最糟糕的情況是你複製了所有的列,當然。

  3. 有像this這樣的情況,淺拷貝不會受益。

  4. 如果您想更新每個組的data.frame的列,並且組數太多。

  5. 當你想更新基於與聯接的發言權,data.table DT1列另一個data.table DT2 - 這是可以做到的:

    DT1[DT2, col := i.val] 
    

    其中i.指值爲(i參數)的val列,用於匹配行。此語法允許非常有效地執行此操作,而不必首先加入整個結果,然後更新所需的列。

總而言之,有很強的論據,通過引用更新會節省很多時間,並且速度很快。但是人們有時不喜歡更新對象,並且願意爲它犧牲速度/內存。除了現有的參考更新之外,我們還試圖找出如何最好地提供此功能。

希望這會有所幫助。這已經是一個相當長的答案。我會留下你可能留給他人的任何問題或者讓你弄清楚(除了這個答案中的任何明顯的誤解之外)。