Comprender exactamente cuándo una tabla de datos es una referencia a (frente a una copia de) otra tabla de datos.

Resuelto Peter Fine asked hace 54 años • 2 respuestas

Tengo algunos problemas para comprender las propiedades de paso por referencia de data.table. Algunas operaciones parecen "romper" la referencia y me gustaría entender exactamente qué está sucediendo.

Al crear una data.tablea partir de otra data.table(a través de <-y luego actualizar la nueva tabla mediante :=, la tabla original también se modifica. Esto es lo esperado, según:

?data.table::copy y stackoverflow: pasar-por-referencia-el-operador-en-el-paquete-de-tabla-de-datos

He aquí un ejemplo:

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

Sin embargo, si inserto una :=modificación no basada entre la <-tarea y las :=líneas anteriores, DTahora ya no se modifica:

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

Entonces parece que la newDT$b[2] <- 200línea de alguna manera "rompe" la referencia. Supongo que esto invoca una copia de alguna manera, pero me gustaría entender completamente cómo R trata estas operaciones, para asegurarme de no introducir errores potenciales en mi código.

Agradecería mucho si alguien pudiera explicarme esto.

Peter Fine avatar Jan 01 '70 08:01 Peter Fine
Aceptado

Sí, es una subasignación en R usando <-(o =o ->) que hace una copia de todo el objeto. Puede rastrear eso usando tracemem(DT)y .Internal(inspect(DT)), como se muestra a continuación. Las data.tablecaracterísticas :=y set()se asignan por referencia a cualquier objeto que se les pase. Entonces, si ese objeto se copió previamente (mediante una subasignación <-o un explícito copy(DT)), entonces es la copia la que se modifica por referencia.

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

Observe cómo incluso ase copió el vector (un valor hexadecimal diferente indica una nueva copia del vector), aunque ano se cambió. Incluso se copió todo b, en lugar de simplemente cambiar los elementos que deben cambiarse. Es importante evitarlo en el caso de datos de gran tamaño, y por qué :=nos set()presentaron data.table.

Ahora, con nuestra copia newDTpodemos modificarla por referencia:

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

Observe que los 3 valores hexadecimales (el vector de puntos de columna y cada una de las 2 columnas) permanecen sin cambios. Así que fue realmente modificado por referencia sin ninguna copia.

O podemos modificar el original DTpor referencia:

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

Esos valores hexadecimales son los mismos que los valores originales que vimos DTarriba. Escriba example(copy)para obtener más ejemplos de uso tracememy comparación con data.frame.

Por cierto, si lo hace, tracemem(DT)verá DT[2,b:=600]una copia informada. Esa es una copia de las primeras 10 filas que printhace el método. Cuando se incluye invisible()o se llama dentro de una función o secuencia de comandos, printno se llama al método.

Todo esto también se aplica dentro de las funciones; es decir, :=y set()no copiar al escribir, ni siquiera dentro de funciones. Si necesita modificar una copia local, llame x=copy(x)al inicio de la función. Pero recuerde data.tableque es para datos grandes (así como ventajas de programación más rápida para datos pequeños). Deliberadamente no queremos copiar objetos grandes (nunca). Como resultado, no necesitamos permitir la regla general habitual del factor de memoria de trabajo 3*. Intentamos necesitar sólo una memoria de trabajo del tamaño de una columna (es decir, un factor de memoria de trabajo de 1/ncol en lugar de 3).

Matt Dowle avatar Apr 19 '2012 10:04 Matt Dowle

Sólo un breve resumen.

<-with data.tablees como la base; es decir, no se realiza ninguna copia hasta que se realiza una subasignación posterior <-(como cambiar los nombres de las columnas o cambiar un elemento como DT[i,j]<-v). Luego toma una copia de todo el objeto como si fuera la base. Esto se conoce como copia en escritura. ¡Creo que sería mejor conocido como copiar en subasignación! NO copia cuando usa el :=operador especial o las set*funciones proporcionadas por data.table. Si tiene una gran cantidad de datos, probablemente desee utilizarlos en su lugar. :=y set*NO COPIARÁ el data.table, INCLUSO DENTRO DE LAS FUNCIONES.

Dados estos datos de ejemplo:

DT <- data.table(a=c(1,2), b=c(11,12))

Lo siguiente simplemente "vincula" otro nombre DT2al mismo objeto de datos vinculado actualmente al nombre DT:

DT2 <- DT

Esto nunca copia, y tampoco copia nunca en la base. Simplemente marca el objeto de datos para que R sepa que dos nombres diferentes ( DT2y DT) apuntan al mismo objeto. Y entonces R necesitará copiar el objeto si alguno de ellos se subasigna posteriormente.

data.tableEso también es perfecto para él . No :=es para hacer eso. Entonces, lo siguiente es un error deliberado y :=no sirve solo para vincular nombres de objetos:

DT2 := DT    # not what := is for, not defined, gives a nice error

:=es para subasignar por referencia. Pero no lo usas como lo harías en base:

DT[3,"foo"] := newvalue    # not like this

lo usas así:

DT[3,foo:=newvalue]    # like this

Eso cambió DTpor referencia. Supongamos que agrega una nueva columna newpor referencia al objeto de datos, no es necesario hacer esto:

DT <- DT[,new:=1L]

porque el RHS ya cambió DTpor referencia. El extra DT <-es entender mal lo que :=hace. Puedes escribirlo allí, pero es superfluo.

DTse cambia por referencia, por :=, INCLUSO DENTRO DE FUNCIONES:

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.tablees para grandes conjuntos de datos, recuerde. Si tiene 20 GB data.tablede memoria, entonces necesita una forma de hacerlo. Es una decisión de diseño muy deliberada data.table.

Por supuesto, se pueden hacer copias. Solo necesita decirle a data.table que está seguro de que desea copiar su conjunto de datos de 20 GB, usando la copy()función:

DT3 <- copy(DT)   # rather than DT3 <- DT
DT3[,new3:=3L]     # now, this just changes DT3 because it's a copy, not DT too.

Para evitar copias, no utilice la asignación de tipo base ni actualice:

DT$new4 <- 1L                 # will make a copy so use :=
attr(DT,"sorted") <- "a"      # will make a copy use setattr() 

Si desea estar seguro de que está actualizando por referencia, utilice .Internal(inspect(x))y observe los valores de dirección de memoria de los componentes (consulte la respuesta de Matthew Dowle).

Escribir :=así jle permite subasignar por referencia por grupo . Puede agregar una nueva columna por referencia por grupo. Por eso :=se hace así por dentro [...]:

DT[, newcol:=mean(x), by=group]
statquant avatar Jan 12 '2013 11:01 statquant