在数据框中计算有效观测值(没有NA)的成对数量。

6

假设我有以下这样的数据框:

Df <- data.frame(
    V1 = c(1,2,3,NA,5),
    V2 = c(1,2,NA,4,5),
    V3 = c(NA,2,NA,4,NA)
)

现在我想要统计每个两个变量组合的有效观测值数量。为此,我编写了一个名为 sharedcount 的函数:

sharedcount <- function(x,...){
    nx <- names(x)
    alln <- combn(nx,2)
    out <- apply(alln,2,
      function(y)sum(complete.cases(x[y]))
    )
    data.frame(t(alln),out)
}

这将会输出以下结果:
> sharedcount(Df)
  X1 X2 out
1 V1 V2   3
2 V1 V3   1
3 V2 V3   2

一切都好,但是这个函数对于大型数据框(600个变量和约10000个观测值)需要相当长的时间。我有一种感觉,我可能正在忽视一个更简单的方法,特别是因为cor(..., use='pairwise') 仍然运行得要快得多,尽管它必须做类似的事情:

> require(rbenchmark)    
> benchmark(sharedcount(TestDf),cor(TestDf,use='pairwise'),
+     columns=c('test','elapsed','relative'),
+     replications=1
+ )
                           test elapsed relative
2 cor(TestDf, use = "pairwise")    0.25     1.0
1           sharedcount(TestDf)    1.90     7.6

非常感谢您的提问。


注意:使用Vincent的技巧,我编写了一个函数来返回相同的数据框。请查看下面我的答案中的代码。

3个回答

10
以下稍微快一些:
x <- !is.na(Df)
t(x) %*% x

#       test elapsed relative
#    cor(Df)  12.345 1.000000
# t(x) %*% x  20.736 1.679708

3
很好!使用crossprod(x)代替t(x) %*% x可以稍微提高一下效率。我仍然需要把它放进一个类似例子中的数据框中,但这并不难。 - Joris Meys
由于您的技巧是缩短时间的关键,因此您的回答被接受。我提供了自己的函数作为参考答案。 - Joris Meys

3

我认为Vincent的代码看起来非常优雅,更不用说比我的初级for循环快得多了。但是它似乎需要一个提取步骤,我在下面添加了。这只是使用数据框时apply方法的重负的一个例子。

shrcnt <- function(Df) {Comb <- t(combn(1:ncol(Df),2) )
shrd <- 1:nrow(Comb)
for (i in seq_len(shrd)){ 
     shrd[i] <- sum(complete.cases(Df[,Comb[i,1]], Df[,Comb[i,2]]))}
return(shrd)}

   benchmark(
      shrcnt(Df), sharedcount(Df), {prs <- t(x) %*% x; prs[lower.tri(prs)]}, 
      cor(Df,use='pairwise'),
        columns=c('test','elapsed','relative'),
        replications=100
      )
 #--------------
                       test elapsed relative
3                         {   0.008      1.0
4 cor(Df, use = "pairwise")   0.020      2.5
2           sharedcount(Df)   0.092     11.5
1                shrcnt(Df)   0.036      4.5

还有一个不错的优化技巧。对我来说,带回家的教训是:使用索引而不是名称。 - Joris Meys

2

基于Vincent的巧妙技巧和DWin的lower.tri()建议,我想出了以下函数,它给我与原始函数相同的输出(即数据框),并且运行速度要快得多:

sharedcount2 <- function(x,stringsAsFactors=FALSE,...){
    counts <- crossprod(!is.na(x))
    id <- lower.tri(counts)
    count <- counts[id]
    X1 <- colnames(counts)[col(counts)[id]]
    X2 <- rownames(counts)[row(counts)[id]]
    data.frame(X1,X2,count)
}

请注意使用crossprod(),因为与%*%相比,它可以略微提高性能,但实际上执行的是完全相同的操作。
时间如下:
> benchmark(sharedcount(TestDf),sharedcount2(TestDf),
+           replications=5,
+           columns=c('test','replications','elapsed','relative'))

                  test replications elapsed relative
1  sharedcount(TestDf)            5   10.00 90.90909
2 sharedcount2(TestDf)            5    0.11  1.00000

注意: 我在问题中提供了TestDf,因为我注意到时间取决于数据框的大小而有所不同。如下所示,与使用小型数据框相比,时间增加得更加剧烈。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接