【问题标题】:Efficiently perform row-wise distribution test有效地执行逐行分布测试
【发布时间】:2015-07-03 07:15:57
【问题描述】:

我有一个矩阵,其中每一行都是来自分布的样本。我想使用ks.test 对分布进行滚动比较,并在每种情况下保存测试统计信息。从概念上实现这一点的最简单方法是使用循环:

set.seed(1942)
mt <- rbind(rnorm(5), rnorm(5), rnorm(5), rnorm(5))

results <- matrix(as.numeric(rep(NA, nrow(mt))))

for (i in 2 : nrow(mt)) {

  results[i] <- ks.test(x = mt[i - 1, ], y = mt[i, ])$statistic

}

但是,对于单个示例,我的真实数据有约 400 列和约 300,000 行,而且我有很多示例。所以我希望这很快。 Kolmogorov-Smirnov 测试在数学上并不是那么复杂,所以如果答案是“在Rcpp 中实现它”,我会勉强接受,但我会有点惊讶——计算起来已经非常快了R 中的一对。

我尝试过但无法正常工作的方法:dplyr 使用 rowwise/do/lagzoo 使用 rollapply(这是我用来生成分布的方法),并在其中填充 data.table一个循环(编辑:这个有效,但它仍然很慢)。

【问题讨论】:

  • 您真的在使用KernSmooth 包吗? ks.teststats 包中。
  • 你是对的!我正在使用 KernSmooth,但不适用于此功能——我正在使用它来生成分布。我会编辑。

标签: r optimization rollapply


【解决方案1】:

在 Rcpp 中快速而肮脏的实现

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

double KS(arma::colvec x, arma::colvec y) {
  int n = x.n_rows;
  arma::colvec w = join_cols(x, y);
  arma::uvec z = arma::sort_index(w);
  w.fill(-1); w.elem( find(z <= n-1) ).ones();
  return max(abs(cumsum(w)))/n;
}
// [[Rcpp::export]]
Rcpp::NumericVector K_S(arma::mat mt) {
  int n = mt.n_cols; 
  Rcpp::NumericVector results(n);
  for (int i=1; i<n;i++) {
    arma::colvec x=mt.col(i-1);
    arma::colvec y=mt.col(i);
    results[i] = KS(x, y);
    }
  return results;
}

对于大小为(400, 30000)的矩阵,它在1s内完成。

system.time(K_S(t(mt)))[3]
#elapsed 
#   0.98 

而且结果似乎是准确的。

set.seed(1942)
mt <- matrix(rnorm(400*30000), nrow=30000)
results <- rep(0, nrow(mt))
for (i in 2 : nrow(mt)) {
  results[i] <- ks.test(x = mt[i - 1, ], y = mt[i, ])$statistic
}
result <- K_S(t(mt))
all.equal(result, results)
#[1] TRUE

【讨论】:

  • 这很快。我会测试一下!
  • 这太快了。优秀作品。作为比较,我在大约 2 小时后停止了我的 rollapplyr() 解决方案(当时它几乎生成了所有结果,但仍在运行)。它与ks.test() 的结果是否匹配?
  • 我没有检查准确性,因此标识符为“dirty”。
  • 不完全是,但非常接近:all.equal(results.ks2, results.cpp[2:280007]) [1] "Mean relative difference: 7.642923e-05"。在我的实际数据上,它比 ks.test2 快大约 9 倍。
  • 鉴于性能和可接受的准确性,我认为这可能是您的最佳解决方案,@Ajar。
【解决方案2】:

加速的一个来源是编写一个更小的ks.test 版本,它做的更少。下面的ks.test2ks.test 更严格。例如,它假设您没有缺失值,并且您始终希望获得与双边检验相关的统计数据。

ks.test2 <- function(x, y){

  n.x <- length(x)
  n.y <- length(y)
  w <- c(x, y)
  z <- cumsum(ifelse(order(w) <= n.x, 1/n.x, -1/n.y))

  max(abs(z))

}

验证输出是否与ks.test一致。

set.seed(999)
x <- rnorm(400)
y <- rnorm(400)

ks.test(x, y)$statistic

    D 
0.045

ks.test2(x, y)

[1] 0.045

现在确定较小函数的节省:

library(microbenchmark)

microbenchmark(
  ks.test(x, y),
  ks.test2(x, y)
  )

Unit: microseconds
           expr      min       lq      mean   median        uq      max neval cld
  ks.test(x, y) 1030.238 1070.303 1347.3296 1227.207 1313.8490 6338.918   100   b
 ks.test2(x, y)  709.719  730.048  832.9532  833.861  888.5305 1281.284   100  a 

【讨论】:

  • 我有兴趣查看我的rollapplyr() 解决方案的基准测试,使用此函数代替ks.test()。一旦当前基准测试完成,我将对其进行测试。
  • 我也会对此非常感兴趣!我目前正在自己​​测试其中的一些答案。
【解决方案3】:

我能够使用 ks.test()rollapplyr() 计算成对的 Kruskal-Wallis 统计量。

results <- rollapplyr(data = big,
                      width = 2,
                      FUN = function(x) ks.test(x[1, ], x[2, ])$statistic,
                      by.column = FALSE)

这会得到预期的结果,但对于您这样大小的数据集来说速度很慢。慢慢慢。这可能是因为ks.test() 计算的不仅仅是每次迭代的统计数据;它还获取 p 值并进行大量错误检查。

确实,如果我们像这样模拟一个大型数据集:

big <- NULL
for (i in 1:400) {
    big <- cbind(big, rnorm(300000))
}

rollapplyr() 解决方案耗时较长;我在大约 2 小时后停止了执行,此时它已经计算了几乎所有(但不是所有)结果。

看起来虽然rollapplyr() 可能比for 循环更快,但就性能而言,它可能不是最佳的整体解决方案。

【讨论】:

    【解决方案4】:

    这是一个dplyr 解决方案,它与您的循环获得相同的结果。我怀疑这是否真的比循环快,但也许它可以作为解决方案的第一步。

    require(dplyr)
    mt %>% 
      as.data.frame %>%
      mutate_each(funs(lag)) %>%
      cbind(mt) %>%
      slice(-1) %>%
      rowwise %>%
      do({
        x = unlist(.)
        n <- length(x)
        data.frame(ks = ks.test(head(x, n/2), tail(x, n/2))$statistic)
      }) %>%
      unlist %>%
      c(NA, .) %>%
      matrix
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-27
      • 2022-07-02
      • 1970-01-01
      相关资源
      最近更新 更多