NA를 최신 비 NA 값으로 교체


141

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

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


1
이 한 이후 다른 질문에서, 나는 당신이 지금 발견 한 생각 roll=TRUEdata.table.
매트 Dowle

3
새로운 방법이 도입되고있다 fill에서R
Saksham

14
또한을 살펴보십시오 tidyr::fill().
zx8754

답변:


160

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 

2
또한 na.locf동물원에서는 동물원 객체뿐만 아니라 일반 벡터와 함께 작동합니다. 그 na.rm주장은 일부 응용 프로그램에서 유용 할 수 있습니다.
G. Grothendieck

5
na.locf(cz, na.rm=FALSE)계속지도하는 데 사용 합니다 NA.
BallpointBen

@BallpointBen의 의견은 중요하며 답변에 포함되어야합니다. 감사!

62

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

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

그러나 그것은 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 만 해당).


2
+1이지만 df여러 열이있는 열에 적용하려면 열마다 반복해야한다고 생각합니다 .
Zhubarb

3
@Ruben 보고서에 다시 한번 감사드립니다. 이제 버그는 R-Forge에서 수정되었습니다. 또한 na.locf0범위와 성능이 repeat_last기능 과 유사한 기능 을 조정하고 내보냈습니다 . 단서는 diff오히려 사용 cumsum하고 피하는 것이 었습니다 ifelse. 주요 na.locf.default기능은 더 많은 검사를 수행하고 여러 열을 처리하기 때문에 여전히 약간 느립니다.
Achim Zeileis

23

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

1
그룹별로 수행 할 수있는 기능은 훌륭합니다!
JCWong

22

더 큰 데이터 볼륨을 처리하기 위해 더 효율적으로하기 위해 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)
}

2
lapply를 추가하여 여러 NA 열에 직접 적용 할 수 있습니다.replaceNaWithLatest <- function( dfIn, nameColsNa = names(dfIn)[1] ){ dtTest <- data.table(dfIn) invisible(lapply(nameColsNa, function(nameColNa){ 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) }
xclotet

처음에는이 솔루션에 흥분했지만 실제로는 전혀 같은 일을하지 않습니다. 문제는 하나의 데이터 세트를 다른 데이터 세트로 채우는 것에 관한 것입니다. 이 답변은 단지 대치입니다.
Hack-R

19

내 모자 던지기 :

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

15

이것은 나를 위해 일했다 :

  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 

2
이 기능은 선행 NA가있을 때 예상 한대로 수행되지 않습니다. replace_na_with_last(c(NA,1:4,NA))(즉, 다음 값으로 채워집니다). 이 또한 기본 동작입니다 imputeTS::na.locf(x, na.remaining = "rev").
Ruben

이 경우에는 약간 다른 방법으로 기본값을 추가하는 것이 좋습니다. replace_na_with_last<-function(x,p=is.na,d=0)c(d,x)[cummax(seq_along(x)*(!p(x)))+1]
Nick Nassuphis

@NickNassuphis의 대답은 짧고 달콤하며 패키지에 의존하지 않으며 dplyr 파이프와 잘 작동합니다!

14

이 기능을 사용해보십시오. 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

그것을 향상시키기 위해 이것을 추가 할 수 있습니다 : if (!anyNA(x)) return(x).
Artem Klevtsov

13

선행을 갖는 것은 NA약간의 주름이지만, 주요 용어가 누락 되지 않은 경우 LOCF를 수행하는 매우 읽기 쉽고 (벡터화 된) 방법 은 다음과 같습니다.

na.omit(y)[cumsum(!is.na(y))]

약간 덜 읽기 쉬운 수정이 일반적으로 작동합니다.

c(NA, na.omit(y))[cumsum(!is.na(y))+1]

원하는 출력을 제공합니다.

c(NA, 2, 2, 2, 2, 3, 3, 4, 4, 4)


3
이것은 다소 우아합니다. 그것이 모든 경우에 작동하는지 확실하지 않지만 확실히 나를 위해 일했습니다!
ABT

13

에서 사용할 수 있는 data.table기능 nafill을 사용할 수 있습니다 data.table >= 1.12.3.

library(data.table)
nafill(y, type = "locf")
# [1] NA  2  2  2  2  3  3  4  4  4

벡터가의 열인 경우 다음 data.table을 참조하여 업데이트 할 수도 있습니다 setnafill.

d <- data.table(x = 1:10, y)
setnafill(d, type = "locf", cols = "y")
d
#      x  y
#  1:  1 NA
#  2:  2  2
#  3:  3  2
#  4:  4  2
#  5:  5  2
#  6:  6  3
#  7:  7  3
#  8:  8  4
#  9:  9  4
# 10: 10  4

NA여러 열에 있다면 ...

d <- data.table(x = c(1, NA, 2), y = c(2, 3, NA), z = c(4, NA, 5))
#     x  y  z
# 1:  1  2  4
# 2: NA  3 NA
# 3:  2 NA  5

... 한 번에 참조로 채울 수 있습니다.

setnafill(d, type = "locf")
d
#    x y z
# 1: 1 2 4
# 2: 1 3 4
# 3: 2 3 5

참고 :

현재는 이중정수 데이터 유형 만 data.table 1.12.6지원됩니다.

기능은 곧 확장 될 것입니다. 일시적인 해결 방법을 찾을 수있는 공개 문제 nafill, character, factor 및 기타 유형에 대한 setnafill을 참조하십시오 .


5

tidyverse 패키지는 다음과 같은 간단한 방법을 제안합니다.

y = c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)

# first, transform it into a data.frame

y = as.data.frame(y)
   y
1  NA
2   2
3   2
4  NA
5  NA
6   3
7  NA
8   4
9  NA
10 NA

fill(y, y, .direction = 'down')
    y
1  NA
2   2
3   2
4   2
5   2
6   3
7   3
8   4
9   4
10  4

3

na.locf( NA마지막 관측 전달) 기능을 제공하는 많은 패키지가 있습니다.

  • xts - xts::na.locf
  • zoo - zoo::na.locf
  • imputeTS - imputeTS::na.locf
  • spacetime - spacetime::na.locf

또한이 기능의 이름이 다른 다른 패키지도 있습니다.


2

Brandon Bertelsen의 Rcpp에 대한 후속 조치. 나를 위해 NumericVector 버전은 작동하지 않았습니다. 첫 번째 NA 만 교체했습니다. 그 이유는ina함수 시작시 벡터가 한 번만 평가 입니다.

대신 IntegerVector 함수와 동일한 접근 방식을 취할 수 있습니다. 다음은 나를 위해 일했습니다.

library(Rcpp)
cppFunction('NumericVector na_locf_numeric(NumericVector x) {
  R_xlen_t n = x.size();
  for(R_xlen_t i = 0; i<n; i++) {
    if(i > 0 && !R_finite(x[i]) && R_finite(x[i-1])) {
      x[i] = x[i-1];
    }
  }
  return x;
}')

CharacterVector 버전이 필요한 경우 동일한 기본 접근 방식도 작동합니다.

cppFunction('CharacterVector na_locf_character(CharacterVector x) {
  R_xlen_t n = x.size();
  for(R_xlen_t i = 0; i<n; i++) {
    if(i > 0 && x[i] == NA_STRING && x[i-1] != NA_STRING) {
      x[i] = x[i-1];
    }
  }
  return x;
}')

int n = x.size () 및 for (int i = 0; i <n; i ++)는 double로 바꿔야합니다. R에서 벡터는 c ++ int 크기보다 클 수 있습니다.
stats0007

이 함수는 "R_xlen_t"를 반환하는 것 같습니다. R이 긴 벡터 지원으로 컴파일되면 ptrdiff_t로 정의됩니다. 그렇지 않으면 int입니다. 정정 주셔서 감사합니다!
Evan Cortens

1

@AdamO 솔루션의 수정 사항은 다음과 같습니다. 이것은 na.omit기능을 우회하기 때문에 더 빨리 실행됩니다 . 이것은 NA벡터 의 값을 덮어 씁니다 y(선행 NAs 제외 ).

   z  <- !is.na(y)                  # indicates the positions of y whose values we do not want to overwrite
   z  <- z | !cumsum(z)             # for leading NA's in y, z will be TRUE, otherwise it will be FALSE where y has a NA and TRUE where y does not have a NA
   y  <- y[z][cumsum(z)]

0

나는 아래를 시도했다.

nullIdx <- as.array(which(is.na(masterData$RequiredColumn)))
masterData$RequiredColumn[nullIdx] = masterData$RequiredColumn[nullIdx-1]

nullIdx는 masterData $ RequiredColumn이 Null / NA 값을 갖는 idx 번호를 가져옵니다. 다음 줄에서는 해당 Idx-1 값, 즉 각 NULL / NA 이전의 마지막 유효 값으로 대체합니다.


연속 된 여러 누락 된 값이있는 경우이 작동하지 않습니다 - 1 NA NA교대로 1 1 NA. 또한 as.array()불필요 하다고 생각합니다 .
Gregor Thomas

0

다른 제안보다 더 효율적인지 확실하지 않지만 이것은 나를 위해 일했습니다.

rollForward <- function(x){
  curr <- 0
  for (i in 1:length(x)){
    if (is.na(x[i])){
      x[i] <- curr
    }
    else{
      curr <- x[i]
    }
  }
  return(x)
}

0
fill.NAs <- function(x) {is_na<-is.na(x); x[Reduce(function(i,j) if (is_na[j]) i else j, seq_len(length(x)), accumulate=T)]}

fill.NAs(c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA))

[1] NA  2  2  2  2  3  3  4  4  4

Reduce는 유사한 작업에 유용 할 수있는 훌륭한 기능적 프로그래밍 개념입니다. 불행히도 R repeat.before에서는 위의 답변 보다 ~ 70 배 느립니다 .


0

나는 개인적으로이 기능을 사용합니다. 나는 그것이 얼마나 빠르거나 느린 지 모른다. 그러나 라이브러리를 사용할 필요없이 작업을 수행합니다.

replace_na_with_previous<-function (vector) {
        if (is.na(vector[1])) 
            vector[1] <- na.omit(vector)[1]
        for (i in 1:length(vector)) {
            if ((i - 1) > 0) {
                if (is.na(vector[i])) 
                    vector[i] <- vector[i - 1]
            }
        }
        return(vector)
    }

이 기능을 데이터 프레임에 적용하려면 데이터 프레임을 df라고하면 간단히

df[]<-lapply(df,replace_na_with_previous)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.