使用带有附加向量参数的R中的apply

时间:2013-08-23 08:43:31

标签: r vector matrix apply

我有一个大小为10000 x 100的矩阵和一个长度为100的向量。我想应用一个自定义函数百分位,它接受一个向量参数和一个标量参数,矩阵的每一列,使得在迭代 j 时,与百分位数一起使用的参数是矩阵的列 j ,条目 j < / em>向量。有没有办法使用其中一个 apply 函数来执行此操作?

这是我的代码。它会运行,但不会返回正确的结果。

percentile <- function(x, v){
  length(x[x <= v]) / length(x)
}

X <- matrix(runif(10000 * 100), nrow = 10000, ncol = 100)
y <- runif(100)
result <- apply(X, 2, percentile, v = y)

我一直在使用的解决方法是将 y 附加到 X ,然后重写百分位函数,如下所示。

X <- rbind(X, y)
percentile2 <- function(x){
  v <- x[length(x)]
  x <- x[-length(x)]
  length(x[x <= v]) / length(x)
}
result <- apply(X, 2, percentile2)

这段代码确实返回了正确的结果,但我更喜欢更优雅的东西。

2 个答案:

答案 0 :(得分:2)

我认为最简单,最清晰的方法是使用for循环:

result2 <- numeric(ncol(X))
for (i in seq_len(ncol(X))) {
  result2[i] <- sum(X[,i] <= y[i])
}
result2 <- result2 / nrow(X)

我能想到的最快最短的解决方案是:

result1 <- rowSums(t(X) <= y) / nrow(X)

SimonO101在他的回答中解释了这是如何工作的。正如我所说,它很快。然而,缺点是不清楚这里究竟计算了什么,尽管你可以通过将这段代码放在一个名字很好的函数中来解决这个问题。

flodel还建议使用mapply的解决方案,apply可以处理多个向量。但是,要实现这一点,您首先需要将每个列或矩阵放在listdata.frame中:

result3 <- mapply(percentile, as.data.frame(X), y)

速度方面(参见下面的一些基准测试)for-loop并没有那么糟糕,而且比使用apply(在这种情况下至少)更快。使用rowSums和矢量回收的技巧更快,比使用apply的解决方案快10倍。

> X <- matrix(rnorm(10000 * 100), nrow = 10000, ncol = 100)
> y <- runif(100)
> 
> system.time({result1 <- rowSums(t(X) <= y) / nrow(X)})
   user  system elapsed 
  0.020   0.000   0.018 
> 
> system.time({
+   X2 <- rbind(X, y)
+   percentile2 <- function(x){
+     v <- x[length(x)]
+     x <- x[-length(x)]
+     length(x[x <= v]) / length(x)
+   }
+   result <- apply(X2, 2, percentile2)
+ })
   user  system elapsed 
  0.252   0.000   0.249 
> 
> 
> system.time({
+   result2 <- numeric(ncol(X))
+   for (i in seq_len(ncol(X))) {
+     result2[i] <- sum(X[,i] <= y[i])
+   }
+   result2 <- result2 / nrow(X)
+ })
   user  system elapsed 
  0.024   0.000   0.024 
>
> system.time({
+   result3 <- mapply(percentile, as.data.frame(X), y)
+ })
   user  system elapsed 
  0.076   0.000   0.073 
>
> all(result2 == result1)
[1] TRUE
> all(result2 == result)
[1] TRUE
> all(result3 == result)
[1] TRUE

答案 1 :(得分:2)

如果您了解R是向量化的并且知道正确的函数,那么您可以完全避免循环,并在一个相对简单的行中完成整个事情......

 colSums(  t( t( X ) <= y ) ) / nrow( X ) 

通过向量化R将在y的每一列中回收X中的每个元素(默认情况下,它会跨行执行此操作,因此我们使用转置函数t来转换列到行,应用逻辑比较<=,然后再转置回来。

由于TRUEFALSE分别评估为1和0,我们可以使用colSums来有效地获取满足条件的每列中的行数,然后再分别总行数(记住回收规则!)。这是完全相同的结果......

res1 <- apply(X2, 2, percentile2)
res2 <- colSums(  t( t( X ) <= y ) ) / nrow( X )
identical( res1 , res2 )
[1] TRUE

显然,因为这不使用任何R循环,所以批次更快(在这个小矩阵上大约10次)。

更好的方法是使用rowMeans这样( 感谢@flodel ):

     rowMeans(  t(X) <= y  )