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

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

Veo dos opciones:
1: Convierte a un data.frame, y usa algo como esto
2: Algún tipo de comando de configuración de sub.

Estaré contento con una solución bastante eficiente de tipo 1. La conversión a un data.frame y luego de vuelta a un data.table no tomará demasiado tiempo.

Aquí hay una solución que usa data.table ‘s := operator, 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, se necesita una llamada explícita a la función de copy para hacer una copia local de todo el conjunto de datos. setkey de setkey , key<- y := no copiar-en-escribir.

A continuación, veamos dónde f_dowle está pasando su tiempo.

 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.replace is.na , donde hay algunas copias vectoriales y escaneos vectoriales. Esos pueden ser eliminados con bastante facilidad escribiendo una pequeña función en C que reemplaza NA por referencia en el vector. Eso al menos reduciría a la mitad los 20 segundos, creo. ¿Existe tal función en cualquier paquete R?

La razón f_andrie fracasa el f_andrie puede deberse a que copia el conjunto de dt1 o crea una matriz lógica del dt1 de dt1 varias veces. Los otros 2 métodos funcionan en una columna a la vez (aunque solo miré brevemente a NAToUnknown ).

EDITAR (solución más elegante solicitada 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 una gran cantidad de columnas en bucle, ya que evita la sobrecarga (pequeña) de llamar a [,:=,] en un bucle. set es un loopable := . 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) } 

Aquí está el más simple que pude encontrar:

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

Es eficiente y no es necesario escribir funciones y otros códigos de pegamento.

Aquí hay una solución que usa NAToUnknown en el paquete gdata . Utilicé la solución de Andrie para crear una gran tabla de datos y también incluí las comparaciones de tiempo con la solución de Andrie.

 # CREATE DATA TABLE dt1 = create_dt(2e5, 200, 0.1) # FUNCTIONS TO SET NA TO ZERO f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un) f_Andrie = function(dt) remove_na(dt) # COMPARE SOLUTIONS AND TIMES system.time(a_gdata <- f_gdata(dt1)) user system elapsed 4.224 2.962 7.388 system.time(a_andrie <- f_Andrie(dt1)) user system elapsed 4.635 4.730 20.060 identical(a_gdata, g_andrie) TRUE 
 library(data.table) DT = data.table(a=c(1,"A",NA),b=c(4,NA,"B")) DT ab 1: 1 4 2: A NA 3: NA B DT[,lapply(.SD,function(x){ifelse(is.na(x),0,x)})] ab 1: 1 4 2: A 0 3: 0 B 

Solo como referencia, más lento en comparación con gdata o data.matrix, pero usa solo el paquete data.table y puede tratar con entradas no numéricas.

Para completar, otra forma de reemplazar NA con 0 es usar

 f_rep <- function(dt) { dt[is.na(dt)] <- 0 return(dt) } 

Para comparar resultados y tiempos, he incorporado todos los enfoques mencionados hasta ahora.

 set.seed(1) dt1 <- create_dt(2e5, 200, 0.1) dt2 <- dt1 dt3 <- dt1 system.time(res1 <- f_gdata(dt1)) User System verstrichen 3.62 0.22 3.84 system.time(res2 <- f_andrie(dt1)) User System verstrichen 2.95 0.33 3.28 system.time(f_dowle2(dt2)) User System verstrichen 0.78 0.00 0.78 system.time(f_dowle3(dt3)) User System verstrichen 0.17 0.00 0.17 system.time(res3 <- f_unknown(dt1)) User System verstrichen 6.71 0.84 7.55 system.time(res4 <- f_rep(dt1)) User System verstrichen 0.32 0.00 0.32 identical(res1, res2) & identical(res2, res3) & identical(res3, res4) & identical(res4, dt2) & identical(dt2, dt3) [1] TRUE 

Entonces, el nuevo enfoque es un poco más lento que f_dowle3 pero más rápido que todos los demás enfoques. Pero para ser honesto, esto va en contra de mi intuición de la syntax data.table y no tengo idea de por qué funciona esto. ¿Alguien puede iluminarme?

Según tengo entendido, el secreto para las operaciones rápidas en R es utilizar vectores (o matrices, que son vectores bajo el capó).

En esta solución, uso una data.matrix que es una array pero se comporta como un data.frame . Como es una matriz, puede usar una sustitución vectorial muy simple para reemplazar las NA :

Una pequeña función auxiliar para eliminar las NA . La esencia es una sola línea de código. Solo hago esto para medir el tiempo de ejecución.

 remove_na <- function(x){ dm <- data.matrix(x) dm[is.na(dm)] <- 0 data.table(dm) } 

Una pequeña función auxiliar para crear una data.table de data.table de un tamaño dado.

 create_dt <- function(nrow=5, ncol=5, propNA = 0.5){ v <- runif(nrow * ncol) v[sample(seq_len(nrow*ncol), propNA * nrow*ncol)] <- NA data.table(matrix(v, ncol=ncol)) } 

Demostración en una pequeña muestra:

 library(data.table) set.seed(1) dt <- create_dt(5, 5, 0.5) dt V1 V2 V3 V4 V5 [1,] NA 0.8983897 NA 0.4976992 0.9347052 [2,] 0.3721239 0.9446753 NA 0.7176185 0.2121425 [3,] 0.5728534 NA 0.6870228 0.9919061 NA [4,] NA NA NA NA 0.1255551 [5,] 0.2016819 NA 0.7698414 NA NA remove_na(dt) V1 V2 V3 V4 V5 [1,] 0.0000000 0.8983897 0.0000000 0.4976992 0.9347052 [2,] 0.3721239 0.9446753 0.0000000 0.7176185 0.2121425 [3,] 0.5728534 0.0000000 0.6870228 0.9919061 0.0000000 [4,] 0.0000000 0.0000000 0.0000000 0.0000000 0.1255551 [5,] 0.2016819 0.0000000 0.7698414 0.0000000 0.0000000 
 > DT = data.table(a=LETTERS[c(1,1:3,4:7)],b=sample(c(15,51,NA,12,21),8,T),key="a") > DT ab 1: A 12 2: A NA 3: B 15 4: C NA 5: D 51 6: E NA 7: F 15 8: G 51 > DT[is.na(b),b:=0] > DT ab 1: A 12 2: A 0 3: B 15 4: C 0 5: D 51 6: E 0 7: F 15 8: G 51 >