加快计算每个3元组列的行中位数

时间:2015-10-20 19:08:29

标签: r performance function dataframe median

如果我有这样的数据框:

df = data.frame(matrix(rnorm(100), 5000, 100))

我可以使用以下函数逐行获得三项中位数的每个组合:

median_df = t(apply(df, 1, combn, 3, median))

问题是,此功能需要几个小时才能运行。罪魁祸首是中位数(),运行时间比max()或min()大十倍。

如何加快此功能,可能是通过编写更快版本的median()或以不同方式处理原始数据?

更新

如果我运行上面的代码但仅针对df [,1:10]:

median_df = t(apply(df[,1:10], 1, combn, 3, median))

需要29秒

fastMedian_df = t(apply(df[,1:10], 1, combn, 3, fastMedian))
来自包ccaPP的

需要6.5秒

max_df = t(apply(df[,1:10], 1, combn, 3, max))

需要2.5秒

所以我们看到fastMedian()有了显着的改进。我们还能做得更好吗?

1 个答案:

答案 0 :(得分:14)

加快速度的一种方法是注意三个数字的中位数是它们的总和减去最大值减去它们的最小值。这意味着我们可以通过处理每个三次列(在同一计算中执行所有行的中位数)而不是每行处理一次来对我们的中值计算进行矢量化。

set.seed(144)
# Fully random matrix
df = matrix(rnorm(50000), 5000, 10)
original <- function(df) t(apply(df, 1, combn, 3, median))
josilber <- function(df) {
  combos <- combn(seq_len(ncol(df)), 3)
  apply(combos, 2, function(x) rowSums(df[,x]) - pmin(df[,x[1]], df[,x[2]], df[,x[3]]) - pmax(df[,x[1]], df[,x[2]], df[,x[3]]))
}
system.time(res.josilber <- josilber(df))
#    user  system elapsed 
#   0.117   0.009   0.149 
system.time(res.original <- original(df))
#    user  system elapsed 
#  15.107   1.864  16.960 
all.equal(res.josilber, res.original)
# [1] TRUE

当有10列和5000行时,矢量化产生110倍的加速。不幸的是,我没有一台具有足够内存的机器来存储输出中的808.5百万个数字。

你可以通过实现一个Rcpp函数来进一步提高速度,该函数将矩阵的矢量表示(也就是通过向列中读取矩阵获得的矢量)作为输入以及行数并返回每列的中值。该函数在很大程度上依赖于std::nth_element函数,该函数在您使用中位数的元素数量上渐近线性。 (注意,当我取一个偶数长度向量的中位数时,我不会对中间的两个值求平均值;而是采用两者中的较低值。)

library(Rcpp)
cppFunction(
"NumericVector vectorizedMedian(NumericVector x, int chunkSize) {
 const int n = x.size() / chunkSize;
 std::vector<double> input = Rcpp::as<std::vector<double> >(x);
  NumericVector res(n);
  for (int i=0; i < n; ++i) {
    std::nth_element(input.begin()+i*chunkSize, input.begin()+i*chunkSize+chunkSize/2,
                     input.begin()+(i+1)*chunkSize);
    res[i] = input[i*chunkSize+chunkSize/2];
  }
  return res;
}")

现在我们只调用此功能,而不是使用rowSumspminpmax

josilber.rcpp <- function(df) {
  combos <- combn(seq_len(ncol(df)), 3)
  apply(combos, 2, function(x) vectorizedMedian(as.vector(t(df[,x])), 3))
}
system.time(josilber.rcpp(df))
#    user  system elapsed 
#   0.049   0.008   0.081 
all.equal(josilber(df), josilber.rcpp(df))
# [1] TRUE

总的来说,我们获得了210倍的加速;加速的110倍是从median的非矢量化应用程序切换到矢量化应用程序,而剩余的2倍加速来自rowSumspmin和{{ 1}用于以矢量化方式计算中值到基于Rcpp的方法。