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.

Author: smci, 2012-04-19

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).

 121
Author: Matt Dowle,
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]
 91
Author: statquant,
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