2013-03-04 13 views
18

我正在創建一個包,該包使用data.table作爲數據集,並具有一些使用:=進行引用分配的函數。通過引用分配到已加載的包數據集

我建立了一個簡單的包裝,以證明我problem

library(devtools) 
install_github('foo','mnel') 

它包含兩個功能

foo <- function(x){ 
    x[, a := 1] 
} 
fooCall <- function(x){ 
    eval(substitute(x[, a :=1]),parent.frame(1)) 
} 

和數據集(不懶加載)DT,使用

DT <- data.table(b = 1:5) 
save(DT, file = 'data/DT.rda') 
創建

當我安裝這個軟件包時,我的理解是foo(DT)應該在DT內通過引用進行分配。

library(foo) 
data(DT) 
foo(DT) 
    b a 
1: 1 1 
2: 2 1 
3: 3 1 
4: 4 1 
5: 5 1 

# However this has not assigned by reference within `DT` 

DT 
    b 
1: 1 
2: 2 
3: 3 
4: 4 
5: 5 

如果我使用更correct

tracmem(DT) 
DT <- foo(DT) 
# This works without copying 
DT 
b a 
1: 1 1 
2: 2 1 
3: 3 1 
4: 4 1 
5: 5 1 
untracemem(DT) 

如果我使用evalsubstitute功能

fooCall(DT) 
    b a 
1: 1 1 
2: 2 1 
3: 3 1 
4: 4 1 
5: 5 1 
# it does assign by reference 
DT 
    b a 
1: 1 1 
2: 2 1 
3: 3 1 
4: 4 1 
5: 5 1 

我應該堅持內

  1. DT <- foo(DT)eval/substitute路由或
  2. 有沒有什麼我不理解如何加載數據集,即使不懶惰?
+0

從未嘗試過通過引用數據包更新中!但是包中的數據不是隻有在被封裝_sealed_時纔會被讀取?在這裏輸入'DT'並不意味着它已經被引用分配了嗎? DT可能已被複制到'.GlobalEnv',並且可能是它已被更新的地方。 – 2013-03-04 07:58:25

+0

Btw' tracemem'報告R本身的_duplications_。不太可能捕獲data.table所做的副本,例如,第一次過度分配時,因爲從技術上講,這不是完美的重複,而是過度分配(儘管是淺層複製而不是深層複製)。 – 2013-03-04 08:01:19

+0

也許在包中的數據對象上嘗試'alloc.col',看看會發生什麼。 – 2013-03-04 08:04:17

回答

10

這有什麼好做的數據集或鎖定 - 可以重現它只是使用

DT<-unserialize(serialize(data.table(b = 1:5),NULL)) 
foo(DT) 
DT 

我懷疑它的事實,data.table必須重新創建內部的EXTPTR做對象在DT上的第一次訪問,但它在副本上這樣做,所以它不可能在全局環境中與原始共享修改。


[來自馬修]正是。

DT<-unserialize(serialize(data.table(b = 1:3),NULL)) 
DT 
    b 
1: 1 
2: 2 
3: 3 
DT[,newcol:=42] 
DT     # Ok. DT rebound to new shallow copy (when direct) 
    b newcol 
1: 1  42 
2: 2  42 
3: 3  42 

DT<-unserialize(serialize(data.table(b = 1:3),NULL)) 
foo(DT) 
    b a 
1: 1 1 
2: 2 1 
3: 3 1 
DT     # but not ok when via function foo() 
    b 
1: 1 
2: 2 
3: 3 


DT<-unserialize(serialize(data.table(b = 1:3),NULL)) 
alloc.col(DT)  # alloc.col needed first 
    b 
1: 1 
2: 2 
3: 3 
foo(DT) 
    b a 
1: 1 1 
2: 2 1 
3: 3 1 
DT     # now it's ok 
    b a 
1: 1 1 
2: 2 1 
3: 3 1 

或者不通過DT進入功能,只要提到它直接。使用data.table就像一個數據庫:.GlobalEnv中的幾個固定名稱表。

DT <- unserialize(serialize(data.table(b = 1:5),NULL)) 
foo <- function() { 
    DT[, newcol := 7] 
} 
foo() 
    b newcol 
1: 1  7 
2: 2  7 
3: 3  7 
4: 4  7 
5: 5  7 
DT    # Unserialized data.table now over-allocated and updated ok. 
    b newcol 
1: 1  7 
2: 2  7 
3: 3  7 
4: 4  7 
5: 5  7 
+1

@Matthew:但請注意''alloc.col()'同樣不會*在函數內部工作(出於上述相同的原因) - 你確實需要一些不會試圖僞造引用的東西 - 例如DT < - DT [TRUE]'有效。這在'data.table'文檔中值得一提,因爲反序列化data.table對象會產生一個難以追蹤的問題(並且它始終在工作空間,軟件包等中)。 – 2013-03-04 20:10:13

+0

只有一個問題,我相信,如果需要從一個函數內引用一個反序列化的data.table來添加一個列,並且該表名不是事先知道的(例如需要通過函數傳入論據)。我想不出一個例子,在反序列化不可能的情況下直接調用'alloc.col(DT)',但在實踐中也需要。我傾向於像數據庫一樣使用data.table;即.GlobalEnv中的幾個大的固定名稱表。請參閱新的編輯。 – 2013-03-04 21:23:51

+0

@MatthewDowle(和Simon) - 感謝指針,我可以看到你的第二個例子有點類似於我構建適當調用的想法,並在正確的父環境中對它進行評估。 (在我的問題中'fooCall') – mnel 2013-03-05 00:38:16

3

另一種解決方案是使用inst/extdata保存rda文件(這將包含任意數量的數據。表對象),並有一個文件DT.rdata子目錄

# get the environment from the call to `data()` 
env <- get('envir', parent.frame(1)) 
# load the data 
load(system.file('extdata','DT.rda', package= 'foo'), envir = env) 
# overallocate (evaluating in correct environment) 
if(require(data.table)){ 
# the contents of `DT.rda` are known, so write out in full 
    evalq(alloc.col(DT), envir = env) 

} 
# clean up so `env` object not present in env environment after calling `data(DT)` 
rm(list = c('env'), envir = env) 



} 
+0

+1有趣。我想知道是否應該增強'alloc.col'來接受一個字符向量?然後它可以包裝'load()'調用。我不認爲你需要'data.table ::'前綴,因爲'alloc.col'被導出並且供用戶使用。 – 2013-03-05 13:39:28

+0

@MatthewDowle,好點re'data.table ::'固定,並修改爲預先知道'load'的結果的特定情況。 'alloc.col'也可能需要環境參數。 – mnel 2013-03-05 23:21:54

+0

好主意。 [FR#2595](https://r-forge.r-project.org/tracker/index.php?func=detail&aid=2595&group_id=240&atid=978)來增強現在提交的'alloc.col'。 – 2013-03-06 10:36:49