删除缺少值的行的最快方法?

时间:2012-12-07 01:26:02

标签: r data.table

我正在使用大型数据集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]

有更快的方法吗? 感谢。

4 个答案:

答案 0 :(得分:9)

这应该比使用apply

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

答案 1 :(得分:3)

这是c ++解决方案的修订版本,基于与Matthew的长时间讨论(见下面的评论)进行了大量修改。我是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

*/

答案 2 :(得分:2)

另外两种方法

两次矢量扫描

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, with = FALSE])) == 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 

答案 3 :(得分:2)

对于速度,有大量varcols,或许看起来按列迭代。像这样(未经测试):

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

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

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

2. !is.na(var1) & !is.na(var2)
好的,但是!会再次创建一个新的向量,&也是如此。 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中)进行一次单独分配,然后只要看到NA,C循环就会遍历更新keep的列。 C可以在Rcpp,RStudio,内联包或旧学校中完成。为了提高缓存效率,这两个循环非常重要。我们的想法是keep[i] &&部分在某些行中存在大量NA时有助于提高速度,以便在每行中第一个NA之后甚至可以保存甚至获取后面的列值。