2014-10-03 20 views
11

Se si vuole inserire i valori di una variabile basata sull'osservazione NA non precedente/posteriore all'interno di un gruppo mancante, il comando data.table èCompilare i valori mancanti, per gruppo, data.table

setkey(DT,id,date) 
DT[, value_filled_in := DT[!is.na(value), list(id, date, value)][DT[, list(id, date)], value, roll = TRUE]] 

che è abbastanza complesso E 'un peccato perché roll è un'opzione molto veloce e potente (specialmente in confronto con l'applicazione di una funzione come zoo::na.locf all'interno di ciascun gruppo)

posso scrivere una funzione di convenienza per riempire i valori mancanti

fill_na <- function(x , by = NULL, roll =TRUE , rollends= if (roll=="nearest") c(TRUE,TRUE) 
      else if (roll>=0) c(FALSE,TRUE) 
      else c(TRUE,FALSE)){ 
    id <- seq_along(x) 
    if (is.null(by)){ 
     DT <- data.table("x" = x, "id" = id, key = "id") 
     return(DT[!is.na(x)][DT[, list(id)], x, roll = roll, rollends = rollends, allow.cartesian = TRUE]) 

    } else{ 
     DT <- data.table("x" = x, "by" = by, "id" = id, key = c("by", "id")) 
     return(DT[!is.na(x)][DT[, list(by, id)], x, roll = roll, rollends = rollends, allow.cartesian = TRUE]) 
    } 
    } 

E poi scrivere

setkey(DT,id, date) 
DT[, value_filled_in := fill_na(value, by = id)] 

Questo non è davvero soddisfacente in quanto si vorrebbe scrivere

setkey(DT,id, date) 
DT[, value_filled_in := fill_na(value), by = id] 

Tuttavia, questa operazione richiede molto tempo. E, per l'utente finale, è improbabile sapere che fill_na deve essere chiamato con l'opzione by e non deve essere utilizzato con data.tableby. C'è una soluzione elegante intorno a questo?

Alcuni test di velocità

N <- 2e6 
set.seed(1) 
DT <- data.table(
     date = sample(10, N, TRUE), 
      id = sample(1e5, N, TRUE), 
     value = sample(c(NA,1:5), N, TRUE), 
     value2 = sample(c(NA,1:5), N, TRUE)     
    ) 
setkey(DT,id,date) 
DT<- unique(DT) 

system.time(DT[, filled0 := DT[!is.na(value), list(id, date, value)][DT[, list(id, date)], value, roll = TRUE]]) 
#> user system elapsed 
#> 0.086 0.006 0.105 
system.time(DT[, filled1 := zoo::na.locf.default(value, na.rm = FALSE), by = id]) 
#> user system elapsed 
#> 5.235 0.016 5.274 
# (lower speed and no built in option like roll=integer or roll=nearest, rollend, etc) 
system.time(DT[, filled2 := fill_na(value, by = id)]) 
#> user system elapsed 
#> 0.194 0.019 0.221 
system.time(DT[, filled3 := fill_na(value), by = id]) 
#> user system elapsed 
#> 237.256 0.913 238.405 

Perché non mi basta usare na.locf.default? Anche se la differenza di velocità non è veramente importante, lo stesso problema si pone per altri tipi di comandi data.table (quelli che si basano su un'unione con la variabile in "by") - è un peccato ignorarli sistematicamente per ottenere un sintassi più semplice. Mi piacciono molto anche tutte le opzioni di lancio.

+2

Come funziona la soluzione 'na.locf' confronta con questa soluzione in termini di velocità? – GSee

+0

È l'involucro dell'intera cosa (a la 'dplyr :: mutate') non un'opzione? – shadowtalker

+0

Sarebbe utile se fornissi il codice per [creare un sample data.table] (http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example) che potremmo utilizzare per verificare i nostri risultati e per aiutare con il benchmarking. – GSee

risposta

9

Ecco un po 'più veloce e il modo più compatto di farlo (versione 1.9.3+):

DT[, filled4 := DT[!is.na(value)][DT, value, roll = T]] 
+0

Il mio errore! Ho pensato che questo avrebbe copiato l'intera tabella due volte (una volta tramite 'DT [! Is.na (valore)]', l'altra tramite 'X [Y]'), il che sarebbe problematico per un tipico set di dati ampio. Non è il caso (almeno per 'DT [! Is.na (valore)]')? – Matthew

+1

Ok. Le colonne di subsetting in Y non cambiano nulla. Tuttavia, sembra '(DT [, filled4: = DT [! Is.na (valore), lista (data, id, valore)] [DT, valore, roll = T]]' è più veloce della risposta in un ampio Banca dati – Matthew

Problemi correlati