La forma más rápida de reemplazar NA en una tabla de datos grande

Resuelto Zach asked hace 54 años • 11 respuestas

Tengo una tabla de datos grande , con muchos valores faltantes repartidos por sus ~200k filas y 200 columnas. Me gustaría volver a codificar esos valores de NA a ceros de la manera más eficiente posible.

Veo dos opciones:
1: Convertir a un data.frame y usar algo como esto
2: Algún tipo de comando de subconfiguración interesante de data.table

Estaré contento con una solución bastante eficiente del tipo 1. Convertir a un marco de datos y luego volver a una tabla de datos no llevará mucho tiempo.

Zach avatar Jan 01 '70 08:01 Zach
Aceptado

Aquí hay una solución que utiliza el operador data.table:= , basándose en las respuestas de Andrie y Ramnath.

require(data.table)  # v1.6.6
require(gdata)       # v2.8.2

set.seed(1)
dt1 = create_dt(2e5, 200, 0.1)
dim(dt1)
[1] 200000    200    # more columns than Ramnath's answer which had 5 not 200

f_andrie = function(dt) remove_na(dt)

f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un)

f_dowle = function(dt) {     # see EDIT later for more elegant solution
  na.replace = function(v,value=0) { v[is.na(v)] = value; v }
  for (i in names(dt))
    eval(parse(text=paste("dt[,",i,":=na.replace(",i,")]")))
}

system.time(a_gdata = f_gdata(dt1)) 
   user  system elapsed 
 18.805  12.301 134.985 

system.time(a_andrie = f_andrie(dt1))
Error: cannot allocate vector of size 305.2 Mb
Timing stopped at: 14.541 7.764 68.285 

system.time(f_dowle(dt1))
  user  system elapsed 
 7.452   4.144  19.590     # EDIT has faster than this

identical(a_gdata, dt1)   
[1] TRUE

Tenga en cuenta que f_dowle actualizó dt1 por referencia. Si se requiere una copia local, entonces copyse necesita una llamada explícita a la función para hacer una copia local de todo el conjunto de datos. data.table's setkeyy key<-no :=copiar en escritura.

A continuación, veamos dónde pasa su tiempo f_dowle.

Rprof()
f_dowle(dt1)
Rprof(NULL)
summaryRprof()
$by.self
                  self.time self.pct total.time total.pct
"na.replace"           5.10    49.71       6.62     64.52
"[.data.table"         2.48    24.17       9.86     96.10
"is.na"                1.52    14.81       1.52     14.81
"gc"                   0.22     2.14       0.22      2.14
"unique"               0.14     1.36       0.16      1.56
... snip ...

Allí, me centraría en na.replacey is.na, donde hay algunas copias y escaneos de vectores. Estos pueden eliminarse con bastante facilidad escribiendo una pequeña función na.replace C que se actualice NApor referencia en el vector. Creo que eso reduciría al menos a la mitad los 20 segundos. ¿Existe tal función en algún paquete R?

La razón por f_andriela que falla puede ser porque copia todo dt1o crea una matriz lógica tan grande como todo dt1varias veces. Los otros 2 métodos funcionan en una columna a la vez (aunque solo los miré brevemente NAToUnknown).

EDITAR (solución más elegante según lo solicitado por Ramnath en los comentarios):

f_dowle2 = function(DT) {
  for (i in names(DT))
    DT[is.na(get(i)), (i):=0]
}

system.time(f_dowle2(dt1))
  user  system elapsed 
 6.468   0.760   7.250   # faster, too

identical(a_gdata, dt1)   
[1] TRUE

¡Ojalá lo hubiera hecho así para empezar!

EDIT2 (más de 1 año después, ahora)

También hay set(). Esto puede ser más rápido si hay muchas columnas en bucle, ya que evita la (pequeña) sobrecarga de llamar [,:=,]en un bucle. setes un bucle :=. Ver ?set.

f_dowle3 = function(DT) {
  # either of the following for loops

  # by name :
  for (j in names(DT))
    set(DT,which(is.na(DT[[j]])),j,0)

  # or by number (slightly faster than by name) :
  for (j in seq_len(ncol(DT)))
    set(DT,which(is.na(DT[[j]])),j,0)
}
Matt Dowle avatar Aug 30 '2011 20:08 Matt Dowle

Aquí está el más simple que se me ocurrió:

dt[is.na(dt)] <- 0

Es eficiente y no es necesario escribir funciones ni otros códigos adhesivos.

Bar avatar Oct 12 '2016 13:10 Bar