Идиоматический способ копирования значений ячеек в векторе R

Возможный дубликат:
Заполнить NA в векторе, используя предыдущие значения, не относящиеся к NA?

Есть ли идиоматический способ скопировать значения ячеек «вниз» в векторе R? Под «копированием» я подразумеваю замену NA ближайшим предыдущим значением, отличным от NA.

Хотя я могу сделать это очень просто с помощью цикла for, он работает очень медленно. Любые советы о том, как векторизовать это, будут оценены.

# Test code
# Set up test data
len <- 1000000
data <- rep(c(1, rep(NA, 9)), len %/% 10) * rep(1:(len %/% 10), each=10)
head(data, n=25)
tail(data, n=25)

# Time naive method
system.time({
  data.clean <- data;
  for (i in 2:length(data.clean)){
    if(is.na(data.clean[i])) data.clean[i] <- data.clean[i-1]
  }
})

# Print results
head(data.clean, n=25)
tail(data.clean, n=25)

Результат тестового запуска:

> # Set up test data
> len <- 1000000
> data <- rep(c(1, rep(NA, 9)), len %/% 10) * rep(1:(len %/% 10), each=10)
> head(data, n=25)
 [1]  1 NA NA NA NA NA NA NA NA NA  2 NA NA NA NA NA NA NA NA NA  3 NA NA NA NA
> tail(data, n=25)
 [1]     NA     NA     NA     NA     NA  99999     NA     NA     NA     NA
[11]     NA     NA     NA     NA     NA 100000     NA     NA     NA     NA
[21]     NA     NA     NA     NA     NA
> 
> # Time naive method
> system.time({
+   data.clean <- data;
+   for (i in 2:length(data.clean)){
+     if(is.na(data.clean[i])) data.clean[i] <- data.clean[i-1]
+   }
+ })
   user  system elapsed 
   3.09    0.00    3.09 
> 
> # Print results
> head(data.clean, n=25)
 [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3
> tail(data.clean, n=25)
 [1]  99998  99998  99998  99998  99998  99999  99999  99999  99999  99999
[11]  99999  99999  99999  99999  99999 100000 100000 100000 100000 100000
[21] 100000 100000 100000 100000 100000
> 

person fmark    schedule 22.01.2013    source источник
comment
stackoverflow.com/questions/9023072/   -  person Matthew Lundberg    schedule 22.01.2013


Ответы (2)


Используйте 1_

Оборачиваем ваш код в функцию f (включая возврат data.clean в конце):

library(rbenchmark)
library(zoo)

identical(f(data), na.locf(data))
## [1] TRUE

benchmark(f(data), na.locf(data), replications=10, columns=c("test", "elapsed", "relative"))
##            test elapsed relative
## 1       f(data)  21.460   14.471
## 2 na.locf(data)   1.483    1.000
person Matthew Lundberg    schedule 22.01.2013

Я не знаю насчет идиоматики, но здесь мы идентифицируем значения, не относящиеся к NA (idx), и индекс последнего значения, не относящегося к NA (cumsum(idx)).

f1 <- function(x) {
    idx <- !is.na(x)
    x[idx][cumsum(idx)]
}

что кажется примерно в 6 раз быстрее, чем na.locf для данных примера. Он отбрасывает ведущие NA, как na.locf по умолчанию, поэтому

f2 <- function(x, na.rm=TRUE) {
    idx <- !is.na(x)
    cidx <- cumsum(idx)
    if (!na.rm)
        cidx[cidx==0] <- NA_integer_
    x[idx][cidx]
}

что, кажется, добавляет около 30% времени, когда na.rm=FALSE. Предположительно, у na.locf есть и другие достоинства, охватывающие больше угловых случаев и позволяющие заполнять вверх, а не вниз (что в любом случае является интересным упражнением в мире cumsum). Также ясно, что мы делаем как минимум пять распределений потенциально больших данных — idx (на самом деле мы вычисляем is.na() и его дополнение), cumsum(idx), x[idx] и x[idx][cumsum(idx)] — так что есть место для дальнейшего улучшения, например, в C

person Martin Morgan    schedule 22.01.2013
comment
Я бы назвал это идиоматическим. Очень хорошо. В 7 раз быстрее, чем na.locf в моей системе. - person Matthew Lundberg; 22.01.2013