Oui, c'est une sous-attribution en R utilisant <-
(ou =
ou ->
) qui fait une copie de l'objet entier. Vous pouvez le suivre en utilisant tracemem(DT)
et .Internal(inspect(DT))
, comme ci-dessous. Les fonctionnalités de data.table
:=
et set()
assignent par référence à tout objet auquel ils sont passés. Donc si cet objet a été précédemment copié (par une sous-attribution <-
ou un explicite copy(DT)
) alors c'est la copie qui est modifiée par référence.
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)) # précisément le même objet à ce stade
# @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..
Remarquez comment même le vecteur a
a été copié (différente valeur hexadécimale indique une nouvelle copie du vecteur), même si a
n'a pas été changé. Même la totalité de b
a été copiée, plutôt que de changer simplement les éléments qui nécessitent d'être changés. C'est important de l'éviter pour les grands ensembles de données, c'est pourquoi :=
et set()
ont été introduits dans data.table
.
Maintenant, avec notre newDT
copié, nous pouvons le modifier par référence :
newDT
# a b
# [1,] 1 11
# [2,] 2 200
newDT[2, b := 400]
# a b # Voir la FAQ 2.21 pour savoir pourquoi cela affiche 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 ..
Remarquez que les 3 valeurs hexadécimales (le vecteur des points de colonne, et chacune des 2 colonnes) restent inchangées. Donc il a été réellement modifié par référence sans aucune copie.
Ou, nous pouvons modifier l'original DT
par référence :
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..
Ces valeurs hexadécimales sont les mêmes que les valeurs originales que nous avons vues pour DT
ci-dessus. Tapez example(copy)
pour plus d'exemples utilisant tracemem
et comparaison à data.frame
.
En passant, si vous tracemem(DT)
ensuite DT[2,b:=600]
vous verrez une copie signalée. C'est une copie des 10 premières lignes que la méthode print
fait. Lorsqu'elle est enveloppée avec invisible()
ou lorsqu'elle est appelée dans une fonction ou un script, la méthode print
n'est pas appelée.
Tout ceci s'applique également à l'intérieur des fonctions ; c'est-à-dire, :=
et set()
ne copient pas à l'écriture, même à l'intérieur des fonctions. Si vous devez modifier une copie locale, alors appelez x=copy(x)
au début de la fonction. Mais, rappelez-vous que data.table
est pour les grands ensembles de données (ainsi que les avantages de programmation plus rapides pour les petits ensembles de données). Nous ne voulons délibérément pas copier de grands objets (jamais). En conséquence nous n'avons pas besoin de permettre la règle d'or habituelle du facteur de mémoire de travail 3*. Nous essayons de n'avoir besoin que de la mémoire de travail aussi grande qu'une colonne (c'est-à-dire un facteur de mémoire de travail de 1/ncol au lieu de 3).
2 votes
Je viens de découvrir cette "fonctionnalité", et c'est horrifiant. Il est largement recommandé sur Internet d'utiliser
<-
au lieu de=
pour l'assignation de base en R (par exemple, par Google: google.github.io/styleguide/Rguide.xml#assignment). Mais cela signifie que la manipulation des data.table ne fonctionnera pas de la même manière que la manipulation des data frame et n'est donc pas un remplacement standard des data frame.