Il modo più veloce per sostituire NA in un data.table di grandi dimensioni

Ho un grande data.table , con molti valori mancanti sparsi per le sue ~ 200k righe e 200 colonne. Vorrei ri-codificare questi valori NA a zeri nel modo più efficiente ansible.

Vedo due opzioni:
1: Converti in un data.frame e usa qualcosa di simile
2: Un qualche tipo di comando cool setting.table sub setting

Sarò felice con una soluzione abbastanza efficiente di tipo 1. Conversione in un data.frame e quindi tornare a un data.table non ci vorrà troppo tempo.

Ecco una soluzione che utilizza data.table ‘s := operator, basandosi sulle risposte di Andrie e 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 

Si noti che f_dowle ha aggiornato dt1 per riferimento. Se è necessaria una copia locale, è necessaria una chiamata esplicita alla funzione di copy per creare una copia locale dell’intero set di dati. setkey di setkey , key< - e := non copy-on-write.

Successivamente, vediamo dove f_dowle sta spendendo il suo tempo.

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

Lì, mi concentrerei su na.replace e is.na , dove ci sono alcune copie vettoriali e scansioni vettoriali. Questi possono essere facilmente eliminati scrivendo una piccola funzione C na.replace che aggiorna NA per riferimento nel vettore. Ciò dovrebbe almeno dimezzare i 20 secondi che penso. Esiste una tale funzione in qualsiasi pacchetto R?

La ragione per cui f_andrie fallisce può essere perché copia l'intero dt1 o crea una matrice logica grande quanto l'intero dt1 , alcune volte. Gli altri 2 metodi funzionano su una colonna alla volta (anche se ho solo analizzato brevemente NAToUnknown ).

EDIT (soluzione più elegante richiesta da Ramnath nei commenti):

 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 

Vorrei averlo fatto in quel modo per iniziare!

EDIT2 (oltre 1 anno dopo, ora)

C'è anche set() . Questo può essere più veloce se c'è un sacco di colonne in loop, in quanto evita il (piccolo) sovraccarico di chiamare [,:=,] in un ciclo. set è un loopable := . Vedere ?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) } 

Ecco la più semplice che potrei inventare:

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

È efficiente e non è necessario scrivere funzioni e altri codici di colla.

Ecco una soluzione che utilizza NAToUnknown nel pacchetto gdata . Ho usato la soluzione di Andrie per creare un’enorme tabella di dati e ho anche incluso il confronto temporale con la soluzione di 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 per riferimento, più lento rispetto a gdata o data.matrix, ma utilizza solo il pacchetto data.table e può gestire voci non numeriche.

Per completezza, è necessario utilizzare un altro modo per sostituire NA con 0

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

Per confrontare risultati e tempi ho incorporato tutti gli approcci menzionati finora.

 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 

Quindi il nuovo approccio è leggermente più lento di f_dowle3 ma più veloce di tutti gli altri approcci. Ma per essere onesti, questo è contro la mia Intuizione della syntax data.table e non ho idea del perché funzioni. Qualcuno può illuminarmi?

La mia comprensione è che il segreto per le operazioni veloci in R è quello di utilizzare vettoriale (o matrici, che sono vettori sotto il cofano.)

In questa soluzione uso un data.matrix che è un array ma si comporta un po ‘come un data.frame . Poiché si tratta di un array, è ansible utilizzare una sostituzione vettoriale molto semplice per sostituire i NA :

Una piccola funzione di supporto per rimuovere le NA . L’essenza è una singola riga di codice. Lo faccio solo per misurare il tempo di esecuzione.

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

Una piccola funzione di supporto per creare un data.table di una determinata dimensione.

 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)) } 

Dimostrazione su un piccolo campione:

 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 >