2014-10-03 22 views
11

Jeśli ktoś chce wypełnić brakujące wartości zmiennej opartej na poprzedniej/posterior non NA obserwacji w grupie, komenda data.table jestuzupełnienie brakujących wartości przez grupę w 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]] 

który jest dość skomplikowany. To wstyd, ponieważ roll jest bardzo szybki i mocny opcja (szczególnie w porównaniu z zastosowaniem funkcji, takich jak zoo::na.locf w każdej grupie)

mogę napisać funkcję wygody wypełnienie brakujących wartości

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

a następnie Napisać

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

to naprawdę nie jest satysfakcjonująca, ponieważ chciałoby się napisać

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

To jednak wymaga dużej ilości czasu. Dla użytkownika końcowego trudno jest się dowiedzieć, że należy zadzwonić pod numer fill_na za pomocą opcji by i nie należy go używać z data.tableby. Czy istnieje wokół tego eleganckie rozwiązanie?

Niektóre Test prędkości

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 

Dlaczego nie wystarczy użyć na.locf.default? Nawet jeśli różnica prędkości nie jest tak naprawdę ważna, ta sama kwestia pojawia się w przypadku innych rodzajów komend data.table (tych, które polegają na scalaniu przez zmienną w "od") - szkoda, że ​​systematycznie je ignoruje, aby uzyskać łatwiejsza składnia. Bardzo podobają mi się również wszystkie opcje roll.

+2

jaki sposób 'rozwiązanie na.locf' porównać do tego rozwiązania w zakresie prędkości? – GSee

+0

Czy owinięcie całej rzeczy (a la 'dplyr :: mutate') nie jest opcją? – shadowtalker

+0

Byłoby pomocne, gdybyś podał kod [stworzyć przykładową tabelę danych] (http://stackoverflow.com/questions/5963269/how-to-make-a-great-r-reproducible-example), że możemy użyj, aby sprawdzić nasze wyniki i pomóc w benchmarkingu. – GSee

Odpowiedz

9

Oto nieco szybciej i bardziej zwarty sposób to zrobić (wersja 1.9.3+):

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

Mój błąd! Pomyślałem, że skopiowałoby to całą tabelę dwa razy (raz przez 'DT [! Is.na (value)]', drugie przez 'X [Y]'), co byłoby problematyczne dla typowego szerokiego zbioru danych. Czy tak nie jest (przynajmniej dla 'DT [! Is.na (value)])? – Matthew

+1

Ok. Podzestaw kolumn w Y nic nie zmienia. Jednakże wydaje się, że '(DT [, filled4: = DT [! Is.na (value), list (date, id, value)] [DT, value, roll = T]]' jest szybsze niż twoja odpowiedź w szerokim Baza danych – Matthew

Powiązane problemy