Подтвердить что ты не робот

Назначение по ссылке в загруженные пакетные наборы данных

Я создаю пакет, который использует 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 загружает наборы данных, даже если они не ленивы?
4b9b3361

Ответ 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)



}