Zrozumienie dokładnie, kiedy dane.tabela jest odniesieniem do (w porównaniu z kopią) innych danych.tabela
Mam mały problem ze zrozumieniem właściwości pass-by-reference data.table
. Niektóre operacje wydają się "łamać" odniesienie i chciałbym dokładnie zrozumieć, co się dzieje.
Podczas tworzenia data.table
z innego data.table
(poprzez <-
, a następnie aktualizacji nowej tabeli przez :=
, oryginalna tabela jest również zmieniana. Jest to oczekiwane, jak na:
?data.table::copy
i stackoverflow: pass-by-reference-the-operator-in-the-data-table-package
Oto przykład:
library(data.table)
DT <- data.table(a=c(1,2), b=c(11,12))
print(DT)
# a b
# [1,] 1 11
# [2,] 2 12
newDT <- DT # reference, not copy
newDT[1, a := 100] # modify new DT
print(DT) # DT is modified too.
# a b
# [1,] 100 11
# [2,] 2 12
Jeśli jednak wstawiam modyfikację Nie:=
między przypisaniem <-
a :=
powyżej, {[11] } nie jest już modyfikowana:
DT = data.table(a=c(1,2), b=c(11,12))
newDT <- DT
newDT$b[2] <- 200 # new operation
newDT[1, a := 100]
print(DT)
# a b
# [1,] 1 11
# [2,] 2 12
Wygląda więc na to, że newDT$b[2] <- 200
Linia jakoś "łamie" odniesienie. Domyślam się, że to w jakiś sposób wywołuje kopię, ale chciałbym w pełni zrozumieć, jak R traktuje te operacje, aby upewnić się, że nie wprowadzam potencjalnych błędów w moim kodzie.
Byłbym bardzo wdzięczny, gdyby ktoś mógł to wyjaśnić ja.
2 answers
Tak, to subassignment w R za pomocą <-
(lub =
lub ->
) tworzy kopię całego obiektu. Możesz to prześledzić używając tracemem(DT)
i .Internal(inspect(DT))
, jak poniżej. Funkcje data.table
:=
i set()
przypisują przez odniesienie do dowolnego obiektu, który są przekazywane. Jeśli więc obiekt został wcześniej skopiowany (przez subassigning <-
lub jawny copy(DT)
), to jest to kopia, która zostanie zmodyfikowana przez odniesienie.
DT <- data.table(a = c(1, 2), b = c(11, 12))
newDT <- DT
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
.Internal(inspect(newDT)) # precisely the same object at this point
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
tracemem(newDT)
# [1] "<0x0000000003b7e2a0"
newDT$b[2] <- 200
# tracemem[0000000003B7E2A0 -> 00000000040ED948]:
# tracemem[00000000040ED948 -> 00000000040ED830]: .Call copy $<-.data.table $<-
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),TR,ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,12
# ATTRIB: # ..snip..
.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,200
# ATTRIB: # ..snip..
Zauważ jak nawet wektor a
został skopiowany (inna wartość hex oznacza nową kopię wektora), mimo że a
nie została zmieniona. Nawet całość b
została skopiowana, a nie tylko zmiana elementów, które trzeba zmienić. Należy tego unikać w przypadku dużych danych i dlaczego :=
i set()
zostały wprowadzone do data.table
.
Teraz, z naszego skopiowanego newDT
możemy go zmodyfikować przez odniesienie:
newDT
# a b
# [1,] 1 11
# [2,] 2 200
newDT[2, b := 400]
# a b # See FAQ 2.21 for why this prints newDT
# [1,] 1 11
# [2,] 2 400
.Internal(inspect(newDT))
# @0000000003D97A58 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040ED7F8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040ED8D8 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,400
# ATTRIB: # ..snip ..
Zauważ, że wszystkie 3 wartości szesnastkowe (wektor punktów kolumn i każda z 2 kolumn) pozostają bez zmian. Więc został naprawdę zmodyfikowany przez odniesienie bez kopii.
Możemy też zmodyfikować oryginałDT
poprzez odniesienie:
DT[2, b := 600]
# a b
# [1,] 1 11
# [2,] 2 600
.Internal(inspect(DT))
# @0000000003B7E2A0 19 VECSXP g0c7 [OBJ,NAM(2),ATT] (len=2, tl=100)
# @00000000040C2288 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 1,2
# @00000000040C2250 14 REALSXP g0c2 [NAM(2)] (len=2, tl=0) 11,600
# ATTRIB: # ..snip..
Te wartości szesnastkowe są takie same jak oryginalne wartości, które widzieliśmy dla DT
powyżej. Wpisz example(copy)
aby uzyskać więcej przykładów za pomocą tracemem
i porównania do data.frame
.
Btw, Jeśli tracemem(DT)
to DT[2,b:=600]
zobaczysz jeden egzemplarz zgłoszony. Jest to Kopia pierwszych 10 wierszy, które wykonuje metoda print
. Po zawinięciu invisible()
lub wywołaniu w funkcji lub skrypcie, metoda print
nie jest wywoływana.
To wszystko ma zastosowanie również wewnątrz funkcji; tzn. :=
i set()
nie kopiują przy zapisie, nawet wewnątrz funkcji. Jeśli chcesz zmodyfikować lokalną kopię, wywołaj x=copy(x)
na początku funkcji. Ale pamiętaj {[8] } jest dla dużych danych (jak również szybsze programowanie korzyści dla małych danych). Celowo nie chcemy kopiować dużych obiektów (nigdy). W rezultacie nie musimy dopuszczać zwykłej Zasady współczynnika pamięci roboczej 3*. Staramy się, aby tylko pamięć robocza była tak duża, jak jedna kolumna (tzn. współczynnik pamięci roboczej 1 / ncol zamiast 3).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-20 21:20:18
Tylko krótkie podsumowanie.
<-
z data.table
jest tak samo jak z base; tzn. Żadna Kopia nie jest pobierana, dopóki nie zostanie wykonana podassign z <-
(np. zmiana nazw kolumn lub zmiana elementu, np. DT[i,j]<-v
). Następnie pobiera kopię całego obiektu, tak jak Baza. To jest znane jako kopiowanie przy zapisie. Byłby lepiej znany jako copy-on-subassign, myślę! Nie kopiuje, gdy używasz specjalnego operatora :=
lub funkcji set*
dostarczanych przez data.table
. Jeśli masz duże dane pewnie chce ich użyć. :=
i set*
nie będą kopiować data.table
, nawet wewnątrz funkcji.
Podane dane przykładowe:
DT <- data.table(a=c(1,2), b=c(11,12))
Po prostu "wiąże" inną nazwę DT2
do tego samego obiektu danych związanego obecnie z nazwą DT
:
DT2 <- DT
To nigdy nie kopiuje i nigdy nie kopiuje w bazie. Po prostu oznacza obiekt danych tak, że R wie, że dwie różne nazwy (DT2
i DT
) wskazują na ten sam obiekt. I tak R będzie musiał skopiować obiekt, jeśli są subassigned To after.
To też jest idealne dla data.table
. :=
nie jest do tego. Tak więc poniższy błąd jest zamierzony, ponieważ :=
nie służy tylko do wiązania nazw obiektów:
DT2 := DT # not what := is for, not defined, gives a nice error
:=
jest dla subassygnacji przez odniesienie. Ale nie używasz go tak jak w bazie:
DT[3,"foo"] := newvalue # not like this
Używasz go w ten sposób:
DT[3,foo:=newvalue] # like this
To zmieniło się DT
przez odniesienie. Powiedzmy, że dodajesz nową kolumnę new
przez odniesienie do danych obiekt, nie ma potrzeby tego robić:
DT <- DT[,new:=1L]
Ponieważ RHS już zmienił DT
przez odniesienie. Dodatkowym DT <-
jest zrozumienie tego, co robi :=
. Możesz to tam napisać, ale to zbędne.
DT
jest zmieniana przez odniesienie, przez :=
, nawet w obrębie funkcji:
f <- function(X){
X[,new2:=2L]
return("something else")
}
f(DT) # will change DT
DT2 <- DT
f(DT) # will change both DT and DT2 (they're the same data object)
data.table
jest dla dużych zbiorów danych, pamiętaj. Jeśli masz 20GB data.table
w pamięci, potrzebujesz sposobu, aby to zrobić. To bardzo przemyślana decyzja projektowa data.table
.
Kopie mogą być zrobione, oczywiście. Musisz tylko powiedzieć data.tabela, w której jesteś pewien, że chcesz skopiować swój zestaw danych 20GB, za pomocą funkcji copy()
:
DT3 <- copy(DT) # rather than DT3 <- DT
DT3[,new3:=3L] # now, this just changes DT3 because it's a copy, not DT too.
Aby uniknąć kopiowania, nie używaj przypisywania typu bazowego ani aktualizacji:
DT$new4 <- 1L # will make a copy so use :=
attr(DT,"sorted") <- "a" # will make a copy use setattr()
Jeśli chcesz mieć pewność, że aktualizujesz przez odniesienie użyj {[39] } i spójrz na wartości adresów pamięci składników (patrz odpowiedź Matthew Dowle ' a).
Zapis :=
w j
w ten sposób pozwala na subassygnację przez odniesienieprzez grupę . Możesz dodać nową kolumnę według referencji według grupy. Więc dlatego :=
robi się tak wewnątrz [...]
:
DT[, newcol:=mean(x), by=group]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-03 11:25:25