使用先前的非NA可用值R填充NA

时间:2018-05-29 14:14:41

标签: r text

我正在尝试使用300万行数据集中的非NA先前填充NA值。目前我能够做到,但需要大约3小时。

约束 - 我不能使用任何库,它必须用R basic

完成

数据 - 我的数据如下(摘录)

Data Extract As Example

目前我一直在使用以下代码

CHARDIF <- diff(VERINDEX_VEC)

k = 1
for (j in VERINDEX_VEC){
  #when value is in vector calculate difference to next value and copy VER. 
Special cases for First and Last value
  ifelse(j == 1, ALL_POS$C01[j:CHARDIF[k]] <- ALL_POS$C01[j],
         ifelse(j == max(VERINDEX_VEC), ALL_POS$C01[j:max(as.numeric
(row.names(ALL_POS)))] <- ALL_POS$C01[j],ALL_POS$C01[j:(j+CHARDIF[k]-1)] <- 
ALL_POS$C01[j]))
  k = k + 1
}

正如你所看到的,我有一个非NA位置的向量,然后我计算位置之间的差异,这有助于我选择我想要粘贴的范围,因为我知道下一个非NA值正在发生。

有没有人有更好的解决方案?尤其是速度更快的

3 个答案:

答案 0 :(得分:1)

首先,我将生成随机数据来测试这个

# generate random data 
test_data <- data.frame(x = 1:100, y = rnorm(100))
# add random NAs
test_data$y[sample(1:100, 50)] <- NA

现在试试这个:

# locate non NAs in the wanted column
not_na <- which(!is.na(test_data$y))

# define the function replace_NAs_custom

replace_NAs_custom <- function(i, col){
         if(is.na(col[i])){
           col[i] <- col[max(not_na[not_na < i] )]
         }
         return(col[i] )
       }

test_data$y_2 <- unlist(lapply(1:nrow(test_data), replace_NAs_custom, test_data$y))

答案 1 :(得分:1)

看起来你的代码在每次循环时都会进行大量的计算和内存分配。为了减少时间,我们希望减少循环每次迭代的工作量。

我不是100%明确你的问题,但我认为我已经掌握了它的要点。听起来您只想获取最后一个非NA值并将其复制到具有NA值的行中。我们可以使用一对或多个索引来执行此操作。

在以下方法中,在进入循环之前,所有内存都已预先分配。唯一的内存操作是将值(NA)替换为另一个值。除了该操作之外,还检查该值是否为NA并且对索引进行了加法运算。为了更快地解决这个问题,您需要使用c优化的向量函数(可能来自包/库)。

使用前一个值填充NA:

# Fill with previous non-NA value
VERINDEX_VEC <- c(NA,"A1","A2",NA,NA,"A3",NA)
VERINDEX_VEC
# [1] NA   "A1" "A2" NA   NA   "A3" NA  

non_na_positions <- which(!is.na(VERINDEX_VEC))
# If the first value is NA we need to fill with NA until we hit a known value...
if(is.na(VERINDEX_VEC[1])){
  non_na_positions <- c(NA,non_na_positions)
}

index = 1

for(i in 1:length(VERINDEX_VEC)){
  if(is.na(VERINDEX_VEC[i])) {
    VERINDEX_VEC[i] <- VERINDEX_VEC[non_na_positions[index]]
  } else {
    index <- index + 1
  }
}

VERINDEX_VEC
# [1] NA   "A1" "A2" "A2" "A2" "A3" "A3"

使用下一个值填充NA:

# Fill with next non-NA Value
VERINDEX_VEC <- c(NA,"A1","A2",NA,NA,"A3",NA)
VERINDEX_VEC
# [1] NA   "A1" "A2" NA   NA   "A3" NA  

non_na_positions <- which(!is.na(VERINDEX_VEC))
# Never need the first position of the vector if we are looking-ahead...
index <- ifelse(non_na_positions[1]==1,2,1)

for(i in 1:length(VERINDEX_VEC)){
  if(is.na(VERINDEX_VEC[i])) {
    VERINDEX_VEC[i] <- VERINDEX_VEC[non_na_positions[index]]
  } else {
    index <- index + 1
  }
}

VERINDEX_VEC
# [1] "A1" "A1" "A2" "A3" "A3" "A3" NA  

答案 2 :(得分:0)

我相信我可能找到了一种更快的方式,至少比我的上一个答案快得多,但是我无法将它与你的代码进行比较,因为我无法重现输出。

(参见下面的基准测试结果)

你可以试试这个:

set.seed(223)
# generate random data 
test_data <- data.frame(x = 1:1000, y = rnorm(1000))
# add random NAs
test_data$y[sample(1:1000, 500)] <- NA



# which records are filled
not_na <- which(!is.na(test_data$y))

# calculate the distance from the previous filled value
# this is to identify how many times should each value be repeated
dist <- unlist(lapply(1:(length(not_na) - 1), 
                        function(i){
                          not_na[i+1] - not_na[i]
                        }))

# compine both to create a kind of "look-up table"
not_na <- data.frame(idx = not_na, 
                       rep_num = c(dist, nrow(test_data) - not_na[length(not_na)] + 1))

test_data$y_3 <- unlist(lapply(1:nrow(not_na), 
                                 function(x){
                                   rep(test_data[not_na$idx[x], "y"], times = not_na$rep_num[x])
                                 }))

基准测试:

f1()是最后的答案

f2()就是这个答案

  • 对于test_data中的100.000行

    # microbenchmark(f1(), times = 10)
    # Unit: seconds
    #  expr      min       lq     mean  median       uq      max neval
    #  f1() 39.54495 39.72853 40.38092 40.7027 40.76339 41.29006    10
    
    
    # microbenchmark(f2(), times = 10)
    # Unit: seconds
    # expr      min       lq     mean   median       uq      max neval
    # f2() 1.578852 1.610565 1.666488 1.645821 1.736301 1.755673    10
    
  • 对于1.000.000行,新方法需要大约16秒

    # microbenchmark(f2(), times = 1)
    # Unit: seconds
    # expr      min       lq     mean   median       uq      max neval
    # f2() 16.33777 16.33777 16.33777 16.33777 16.33777 16.33777     1