Thay thế NA bằng giá trị không NA mới nhất


141

Trong data.frame (hoặc data.table), tôi muốn "điền vào" NA với giá trị không NA gần nhất trước đó. Một ví dụ đơn giản, sử dụng vectơ (thay vì a data.frame) là như sau:

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

Tôi muốn một chức năng fill.NAs()cho phép tôi xây dựng yysao cho:

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

Tôi cần lặp lại thao tác này trong nhiều (tổng ~ 1 Tb) kích thước nhỏ data.frame(~ 30-50 Mb), trong đó một hàng là NA là tất cả các mục nhập của nó. Một cách tốt để tiếp cận vấn đề là gì?

Giải pháp xấu xí tôi nấu lên sử dụng chức năng này:

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
}

Các chức năng fill.NAsđược sử dụng như sau:

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

Đầu ra

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

... có vẻ như hoạt động. Nhưng, người đàn ông, nó là xấu xí! Bất kỳ đề xuất?


1
Từ những câu hỏi khác kể từ khi thế này, tôi nghĩ rằng bây giờ bạn đã tìm thấy roll=TRUEtrong data.table.
Matt Dowle

3
Một phương pháp mới đang được giới thiệu như filltrongR
Saksham

14
Ngoài ra, nhìn vào tidyr::fill().
zx8754

Câu trả lời:


160

Bạn có thể muốn sử dụng na.locf()chức năng từ gói sở thú để thực hiện quan sát cuối cùng về phía trước để thay thế các giá trị NA của bạn.

Đây là sự khởi đầu của ví dụ sử dụng từ trang trợ giúp:

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
Cũng lưu ý rằng na.locftrong vườn thú hoạt động với các vectơ thông thường cũng như các đối tượng sở thú. na.rmĐối số của nó có thể hữu ích trong một số ứng dụng.
G. Grothendieck

5
Sử dụng na.locf(cz, na.rm=FALSE)để tiếp tục dẫn đầu NA.
BallpointBen

Nhận xét của @BallpointBen rất quan trọng và cần được đưa vào câu trả lời. Cảm ơn!
Ben

62

Xin lỗi vì đã đào lên một câu hỏi cũ. Tôi không thể tìm kiếm chức năng để thực hiện công việc này trên tàu, vì vậy tôi đã tự viết một cái.

Tôi tự hào khi biết rằng nó nhanh hơn một chút.
Nó kém linh hoạt hơn.

Nhưng nó chơi tốt với ave, đó là những gì tôi cần.

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   

Biên tập

Khi đây trở thành câu trả lời được đánh giá cao nhất của tôi, tôi thường được nhắc nhở rằng tôi không sử dụng chức năng của riêng mình, vì tôi thường cần maxgaptranh luận của sở thú . Bởi vì sở thú có một số vấn đề kỳ lạ trong các trường hợp cạnh khi tôi sử dụng dplyr + ngày mà tôi không thể gỡ lỗi, tôi đã quay lại vấn đề này hôm nay để cải thiện chức năng cũ của mình.

Tôi đã điểm chuẩn chức năng cải tiến của tôi và tất cả các mục khác ở đây. Đối với các tính năng cơ bản, tidyr::filllà nhanh nhất trong khi cũng không làm hỏng các trường hợp cạnh. Mục nhập Rcpp của @BrandonBertelsen vẫn nhanh hơn, nhưng nó không linh hoạt về loại đầu vào (anh ta đã kiểm tra các trường hợp cạnh không chính xác do hiểu nhầm all.equal).

Nếu bạn cần maxgap, chức năng của tôi dưới đây nhanh hơn sở thú (và không có vấn đề kỳ lạ với ngày).

Tôi đưa ra các tài liệu của các bài kiểm tra của tôi .

chức năng mới

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
}

Tôi cũng đã đặt chức năng trong gói formr của mình (chỉ dành cho Github).


2
+1, nhưng tôi đoán điều này cần phải được lặp trên mỗi cột nếu bạn muốn áp dụng điều này cho một dfcột có nhiều cột?
Zhubarb

3
@Ruben Cảm ơn một lần nữa cho báo cáo của bạn. Đến bây giờ lỗi đã được sửa trên R-Forge. Ngoài ra tôi đã điều chỉnh và xuất hàm workhorse na.locf0hiện tương tự về phạm vi và hiệu suất với repeat_lastchức năng của bạn . Manh mối là sử dụng diffchứ không phải cumsumvà tránh ifelse. na.locf.defaultChức năng chính vẫn hơi chậm hơn vì nó thực hiện thêm một số kiểm tra và xử lý nhiều cột, v.v.
Achim Zeileis

23

một data.tablegiải pháp:

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

Cách tiếp cận này cũng có thể làm việc với các số không điền vào phía trước:

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

phương pháp này trở nên rất hữu ích trên dữ liệu ở quy mô và nơi bạn muốn thực hiện điền vào theo nhóm (s), điều này không quan trọng bằng data.table. chỉ cần thêm (các) nhóm vào bymệnh đề trước cumsumlogic.

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
Khả năng để làm điều này bởi các nhóm là tuyệt vời!
JCWong

22

Xử lý một khối lượng dữ liệu lớn, để hiệu quả hơn, chúng ta có thể sử dụng gói 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
Một lapply có thể được thêm vào để nó có thể áp dụng trực tiếp vào nhiều cột 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

Lúc đầu, tôi rất hào hứng với giải pháp này, nhưng thực tế nó không làm điều tương tự. Câu hỏi là về việc điền vào 1 bộ dữ liệu với một bộ khác. Câu trả lời này chỉ là sự buộc tội.
Hack-R

19

Ném mũ của tôi vào:

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

Thiết lập mẫu cơ bản và điểm chuẩn:

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

Và chạy một số điểm chuẩn:

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

Chỉ trong trường hợp:

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

Cập nhật

Đối với một vectơ số, hàm này hơi khác một chút:

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

Điều này đã làm việc cho tôi:

  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"

tốc độ cũng hợp lý:

> 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
Chức năng này không làm những gì bạn mong đợi khi có các NA hàng đầu. replace_na_with_last(c(NA,1:4,NA))(tức là chúng chứa đầy giá trị sau). Đây cũng là hành vi mặc định của imputeTS::na.locf(x, na.remaining = "rev").
Ruben

tốt hơn để thêm một mặc định cho trường hợp này, cách tiếp cận hơi khác: replace_na_with_last<-function(x,p=is.na,d=0)c(d,x)[cummax(seq_along(x)*(!p(x)))+1]
Nick Nassuphis

Câu trả lời của @NickNassuphis là ngắn gọn, ngọt ngào, không phụ thuộc vào gói và hoạt động tốt với các ống dplyr!
Kim

14

Hãy thử chức năng này. Nó không yêu cầu gói 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)
    }
}

Thí dụ:

> # 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

Để cải thiện nó, bạn có thể thêm điều này : if (!anyNA(x)) return(x).
Artem Klevtsov

13

Có một vị trí dẫn đầu NAlà một chút khó khăn, nhưng tôi tìm thấy một cách rất dễ đọc (và được vector hóa) khi thực hiện LOCF khi thuật ngữ hàng đầu không bị thiếu là:

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

Một sửa đổi ít dễ đọc hơn nói chung hoạt động:

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

cho đầu ra mong muốn:

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


3
Điều này là khá thanh lịch. Không chắc chắn nếu nó hoạt động trong tất cả các trường hợp nhưng nó chắc chắn làm việc cho tôi!
ABT

12

Bạn có thể sử dụng data.tablechức năng nafill, có sẵn từ data.table >= 1.12.3.

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

Nếu vectơ của bạn là một cột trong a data.table, bạn cũng có thể cập nhật nó bằng cách tham khảo với 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

Nếu bạn có NAtrong một số cột ...

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

... bạn có thể điền chúng bằng cách tham khảo trong một lần:

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

Lưu ý rằng:

Chỉ các kiểu dữ liệu képsố nguyên hiện được [ data.table 1.12.6] hỗ trợ.

Các chức năng rất có thể sẽ sớm được mở rộng; xem vấn đề mở nafill, setnafill cho nhân vật, yếu tố và các loại khác , nơi bạn cũng tìm thấy một cách giải quyết tạm thời .


5

Gói tidyverse đề xuất một cách đơn giản để làm điều đó:

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

Có một loạt các gói cung cấp các chức năng na.locf( NAQuan sát lần cuối được tiến hành):

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

Và các gói khác trong đó chức năng này được đặt tên khác nhau.


2

Theo dõi các đóng góp Rcpp của Brandon Bertelsen. Đối với tôi, phiên bản NumericVector không hoạt động: nó chỉ thay thế NA đầu tiên. Điều này là doina vectơ chỉ được đánh giá một lần, ở đầu hàm.

Thay vào đó, người ta có thể thực hiện chính xác cách tiếp cận như đối với hàm IntegerVector. Sau đây làm việc cho tôi:

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

Trong trường hợp bạn cần một phiên bản CharacterVector, cách tiếp cận cơ bản tương tự cũng hoạt động:

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 () và for (int i = 0; i <n; i ++) nên được thay thế bằng double. Trong R một vectơ có thể lớn hơn kích thước int c ++.
thống kê0007

Có vẻ như hàm này trả về "R_xlen_t". Nếu R được biên dịch với hỗ trợ vector dài, điều này được định nghĩa là ptrdiff_t; nếu không, nó là một int. Cảm ơn vì sự đúng đắn của bạn!
Evan Cortens

1

Đây là bản sửa đổi của giải pháp @ AdamO. Cái này chạy nhanh hơn, vì nó bỏ qua na.omitchức năng. Điều này sẽ ghi đè lên các NAgiá trị trong vectơ y(ngoại trừ NAs hàng đầu ).

   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

Tôi đã thử dưới đây:

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

nullIdx nhận được số idx trong đó bao giờ masterData $ requiredColumn có giá trị Null / NA. Trong dòng tiếp theo, chúng tôi thay thế nó bằng giá trị Idx-1 tương ứng, tức là giá trị tốt cuối cùng trước mỗi NULL / NA


Điều này không hoạt động nếu có nhiều giá trị thiếu liên tiếp - 1 NA NAbiến thành 1 1 NA. Ngoài ra, tôi nghĩ rằng as.array()không cần thiết.
Gregor Thomas

0

Điều này làm việc cho tôi, mặc dù tôi không chắc liệu nó có hiệu quả hơn các đề xuất khác hay không.

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

Giảm là một khái niệm lập trình chức năng tốt đẹp có thể hữu ích cho các nhiệm vụ tương tự. Thật không may trong R nó chậm hơn ~ 70 lần so với repeat.beforecâu trả lời ở trên.


0

Cá nhân tôi sử dụng chức năng này. Tôi không biết nó nhanh hay chậm. Nhưng nó làm công việc của nó mà không phải sử dụng các thư viện.

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

nếu bạn muốn áp dụng chức năng này trong khung dữ liệu, nếu khung dữ liệu của bạn được gọi là df thì chỉ cần

df[]<-lapply(df,replace_na_with_previous)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.