我正在使用大型数据集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]
有更快的方法吗? 感谢。
答案 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)]
如果您事先知道可能的唯一值,那么这将是最快的
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之后甚至可以保存甚至获取后面的列值。