[r] NA를 최신 비 NA 값으로 교체

data.frame (또는 data.table)에서 NA에 가장 가까운 이전의 비 NA 값으로 “채우기”하고 싶습니다. 간단한 대신에 벡터를 사용하는 예 data.frame는 다음과 같습니다.

> y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)

나는 다음과 같이 fill.NAs()구성 할 수 있는 기능 을 원합니다 yy.

> yy
[1] NA NA NA  2  2  2  2  3  3  3  4  4

data.frame행이 NA 인 모든 항목이 많은 (총 ~ 1 Tb) 작은 크기 (~ 30-50 Mb)에 대해이 작업을 반복해야합니다 . 문제에 접근하는 좋은 방법은 무엇입니까?

내가 요리 한 못생긴 해결책은이 기능을 사용합니다.

last <- function (x){
    x[length(x)]
}

fill.NAs <- function(isNA){
if (isNA[1] == 1) {
    isNA[1:max({which(isNA==0)[1]-1},1)] <- 0 # first is NAs 
                                              # can't be forward filled
}
isNA.neg <- isNA.pos <- isNA.diff <- diff(isNA)
isNA.pos[isNA.diff < 0] <- 0
isNA.neg[isNA.diff > 0] <- 0
which.isNA.neg <- which(as.logical(isNA.neg))
if (length(which.isNA.neg)==0) return(NULL) # generates warnings later, but works
which.isNA.pos <- which(as.logical(isNA.pos))
which.isNA <- which(as.logical(isNA))
if (length(which.isNA.neg)==length(which.isNA.pos)){
    replacement <- rep(which.isNA.pos[2:length(which.isNA.neg)],
                                which.isNA.neg[2:max(length(which.isNA.neg)-1,2)] -
                                which.isNA.pos[1:max(length(which.isNA.neg)-1,1)])
    replacement <- c(replacement, rep(last(which.isNA.pos), last(which.isNA) - last(which.isNA.pos)))
} else {
    replacement <- rep(which.isNA.pos[1:length(which.isNA.neg)], which.isNA.neg - which.isNA.pos[1:length(which.isNA.neg)])
    replacement <- c(replacement, rep(last(which.isNA.pos), last(which.isNA) - last(which.isNA.pos)))
}
replacement
}

이 기능 fill.NAs은 다음과 같이 사용됩니다.

y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)
isNA <- as.numeric(is.na(y))
replacement <- fill.NAs(isNA)
if (length(replacement)){
which.isNA <- which(as.logical(isNA))
to.replace <- which.isNA[which(isNA==0)[1]:length(which.isNA)]
y[to.replace] <- y[replacement]
} 

산출

> y
[1] NA  2  2  2  2  3  3  3  4  4  4

… 작동하는 것 같습니다. 그러나, 사람, 그것은 추악하다! 어떤 제안?



답변

NA 값을 대체 하기 위해 zoo 패키지 의 na.locf()함수 를 사용하여 마지막 관찰수행 하려고 할 수 있습니다.

도움말 페이지에서 사용 예제의 시작은 다음과 같습니다.

library(zoo)

az <- zoo(1:6)

bz <- zoo(c(2,NA,1,4,5,2))

na.locf(bz)
1 2 3 4 5 6
2 2 1 4 5 2

na.locf(bz, fromLast = TRUE)
1 2 3 4 5 6
2 1 1 4 5 2

cz <- zoo(c(NA,9,3,2,3,2))

na.locf(cz)
2 3 4 5 6
9 3 2 3 2 


답변

오래된 질문을 찾아 내서 죄송합니다. 나는 기차 에서이 일을하는 기능을 찾을 수 없으므로 직접 작성했습니다.

나는 그것이 조금 더 빠르다는 것을 알게 된 것을 자랑스럽게 생각했습니다.
유연성이 떨어집니다.

그러나 그것은 ave내가 잘하는 것입니다.

repeat.before = function(x) {   # repeats the last non NA value. Keeps leading NA
    ind = which(!is.na(x))      # get positions of nonmissing values
    if(is.na(x[1]))             # if it begins with a missing, add the 
          ind = c(1,ind)        # first position to the indices
    rep(x[ind], times = diff(   # repeat the values at these indices
       c(ind, length(x) + 1) )) # diffing the indices + length yields how often 
}                               # they need to be repeated

x = c(NA,NA,'a',NA,NA,NA,NA,NA,NA,NA,NA,'b','c','d',NA,NA,NA,NA,NA,'e')
xx = rep(x, 1000000)
system.time({ yzoo = na.locf(xx,na.rm=F)})
## user  system elapsed   
## 2.754   0.667   3.406   
system.time({ yrep = repeat.before(xx)})
## user  system elapsed   
## 0.597   0.199   0.793   

편집하다

이것이 나의 가장 큰 답이 되었기 때문에, 나는 종종 동물원의 maxgap주장이 필요하기 때문에 내 자신의 기능을 사용하지 않는다는 생각이 들었다 . 내가 디버깅 할 수없는 dplyr + 날짜를 사용할 때 동물원에는 가장자리가 이상한 문제가 있기 때문에 오늘 이전 기능을 개선하기 위해 다시 돌아 왔습니다.

개선 된 기능과 다른 모든 항목을 벤치마킹했습니다. 기본 기능 세트의 tidyr::fill경우 가장 빠른 속도를 유지하면서도 가장 빠른 기능을 제공 합니다. @BrandonBertelsen의 Rcpp 항목은 여전히 ​​빠르지 만 입력 유형과 관련하여 융통성이 없습니다 (그의 오해로 인해 엣지 케이스를 잘못 테스트했습니다 all.equal).

당신이 필요하다면 maxgap, 아래의 내 기능은 동물원보다 빠릅니다 (날짜에 이상한 문제가 없습니다).

나는 올려 내 시험의 문서를 .

새로운 기능

repeat_last = function(x, forward = TRUE, maxgap = Inf, na.rm = FALSE) {
    if (!forward) x = rev(x)           # reverse x twice if carrying backward
    ind = which(!is.na(x))             # get positions of nonmissing values
    if (is.na(x[1]) && !na.rm)         # if it begins with NA
        ind = c(1,ind)                 # add first pos
    rep_times = diff(                  # diffing the indices + length yields how often
        c(ind, length(x) + 1) )          # they need to be repeated
    if (maxgap < Inf) {
        exceed = rep_times - 1 > maxgap  # exceeding maxgap
        if (any(exceed)) {               # any exceed?
            ind = sort(c(ind[exceed] + 1, ind))      # add NA in gaps
            rep_times = diff(c(ind, length(x) + 1) ) # diff again
        }
    }
    x = rep(x[ind], times = rep_times) # repeat the values at these indices
    if (!forward) x = rev(x)           # second reversion
    x
}

또한 formr 패키지에 함수를 넣었습니다 (Github 만 해당).


답변

data.table솔루션 :

dt <- data.table(y = c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA))
dt[, y_forward_fill := y[1], .(cumsum(!is.na(y)))]
dt
     y y_forward_fill
 1: NA             NA
 2:  2              2
 3:  2              2
 4: NA              2
 5: NA              2
 6:  3              3
 7: NA              3
 8:  4              4
 9: NA              4
10: NA              4

이 접근 방식은 0을 포워드 채울 때도 작동합니다.

dt <- data.table(y = c(0, 2, -2, 0, 0, 3, 0, -4, 0, 0))
dt[, y_forward_fill := y[1], .(cumsum(y != 0))]
dt
     y y_forward_fill
 1:  0              0
 2:  2              2
 3: -2             -2
 4:  0             -2
 5:  0             -2
 6:  3              3
 7:  0              3
 8: -4             -4
 9:  0             -4
10:  0             -4

이 방법은 규모가 큰 데이터와 그룹별로 순방향 채우기를 수행하려는 경우에 매우 유용합니다 data.table. 논리 by이전에 절에 그룹을 추가하십시오 cumsum.

dt <- data.table(group = sample(c('a', 'b'), 20, replace = TRUE), y = sample(c(1:4, rep(NA, 4)), 20 , replace = TRUE))
dt <- dt[order(group)]
dt[, y_forward_fill := y[1], .(group, cumsum(!is.na(y)))]
dt
    group  y y_forward_fill
 1:     a NA             NA
 2:     a NA             NA
 3:     a NA             NA
 4:     a  2              2
 5:     a NA              2
 6:     a  1              1
 7:     a NA              1
 8:     a  3              3
 9:     a NA              3
10:     a NA              3
11:     a  4              4
12:     a NA              4
13:     a  1              1
14:     a  4              4
15:     a NA              4
16:     a  3              3
17:     b  4              4
18:     b NA              4
19:     b NA              4
20:     b  2              2


답변

더 큰 데이터 볼륨을 처리하기 위해 더 효율적으로하기 위해 data.table 패키지를 사용할 수 있습니다.

require(data.table)
replaceNaWithLatest <- function(
  dfIn,
  nameColNa = names(dfIn)[1]
){
  dtTest <- data.table(dfIn)
  setnames(dtTest, nameColNa, "colNa")
  dtTest[, segment := cumsum(!is.na(colNa))]
  dtTest[, colNa := colNa[1], by = "segment"]
  dtTest[, segment := NULL]
  setnames(dtTest, "colNa", nameColNa)
  return(dtTest)
}


답변

내 모자 던지기 :

library(Rcpp)
cppFunction('IntegerVector na_locf(IntegerVector x) {
  int n = x.size();

  for(int i = 0; i<n; i++) {
    if((i > 0) && (x[i] == NA_INTEGER) & (x[i-1] != NA_INTEGER)) {
      x[i] = x[i-1];
    }
  }
  return x;
}')

기본 샘플 및 벤치 마크를 설정하십시오.

x <- sample(c(1,2,3,4,NA))

bench_em <- function(x,count = 10) {
  x <- sample(x,count,replace = TRUE)
  print(microbenchmark(
    na_locf(x),
    replace_na_with_last(x),
    na.lomf(x),
    na.locf(x),
    repeat.before(x)
  ), order = "mean", digits = 1)
}

그리고 몇 가지 벤치 마크를 실행하십시오.

bench_em(x,1e6)

Unit: microseconds
                    expr   min    lq  mean median    uq   max neval
              na_locf(x)   697   798   821    814   821 1e+03   100
              na.lomf(x)  3511  4137  5002   4214  4330 1e+04   100
 replace_na_with_last(x)  4482  5224  6473   5342  5801 2e+04   100
        repeat.before(x)  4793  5044  6622   5097  5520 1e+04   100
              na.locf(x) 12017 12658 17076  13545 19193 2e+05   100

만일을 위해 :

all.equal(
     na_locf(x),
     replace_na_with_last(x),
     na.lomf(x),
     na.locf(x),
     repeat.before(x)
)
[1] TRUE

최신 정보

숫자 형 벡터의 경우 함수가 약간 다릅니다.

NumericVector na_locf_numeric(NumericVector x) {
  int n = x.size();
  LogicalVector ina = is_na(x);

  for(int i = 1; i<n; i++) {
    if((ina[i] == TRUE) & (ina[i-1] != TRUE)) {
      x[i] = x[i-1];
    }
  }
  return x;
}


답변

이것은 나를 위해 일했다 :

  replace_na_with_last<-function(x,a=!is.na(x)){
     x[which(a)[c(1,1:sum(a))][cumsum(a)+1]]
  }


> replace_na_with_last(c(1,NA,NA,NA,3,4,5,NA,5,5,5,NA,NA,NA))

[1] 1 1 1 1 3 4 5 5 5 5 5 5 5 5

> replace_na_with_last(c(NA,"aa",NA,"ccc",NA))

[1] "aa"  "aa"  "aa"  "ccc" "ccc"

속도도 합리적입니다.

> system.time(replace_na_with_last(sample(c(1,2,3,NA),1e6,replace=TRUE)))


 user  system elapsed

 0.072   0.000   0.071 


답변

이 기능을 사용해보십시오. ZOO 패키지가 필요하지 않습니다.

# last observation moved forward
# replaces all NA values with last non-NA values
na.lomf <- function(x) {

    na.lomf.0 <- function(x) {
        non.na.idx <- which(!is.na(x))
        if (is.na(x[1L])) {
            non.na.idx <- c(1L, non.na.idx)
        }
        rep.int(x[non.na.idx], diff(c(non.na.idx, length(x) + 1L)))
    }

    dim.len <- length(dim(x))

    if (dim.len == 0L) {
        na.lomf.0(x)
    } else {
        apply(x, dim.len, na.lomf.0)
    }
}

예:

> # vector
> na.lomf(c(1, NA,2, NA, NA))
[1] 1 1 2 2 2
>
> # matrix
> na.lomf(matrix(c(1, NA, NA, 2, NA, NA), ncol = 2))
     [,1] [,2]
[1,]    1    2
[2,]    1    2
[3,]    1    2