【问题标题】:Remove trailing (last) rows with NAs in all columns删除所有列中带有 NA 的尾随(最后)行
【发布时间】:2021-04-17 16:36:35
【问题描述】:

我试图排除在该行的所有列中具有缺失值 (NA) 的行,并且所有后续行都只有缺失值(或者是最后一个空行本身),即我想删除尾随“所有-NA" 行。

我想出了下面的解决方案,它有效但速度太慢(我在数千个表上使用此功能),可能是因为 while 循环。

## Aux function to remove NA rows below table
remove_empty_row_last <- function(dt){
  dt[ , row_empty := rowSums(is.na(dt)) == ncol(dt)] 
  while (dt[.N, row_empty] == TRUE) {
    dt <- dt[1:(.N-1)]
    
  }
  dt %>% return()
}

d <- data.table(a = c(1,NA,3,NA,5,NA,NA), b = c(1,NA,3,4,5,NA,NA))
remove_empty_row_last(d)

#EDIT2: adding more test cases
d2 <- data.table(A = c(1,NA,3,NA,5,1 ,NA), B = c(1,NA,3,4,5,NA,NA))
remove_empty_row_last(d2)
d3 <- data.table(A = c(1,NA,3,NA,5,NA,NA), B = c(1,NA,3,4,5,1,NA))
remove_empty_row_last(d3)

#Edit3:adding no NA rows test case
d4 <- data.table(A = c(1,2,3,NA,5,NA,NA), B = c(1,2,3,4,5,1,7))
d4 %>% remove_empty_row_last()

【问题讨论】:

  • 你应该多尝试提供简洁的例子,dt %&gt;% return() 真的很糟糕。
  • 不知道速度,但在这方面值得一提的是zoo::na.trim:na.trim(d, is.na = "all", sides = "right")

标签: r data.table subset na missing-data


【解决方案1】:

这是另一种依赖 的方法。

library(Rcpp)
library(data.table)

Rcpp::cppFunction("
IntegerVector which_end_cont(LogicalVector x) {
  const int n = x.size();
  int consecutive = 0;
  
  for (int i = n - 1; i >= 0; i--) {
    if (x[i]) consecutive++; else break;
  }
  IntegerVector out(consecutive);
  if (consecutive == 0) 
    return(out);
  else
    return(seq(1, n - consecutive));
}
")

remove_empty_row_last3 <- function(dt) {
  lgl = rowSums(is.na(dt)) == length(dt)
  ind = which_end_cont(lgl)
  if (length(ind)) return(dt[ind]) else return(dt)
}

基本上是这样

  1. 使用 R 找出哪些行完全不适用。
  2. 它使用 循环遍历逻辑向量以确定末尾有多少连续的空行。使用 可以让我们最小化分配的内存。
  3. 如果最后没有空行,我们会通过返回输入 来防止分配内存。否则,我们在 中分配序列并将其返回给data.table 的子集。

使用,对于末尾有空行的情况大约快 3 倍,在没有空行的情况下大约快 15 倍。

编辑

如果你花时间添加了,好在已经导出了它们的一些内部函数,以便可以直接从C中调用它们。这样可以进一步简化事情,使它非常,非常快,主要是因为我们可以跳过在[data.table 期间执行的 NSE,这就是为什么现在所有条件都比 OP 原始函数快约 15 倍。

Rcpp::cppFunction("
SEXP mysub2(SEXP dt, LogicalVector x) {
const int n = x.size();
int consecutive = 0;
  
  for (int i = n - 1; i >= 0; i--) {
    if (x[i]) consecutive++; else break;
  }
  if (consecutive == 0) 
    return(dt);
  else
    return(DT_subsetDT(dt, wrap(seq(1, n - consecutive)), wrap(seq_len(LENGTH(dt)))));
}",
                  include="#include <datatableAPI.h>",
                  depends="data.table")

remove_empty_row_last4 <- function(dt) {
  lgl = rowSums(is.na(dt)) == length(dt)
  return(mysub2(dt, lgl))
}

dt = copy(d)
dt2 = copy(d2)
dt3 = copy(d3)
dt4 = copy(d4)
microbenchmark::microbenchmark(original = remove_empty_row_last(d3),
                               rcpp_subset = remove_empty_row_last4(dt3), 
                               rcpp_ind_only = remove_empty_row_last3(dt3),
                               waldi = remove_empty_row_last_new(dt3),
                               ian = dt3[!dt3[,any(rowSums(is.na(.SD)) == ncol(.SD)) & rleid(rowSums(is.na(.SD)) == ncol(.SD)) == max(rleid(rowSums(is.na(.SD)) == ncol(.SD))),]])


## Unit: microseconds
##           expr   min     lq    mean median     uq   max neval
##       original 498.0 519.00 539.602 537.65 551.85 621.6   100
##    rcpp_subset  34.0  39.95  43.422  43.30  46.70  59.0   100
##  rcpp_ind_only 116.9 129.75 139.943 140.15 146.35 177.7   100
##          waldi 370.9 387.70 408.910 400.55 417.90 683.4   100
##            ian 432.0 445.30 461.310 456.25 473.35 554.1   100
##         andrew 120.0 131.40 143.153 141.60 151.65 197.5   100

【讨论】:

  • 仅供参考:@JanGorecki 关于您在工作中的公关。请参阅 Jan 关于 data.table C 出口的待处理 PR:github.com/Rdatatable/data.table/pull/4753
  • 不错!在这里。我可以在我的数据中确认 10 倍的速度增益。解析子表后,我现在有一个 6k 小表的列表,其中包含尾随 NA。 Waldi 的答案需要 7.7 秒,而你的答案需要 0.78 秒。
【解决方案2】:

我迟到了,但这里有另一个选择,它应该相对内存效率更高,并且只使用基本 R。

library(data.table)

d <- data.table(a=c(1,NA,3,NA,5,NA,NA),b=c(1,NA,3,4,5,NA,NA))
remove_empty_row_last_andrew(d)
#>     a  b
#> 1:  1  1
#> 2: NA NA
#> 3:  3  3
#> 4: NA  4
#> 5:  5  5

d2 <- data.table(A=c(1,NA,3,NA,5,1 ,NA),B=c(1,NA,3,4,5,NA,NA))
remove_empty_row_last_andrew(d2)
#>     A  B
#> 1:  1  1
#> 2: NA NA
#> 3:  3  3
#> 4: NA  4
#> 5:  5  5
#> 6:  1 NA

d3 <- data.table(A=c(1,NA,3,NA,5,NA,NA),B=c(1,NA,3,4,5,1,NA))
remove_empty_row_last_andrew(d3)
#>     A  B
#> 1:  1  1
#> 2: NA NA
#> 3:  3  3
#> 4: NA  4
#> 5:  5  5
#> 6: NA  1

d4 <- data.table(A=c(1,2,3,NA,5,NA,NA),B=c(1,2,3,4,5,1,7))
remove_empty_row_last_andrew(d4)
#>     A B
#> 1:  1 1
#> 2:  2 2
#> 3:  3 3
#> 4: NA 4
#> 5:  5 5
#> 6: NA 1
#> 7: NA 7

reprex package (v0.3.0) 于 2021-02-01 创建

功能:

remove_empty_row_last_andrew = function(x) {
  idx = do.call(pmin.int, lapply(x, is.na))
  length_idx = length(idx)
  
  if(idx[length_idx] == 0) {
    return(x)
  }
  
  start_idx = length_idx - which.min(idx[length_idx:1L]) + 2
  
  x = x[-(start_idx:length_idx), ]
  x
}

【讨论】:

  • 非常聪明。对于不以空行结尾的 data.frames,这是最快的答案。
  • 感谢@Cole,你的 rcpp 答案(包括这个)总是给我留下深刻印象!
【解决方案3】:

这似乎适用于所有测试用例。
这个想法是使用反向cumsum 过滤掉最后的NA 行。

library(data.table)

remove_empty_row_last_new <- function(d) {
  d[d[,is.na(rev(cumsum(rev(ifelse(rowSums(!is.na(.SD))==0,1,NA)))))]]
}

d <- data.table(a=c(1,NA,3,NA,5,NA,NA),b=c(1,NA,3,4,5,NA,NA))
remove_empty_row_last_new(d)
#>     a  b
#> 1:  1  1
#> 2: NA NA
#> 3:  3  3
#> 4: NA  4
#> 5:  5  5

d2 <- data.table(A=c(1,NA,3,NA,5,1 ,NA),B=c(1,NA,3,4,5,NA,NA))
remove_empty_row_last_new(d2)
#>     A  B
#> 1:  1  1
#> 2: NA NA
#> 3:  3  3
#> 4: NA  4
#> 5:  5  5
#> 6:  1 NA

d3 <- data.table(A=c(1,NA,3,NA,5,NA,NA),B=c(1,NA,3,4,5,1,NA))
remove_empty_row_last_new(d3)
#>     A  B
#> 1:  1  1
#> 2: NA NA
#> 3:  3  3
#> 4: NA  4
#> 5:  5  5
#> 6: NA  1

d4 <- data.table(A=c(1,2,3,NA,5,NA,NA),B=c(1,2,3,4,5,1,7))
remove_empty_row_last_new(d4)
#>     A B
#> 1:  1 1
#> 2:  2 2
#> 3:  3 3
#> 4: NA 4
#> 5:  5 5
#> 6: NA 1
#> 7: NA 7

你必须检查你的真实数据集的性能,但它似乎有点快:

> microbenchmark::microbenchmark(remove_empty_row_last(d),remove_empty_row_last_new(d))
Unit: microseconds
                         expr     min      lq     mean  median       uq      max neval cld
     remove_empty_row_last(d) 384.701 411.800 468.5251 434.251 483.7515 1004.401   100   b
 remove_empty_row_last_new(d) 345.201 359.301 416.1650 382.501 450.5010 1104.401   100  a 

【讨论】:

  • 好主意。这有效并且不会产生之前在私有数据中发现的错误。性能也好一点,在具有 7k 个嵌套表(每个 100 行 x 10 列)的列表上是 20 秒而不是 26 秒。
【解决方案4】:

也许这会足够快?

d[!d[,any(rowSums(is.na(.SD)) == ncol(.SD)) & rleid(rowSums(is.na(.SD)) == ncol(.SD)) == max(rleid(rowSums(is.na(.SD)) == ncol(.SD))),]]
    a  b
1:  1  1
2: NA NA
3:  3  3
4: NA  4
5:  5  5

【讨论】:

  • 应该可以的。
  • 感谢您的帮助。当前版本的答案仍然未通过 2 个测试用例(对于 d2 和 d3,请参阅编辑 2)
  • 感谢您再次更新。对于没有 NA 行的情况仍然失败(参见 d4,来自上面的 edit3)
  • 这是超级快的顺便说一句
猜你喜欢
  • 2017-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-29
  • 1970-01-01
  • 1970-01-01
  • 2019-01-06
  • 2018-10-21
相关资源
最近更新 更多