用最新的非NA值替换NA

Ryo*_*ogi 129 r zoo r-faq data.table

在data.frame(或data.table)中,我想用最近的非NA值"填充"NA.一个简单的例子,使用向量(而不是a data.frame)如下:

> y <- c(NA, 2, 2, NA, NA, 3, NA, 4, NA, NA)
Run Code Online (Sandbox Code Playgroud)

我想要一个fill.NAs()允许我构造的函数yy:

> yy
[1] NA NA NA  2  2  2  2  3  3  3  4  4
Run Code Online (Sandbox Code Playgroud)

我需要对许多(总计~1 Tb)小尺寸data.frames(~30-50 Mb)重复此操作,其中一行是NA,其所有条目都是.解决问题的好方法是什么?

我做的丑陋的解决方案使用这个功能:

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
}
Run Code Online (Sandbox Code Playgroud)

该功能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]
} 
Run Code Online (Sandbox Code Playgroud)

产量

> y
[1] NA  2  2  2  2  3  3  3  4  4  4
Run Code Online (Sandbox Code Playgroud)

......似乎有效.但是,伙计,这太丑了!有什么建议?

Dir*_*tel 144

您可能希望使用zoo包中的na.locf()函数来执行最后一次观察以替换您的NA值.

以下是帮助页面中其使用示例的开头:

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 
Run Code Online (Sandbox Code Playgroud)

  • 使用`na.locf(cz,na.rm = FALSE)`来保持领先的"NA". (3认同)
  • 另请注意,动物园中的"na.locf"适用于普通向量和动物园对象.它的`na.rm`参数在某些应用程序中很有用. (2认同)

Rub*_*ben 58

很抱歉找到一个老问题.我无法查找在火车上完成这项工作的功能,所以我自己写了一个.

我很自豪地发现它的速度要快一点.
但它不那么灵活.

但它很好用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   
Run Code Online (Sandbox Code Playgroud)

编辑

由于这成为我最热烈的答案,我经常被提醒我不使用自己的功能,因为我经常需要动物园的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
}
Run Code Online (Sandbox Code Playgroud)

我还把这个函数放在我的formr包中(仅限Github).

  • @Ruben再次感谢您的举报.到目前为止,该错误已在R-Forge上修复.此外,我已经调整并导出了主力函数`na.locf0`,它的范围和性能与你的`repeat_last`函数类似.线索是使用`diff`而不是`cumsum`并避免使用`ifelse`.主要的`na.locf.default`函数仍然有点慢,因为它执行更多检查并处理多个列等. (3认同)
  • +1,但我猜这个需要在每列中循环,如果你想将它应用于具有多列的`df`? (2认同)

Mic*_*lli 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)
}
Run Code Online (Sandbox Code Playgroud)

  • 可以添加一个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)}` (2认同)

Bra*_*sen 18

扔我的帽子:

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;
}')
Run Code Online (Sandbox Code Playgroud)

设置基本样本和基准:

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)
}
Run Code Online (Sandbox Code Playgroud)

并运行一些基准:

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
Run Code Online (Sandbox Code Playgroud)

以防万一:

all.equal(
     na_locf(x),
     replace_na_with_last(x),
     na.lomf(x),
     na.locf(x),
     repeat.before(x)
)
[1] TRUE
Run Code Online (Sandbox Code Playgroud)

更新

对于数字向量,函数有点不同:

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;
}
Run Code Online (Sandbox Code Playgroud)


Rti*_*ist 16

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
Run Code Online (Sandbox Code Playgroud)

  • @AnilGoyal 这对我的情况来说是一个好处 (2认同)

小智 15

一个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
Run Code Online (Sandbox Code Playgroud)

这种方法也适用于前向填充零:

> 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
Run Code Online (Sandbox Code Playgroud)

这种方法对于大规模数据以及您希望按组执行前向填充的方式非常有用,这是非常简单的data.table.只需bycumsum逻辑之前将组添加到子句中.

  • 通过小组来完成这项工作的能力真是太棒了! (2认同)

Eld*_*rov 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

例:

> # 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
Run Code Online (Sandbox Code Playgroud)


小智 13

这对我有用:

  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"
Run Code Online (Sandbox Code Playgroud)

速度也合理:

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


 user  system elapsed 

 0.072   0.000   0.071 
Run Code Online (Sandbox Code Playgroud)

  • 当存在领先的NA时,此功能无法满足您的期望。`replace_na_with_last(c(NA,1:4,NA))`(即,它们用以下值填充)。这也是imputeTS :: na.locf(x,na.remaining =“ rev”)的默认行为。 (2认同)

Hen*_*rik 9

您可以使用该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
Run Code Online (Sandbox Code Playgroud)

如果向量是中的列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
Run Code Online (Sandbox Code Playgroud)

注意:

当前仅支持双精度整数数据类型[ data.table 1.12.6]。

该功能很可能很快就会扩展;请参阅未解决的问题nafill,针对字符,因子和其他类型的setnafill,您还可以在其中找到临时解决方法


Ada*_*amO 8

有一个领先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认同)

sta*_*007 6

有很多包提供na.locfNA上次观察结转)功能:

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

以及此函数命名不同的其他包。