我有一个矩阵,其中每一行都是来自分布的样本。我想使用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测试不是数学上复杂的所有,所以如果答案是&#34;在Rcpp
中实现它,&#34;我不情愿地接受了这一点,但我有点惊讶 - 在R中的一对计算已经非常快。
方法我已经尝试但无法开始工作:dplyr
使用rowwise/do/lag
,zoo
使用rollapply
(这是我用来生成分布),并在循环中填充data.table
(编辑:这个有效,但它仍然很慢)。
答案 0 :(得分:7)
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)
的矩阵,它在1秒内完成。
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
答案 1 :(得分:3)
加速的一个来源是编写一个较小版本的ks.test
。下面的ks.test2
比ks.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
答案 2 :(得分:2)
我能够使用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
循环更快,但就性能而言,它可能不是最佳的整体解决方案。
答案 3 :(得分:1)
这是一个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