Tengo un pequeño problema para comprender las propiedades de paso por referencia de data.table
. Algunas operaciones parecen "romper" la referencia, y me gustaría entender exactamente lo que está sucediendo.
Al crear un archivo data.table
desde otro data.table
(vía <-
, y luego actualizar la nueva tabla :=
, la tabla original también se modifica. Esto se espera, según:
?data.table::copy
y stackoverflow: pase por referencia del operador en el paquete de tabla de datos
Aquí hay 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 <-
asignación y las :=
líneas anteriores, DT
ahora 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] <- 200
lí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 está tratando estas operaciones, para asegurarme de no introducir errores potenciales en mi código.
Apreciaría mucho si alguien me pudiera explicar esto.
fuente
<-
lugar de=
la asignación básica en R (por ejemplo, Google: google.github.io/styleguide/Rguide.xml#assignment ). Pero esto significa que la manipulación de data.table no funcionará de la misma manera que la manipulación del marco de datos y, por lo tanto, está lejos de ser un reemplazo directo al marco de datos.Respuestas:
Sí, es la subasignación en R usando
<-
(o=
o->
) lo que hace una copia de todo el objeto. Puede rastrear eso usandotracemem(DT)
y.Internal(inspect(DT))
, como a continuación. Lasdata.table
características:=
yset()
asignar por referencia a cualquier objeto que se pasan. Entonces, si ese objeto fue copiado previamente (mediante una subasignación<-
o un explícitocopy(DT)
), entonces es la copia la que se modifica por referencia.Observe cómo
a
se copió incluso el vector (un valor hexadecimal diferente indica una nueva copia del vector), aunquea
no se modificó. Inclusob
se copió todo, en lugar de simplemente cambiar los elementos que deben cambiarse. Eso es importante para evitar grandes datos, y por qué:=
y seset()
les presentódata.table
.Ahora, con nuestra copia
newDT
podemos modificarla por referencia:Observe que los 3 valores hexadecimales (el vector de los puntos de columna y cada una de las 2 columnas) permanecen sin cambios. Por lo tanto, fue realmente modificado por referencia sin copias en absoluto.
O podemos modificar el original
DT
por referencia:Esos valores hexadecimales son los mismos que los valores originales que vimos
DT
anteriormente. Escribaexample(copy)
para obtener más ejemplos utilizandotracemem
y comparación condata.frame
.Por cierto, si
tracemem(DT)
luegoDT[2,b:=600]
verá una copia informada. Esa es una copia de las primeras 10 filas que hace elprint
método. Cuando se envuelve coninvisible()
o cuando se llama dentro de una función o script, elprint
método no se llama.Todo esto se aplica también a las funciones internas; es decir,
:=
yset()
no copie en escritura, incluso dentro de las funciones. Si necesita modificar una copia local, llamex=copy(x)
al inicio de la función. Pero recuerde quedata.table
es para datos grandes (así como ventajas de programación más rápidas para datos pequeños). Deliberadamente no queremos copiar objetos grandes (nunca). Como resultado, no necesitamos permitir la regla general del factor de memoria de trabajo 3 * habitual. Intentamos necesitar solo una memoria de trabajo tan grande como una columna (es decir, un factor de memoria de trabajo de 1 / ncol en lugar de 3).fuente
->
asignación cambia la ubicación de la memoria. Los vectores sin cambios mantienen la ubicación de la memoria de los vectores del marco de datos original. El comportamiento dedata.table
s descrito aquí es el comportamiento actual a partir de 1.12.2.Solo un resumen rápido.
<-
condata.table
es igual que la base; es decir, no se toma ninguna copia hasta que se realiza una subasignación posterior con<-
(como cambiar los nombres de columna o cambiar un elemento comoDT[i,j]<-v
). Luego toma una copia de todo el objeto como base. Eso se conoce como copia en escritura. ¡Sería mejor conocido como copy-on-subassign, creo! NO se copia cuando utiliza el:=
operador especial o lasset*
funciones proporcionadas pordata.table
. Si tiene datos grandes, probablemente quiera usarlos en su lugar.:=
yset*
NO COPIARÁdata.table
, INCLUSO EN FUNCIONES.Dado este ejemplo de datos:
Lo siguiente simplemente "vincula" otro nombre
DT2
al mismo objeto de datos vinculado actualmente al nombreDT
:Esto nunca copia, y nunca copia en base tampoco. Simplemente marca el objeto de datos para que R sepa que dos nombres diferentes (
DT2
yDT
) apuntan al mismo objeto. Y entonces R necesitará copiar el objeto si cualquiera de los dos se asigna a continuación.Eso también es perfecto para
data.table
. El:=
no es para hacer eso. Entonces, el siguiente es un error deliberado, ya:=
que no es solo para vincular nombres de objetos::=
es para subasignar por referencia. Pero no lo usas como lo harías en la base:lo usas así:
Eso cambió
DT
por referencia. Supongamos que agrega una nueva columnanew
por referencia al objeto de datos, no es necesario hacer esto:porque el RHS ya cambió
DT
por referencia. El extraDT <-
es entender mal lo que:=
hace. Puedes escribirlo allí, pero es superfluo.DT
se cambia por referencia, por:=
, INCLUSO EN FUNCIONES:data.table
es para grandes conjuntos de datos, recuerda. Si tiene 20 GBdata.table
de memoria, necesita una forma de hacerlo. Es una decisión de diseño muy deliberada dedata.table
.Se pueden hacer copias, por supuesto. Solo necesita decirle a data.table que está seguro de que desea copiar su conjunto de datos de 20GB, utilizando la
copy()
función:Para evitar copias, no use la asignación o actualización del tipo base:
Si desea estar seguro de que está actualizando por uso de referencia
.Internal(inspect(x))
y observe los valores de dirección de memoria de los componentes (consulte la respuesta de Matthew Dowle).Escribiendo
:=
enj
el estilo le permite subassign por referencia por grupo . Puede agregar una nueva columna por referencia por grupo. Así que por eso:=
se hace así dentro[...]
:fuente