【问题标题】:Fastest way to drop rows with missing values?删除缺少值的行的最快方法?
【发布时间】:2012-12-07 01:26:01
【问题描述】:

我正在处理一个大型数据集x。我想删除x 的一组列中的一个或多个列中缺少的x 行,该组由字符向量varcols 指定。

到目前为止,我已经尝试了以下方法:

require(data.table)
x <- CJ(var1=c(1,0,NA),var2=c(1,0,NA))
x[, textcol := letters[1:nrow(x)]]
varcols <- c("var1","var2")

x[, missing := apply(sapply(.SD,is.na),1,any),.SDcols=varcols]
x <- x[!missing]

有更快的方法吗? 谢谢。

【问题讨论】:

  • 这个怎么样?:x[!apply(x[,.SD],1,function(x) any(is.na(x))),.SDcols=varcols]
  • 2012 年可能还没有。但现在可以使用x[complete.cases(x)]

标签: r data.table


【解决方案1】:

这应该比使用apply 更快:

x[rowSums(is.na(x[, ..varcols])) == 0, ]
#    var1 var2 textcol
# 1:    0    0       e
# 2:    0    1       f
# 3:    1    0       h
# 4:    1    1       i

【讨论】:

  • 没错,它看起来比我的评论要快得多。 +1
【解决方案2】:

这是一个 c++ 解决方案的修订版本,在与 Matthew 的长时间讨论的基础上进行了许多修改(参见下面的 cmets)。我是 c 新手,所以我相信有人仍然可以改进这一点。

library("RcppArmadillo") 之后,您应该能够使用sourceCpp('cleanmat.cpp') 运行包括基准测试在内的整个文件。 c++ 文件包含两个函数。 cleanmat 接受两个参数(X 和列的索引)并返回不含缺失值列的矩阵。 keep 只接受一个参数 X 并返回一个逻辑向量。

关于传递data.table 对象的注意事项:这些函数不接受data.table 作为参数。必须修改函数以将DataFrame 作为参数(参见here

cleanmat.cpp

#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]

using namespace Rcpp;
using namespace arma;


// [[Rcpp::export]]
mat cleanmat(mat X, uvec idx) {
    // remove colums
    X = X.cols(idx - 1);
    // get dimensions
    int n = X.n_rows,k = X.n_cols;
    // create keep vector
    vec keep = ones<vec>(n);
    for (int j = 0; j < k; j++) 
        for (int i = 0; i < n; i++) 
            if (keep[i] && !is_finite(X(i,j))) keep[i] = 0;
    // alternative with view for each row (slightly slower)
    /*vec keep = zeros<vec>(n);
    for (int i = 0; i < n; i++) {
         keep(i) = is_finite(X.row(i));
    }*/  
    return (X.rows(find(keep==1)));
}


// [[Rcpp::export]]
LogicalVector keep(NumericMatrix X) {
    int n = X.nrow(), k = X.ncol();
    // create keep vector
    LogicalVector keep(n, true);
    for (int j = 0; j < k; j++) 
        for (int i = 0; i < n; i++) 
            if (keep[i] && NumericVector::is_na(X(i,j))) keep[i] = false;

    return (keep);
}


/*** R
require("Rcpp")
require("RcppArmadillo")
require("data.table")
require("microbenchmark")

# create matrix
X = matrix(rnorm(1e+07),ncol=100)
X[sample(nrow(X),1000,replace = TRUE),sample(ncol(X),1000,replace = TRUE)]=NA
colnames(X)=paste("c",1:ncol(X),sep="")

idx=sample(ncol(X),90)
microbenchmark(
  X[!apply(X[,idx],1,function(X) any(is.na(X))),idx],
  X[rowSums(is.na(X[,idx])) == 0, idx],
  cleanmat(X,idx),
  X[keep(X[,idx]),idx],
times=3)

# output
# Unit: milliseconds
#                                                     expr       min        lq    median        uq       max
# 1                                       cleanmat(X, idx)  253.2596  259.7738  266.2880  272.0900  277.8921
# 2 X[!apply(X[, idx], 1, function(X) any(is.na(X))), idx] 1729.5200 1805.3255 1881.1309 1913.7580 1946.3851
# 3                                 X[keep(X[, idx]), idx]  360.8254  361.5165  362.2077  371.2061  380.2045
# 4                  X[rowSums(is.na(X[, idx])) == 0, idx]  358.4772  367.5698  376.6625  379.6093  382.5561

*/

【讨论】:

  • 这里有很多需要改进的地方。例如,搜索我的一些 cmets 和有关基准测试的答案。主要的事情是 i)500 次复制可能意味着您计时调用开销(在大数据上运行 3 次通常是一种更好的扩展方式)和 ii)您没有将计时结果包含在表格中(绝对时间通常具有指导意义)。
  • 它是为了跟进我的回答吗?你实际上并没有这么说。我不确定它是否足够不同。
  • 这是计时结果,是的,这是一个跟进,我猜是在修订中丢失了。现在已经说得很清楚了。我将查看您的一些较旧的 cmets,看看如何改进这一点。我刚刚进入rcpp 并将其视为练习,因此欢迎任何改进建议。我还将研究传递data.table 的解决方案,但我认为我不能返回一个。
  • 感谢伟大的锻炼。下一步将是大量增加数据的大小。
  • 如果您可以重新从头开始回答,那就太好了。一个大示例和一个基准测试结果表,其中绝对时间需要几秒钟或几分钟。此外,随机喷洒 NA 将是一个高质量的基准,也许还有无 NA 情况和完全 NA 情况。
【解决方案3】:

为了速度,有大量的varcols,或许看看逐列迭代。像这样的东西(未经测试):

keep = rep(TRUE,nrow(x))
for (j in varcols) keep[is.na(x[[j]])] = FALSE
x[keep]

is.na 的问题在于它创建了一个新的逻辑向量来保存其结果,然后必须由 R 循环遍历该向量以找到 TRUE,以便它知道要设置 FALSE 的哪个保持。然而,在上面的 for 循环中,R 可以为is.na 的结果重用(大小相同的)先前的临时内存,因为它被标记为未使用并且在每次迭代完成后可以重用。 IIUC。

1. is.na(x[, ..varcols])
这没关系,但会创建一个大副本来保存与length(varcols) 一样大的逻辑矩阵。 rowSums 结果上的 ==0 也需要一个新向量。

2. !is.na(var1) &amp; !is.na(var2)
也可以,但是! 将再次创建一个新向量,&amp; 也将如此。 is.na 的每个结果都必须由 R 单独保存,直到表达式完成。在length(varcols) 增加很多或ncol(x) 非常大之前,可能没有区别。

3. CJ(c(0,1),c(0,1))
迄今为止最好的,但不确定随着length(varcols) 的增加,这将如何扩展。 CJ 需要分配新的内存,并在连接开始之前循环使用所有组合填充该内存。

所以,最快的(我猜)应该是这样的 C 版本(伪代码):

keep = rep(TRUE,nrow(x))
for (j=0; j<varcols; j++)
   for (i=0; i<nrow(x); i++)
       if (keep[i] && ISNA(x[i,j])) keep[i] = FALSE;
x[keep]

这需要为keep(在C 或R 中)分配一次,然后C 循环会在看到NA 时遍历更新keep 的列。 C 可以在 Rcpp、RStudio、内联包或老派中完成。重要的是这两个循环是这样循环的,以提高缓存效率。想法是keep[i] &amp;&amp; 部分有助于在某些行中有很多 NA 时加快速度,甚至可以在每行中的第一个 NA 之后完全保存后面的列值。

【讨论】:

    【解决方案4】:

    另外两种方法

    两次矢量扫描

    x[!is.na(var1) & !is.na(var2)]
    

    加入非 NA 值的唯一组合

    如果你提前知道可能的唯一值,这将是最快的

    system.time(x[CJ(c(0,1),c(0,1)), nomatch=0])
    

    一些时间

    x <-data.table(var1 = sample(c(1,0,NA), 1e6, T, prob = c(0.45,0.45,0.1)),
                    var2= sample(c(1,0,NA), 1e6, T, prob = c(0.45,0.45,0.1)),
                    key = c('var1','var2'))
    
    system.time(x[rowSums(is.na(x[, ..varcols])) == 0, ])
       user  system elapsed 
       0.09    0.02    0.11 
    
     system.time(x[!is.na(var1) & !is.na(var2)])
       user  system elapsed 
       0.06    0.02    0.07 
    
    
     system.time(x[CJ(c(0,1),c(0,1)), nomatch=0])
       user  system elapsed 
       0.03    0.00    0.04 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-08
      • 2018-04-24
      • 2010-10-27
      • 2011-03-22
      • 1970-01-01
      • 2010-10-23
      相关资源
      最近更新 更多