Назначение по ссылке в загруженные пакетные наборы данных
Я создаю пакет, который использует 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)
Если я использую eval
и substitute
в функции
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
Должен ли я придерживаться
-
DT <- foo(DT)
или маршрут eval
/substitute
, или
- Есть ли что-то, что я не понимаю о том, как
data
загружает наборы данных, даже если они не ленивы?
Ответы
Ответ 1
Это не имеет ничего общего с наборами данных или блокировкой - вы можете воспроизвести его просто с помощью
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 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
Ответ 2
Другим решением является использование inst/extdata
для сохранения файла rda
(который будет содержать любое количество объектов data.table) и иметь файл DT.r
в подкаталоге data
# 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)
}