昨天I asked一个非常简单的矢量化问题并得到了一些很好的答案。今天问题有点复杂,我想知道R
是否有通过向量化来加速这个循环的运行时的函数。
循环
for(j in 1:N) {
A[j,1] = B[max(which(C[j]>=D))];
}
我试过
A[,1] = B[max(which(C>=D))];
这大大降低了运行时间...但答案是错误的。有没有正确的"在R?
中这样做的方法EDIT1:
感谢有关数据的问题。我将在这里给出数组的大小:
我们正在循环1:N
A is N x 1
B is length M
C is length N
D is length M
如果速度方面很重要,在本例中为N = 844
,M = 2500
。
EDIT2:
以下是较小的模拟数据集的一些值:
B <- c(1.0000000, 1.0000000, 1.0000000, 0.9565217, 0.9565217, 0.9565217, 0.9565217,
0.9565217, 0.9565217, 0.9565217, 0.8967391, 0.8369565, 0.7771739, 0.7173913,
0.7173913, 0.7173913, 0.7173913, 0.7173913, 0.6277174, 0.6277174, 0.5230978,
0.5230978, 0.3923234, 0.3923234, 0.3923234)
C <- c(0.10607, 0.14705, 0.43607, 0.56587, 0.76203, 0.95657, 1.03524, 1.22956, 1.39074, 2.36452)
D <- c(0.10607, 0.13980, 0.14571, 0.14705, 0.29412, 0.33693, 0.43607, 0.53968, 0.56587,
0.58848, 0.64189, 0.65475, 0.75518, 0.76203, 0.95657, 1.03524, 1.05454, 1.18164,
1.22956, 1.23760, 1.39074, 1.87604, 2.36452, 2.89497, 4.42393)
结果应为:
> A
[,1]
[1,] 1.0000000
[2,] 0.9565217
[3,] 0.9565217
[4,] 0.9565217
[5,] 0.7173913
[6,] 0.7173913
[7,] 0.7173913
[8,] 0.6277174
[9,] 0.5230978
[10,] 0.3923234
答案 0 :(得分:1)
您可以使用outer
。
您的代码:
A1 <- matrix(NA_real_, ncol = 1, nrow = length(C))
for(j in seq_along(C)) {
A1[j,1] = B[max(which(C[j]>=D))];
}
测试C
的元素是否大于/等于D
outer
的元素:
test <- outer(C, D, FUN = ">=")
# [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14] [,15] [,16] [,17] [,18] [,19] [,20] [,21] [,22] [,23] [,24] [,25]
# [1,] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [2,] TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [3,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [4,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [5,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [6,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [7,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# [8,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
# [9,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE
#[10,] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE FALSE
请注意,这可能会为大型矢量使用大量内存。
然后找到每行中的最后一个TRUE
值:
ind <- max.col(test, ties.method = "last") * (rowSums(test) > 0)
rowSums(test) > 0
测试是否存在任何TRUE
值,否则会使ind
0的对应元素生效。在这种情况下你不想要发生什么。 (在子集化过程中会忽略0索引。可能在您的最终结果中需要NA
?)
现在子集:
A2 <- as.matrix(B[ind], ncol = 1)
# [,1]
# [1,] 1.0000000
# [2,] 0.9565217
# [3,] 0.9565217
# [4,] 0.9565217
# [5,] 0.7173913
# [6,] 0.7173913
# [7,] 0.7173913
# [8,] 0.6277174
# [9,] 0.5230978
#[10,] 0.3923234
结果是否相同?
identical(A2, A1)
#[1] TRUE
数据(请在下次使用dput
提供示例数据):
B <- c(1.0000000, 1.0000000, 1.0000000, 0.9565217, 0.9565217, 0.9565217, 0.9565217,
0.9565217, 0.9565217, 0.9565217, 0.8967391, 0.8369565, 0.7771739, 0.7173913,
0.7173913, 0.7173913, 0.7173913, 0.7173913, 0.6277174, 0.6277174, 0.5230978,
0.5230978, 0.3923234, 0.3923234, 0.3923234)
C <- c(0.10607, 0.14705, 0.43607, 0.56587, 0.76203, 0.95657, 1.03524, 1.22956, 1.39074,
2.36452)
D <- c(0.10607, 0.13980, 0.14571, 0.14705, 0.29412, 0.33693, 0.43607, 0.53968, 0.56587,
0.58848, 0.64189, 0.65475, 0.75518, 0.76203, 0.95657, 1.03524, 1.05454, 1.18164,
1.22956, 1.23760, 1.39074, 1.87604, 2.36452, 2.89497, 4.42393)
答案 1 :(得分:1)
在看到@Roland的回答后,我想我更清楚你在问什么。要仔细检查:您想要将C
(单独)的每个值与D
的所有值进行比较,并获得D
的最大索引(让我们称之为k
)其值小于C[j]
。然后,您可以使用它将B
的相应值分配给A
,从而A[j]=B[k]
。这是对的吗?
我没有关于如何矢量化你想做什么的答案,但确实有一些关于如何加快它的建议。在此之前,让我问一下它是否真的值得付出努力。对于你提到的更大的例子(N~1000,M~2500),你的循环仍然可以在我的笔记本电脑上运行一秒钟。除非这个计算在另一个循环中多次完成,否则看似不必要的优化......
另外,就像@Roland所指出的那样,如果C
中的值小于D
中的所有值,则不清楚会发生什么。这些功能(包括你的原始循环)如果发生这种情况将不起作用,并且需要稍微调整一下。
无论如何,这些是我的建议:
首先,让我将循环包装到函数中以方便使用。
f_loop <- function(B, C, D){
N <- length(C)
A <- matrix(0, ncol=1, nrow=N)
for(j in 1:N) {
A[j,1] = B[max(which(C[j]>=D))]
}
return(A)
}
如果您希望它看起来更像“R-like”,您可以使用*apply
函数之一替换循环。在这种情况下,它还比循环运行略快。
vapply(C, function(x) B[max(which(x>=D))], 0)
## Wrapped into a function for easier reference
f_vapply <- function(B, C, D){
vapply(C, function(x) B[max(which(x>=D))], 0)
}
我的另一个建议是丑陋的(而不是真正的“R-like”),但可以帮助加快速度(如果这是最终目标)。我使用inline
包来创建循环的编译版本(请注意,根据您的操作系统和R设置,您可能需要下载其他工具或包以便能够编译代码)。
## Translate loop into Fortran
loopcode <-
" integer i, j, k
do i = 1, n
k = 0
do j = 1, m
if (C(i) >= D(j)) k = j
end do
A(i) = B(k)
end do
"
## Compile into function
library(inline)
loopfun <- cfunction(sig = signature(A="numeric", B="numeric", C="numeric", D="numeric", n="integer", m="integer"), dim=c("(n)", "(m)", "(n)", "(m)", "", ""), loopcode, language="F95")
## Wrap into function for easier reference
f_compiled <- function(B, C, D){
A <- C
n <- length(A)
m <- length(B)
out <- loopfun(A, B, C, D, n, m)
return(as.matrix(out$A, ncol=1))
}
让我们检查结果是否匹配:
cbind(A, f_loop(B, C, D), f_vapply(B, C, D), f_compiled(B, C, D))
## [,1] [,2] [,3] [,4]
## [1,] 1.0000000 1.0000000 1.0000000 1.0000000
## [2,] 0.9565217 0.9565217 0.9565217 0.9565217
## [3,] 0.9565217 0.9565217 0.9565217 0.9565217
## [4,] 0.9565217 0.9565217 0.9565217 0.9565217
## [5,] 0.7173913 0.7173913 0.7173913 0.7173913
## [6,] 0.7173913 0.7173913 0.7173913 0.7173913
## [7,] 0.7173913 0.7173913 0.7173913 0.7173913
## [8,] 0.6277174 0.6277174 0.6277174 0.6277174
## [9,] 0.5230978 0.5230978 0.5230978 0.5230978
## [10,] 0.3923234 0.3923234 0.3923234 0.3923234
检查速度:
microbenchmark(f_loop(B, C, D), f_vapply(B, C, D), f_compiled(B, C, D))
## Unit: microseconds
## expr min lq mean median uq max neval cld
## f_loop(B, C, D) 52.804 54.8075 57.34588 56.5420 58.4615 83.843 100 c
## f_vapply(B, C, D) 38.677 41.5055 43.21231 42.8825 44.1525 65.355 100 b
## f_compiled(B, C, D) 17.095 18.2775 20.55372 20.1770 21.4710 66.407 100 a
我们也可以尝试使用与您提到的较大尺寸相似的矢量(注意结果的单位变化):
## Make the vector larger for benchmark
B <- rep(B, 100) # M = 2500
C <- rep(C, 100) # N = 1000
D <- rep(D, 100) # M = 2500
microbenchmark(f_loop(B, C, D), f_vapply(B, C, D), f_compiled(B, C, D))
## Unit: milliseconds
## expr min lq mean median uq max neval cld
## f_loop(B, C, D) 24.380069 24.85061 25.99855 25.839282 25.952433 62.75721 100 b
## f_vapply(B, C, D) 23.543749 24.18427 25.34881 25.015859 25.179924 62.60746 100 b
## f_compiled(B, C, D) 1.976611 2.01403 2.06750 2.032864 2.057594 3.13658 100 a
<强> 编辑: 强>
我意识到如果你总是想要D
所拥有的C[j]>=D
的最大索引,当然从数组的末尾开始循环D
会更有意义。 ,并在找到第一个实例后立即退出(而不是遍历整个数组)。
这是对我上面编写的Fortran代码的一个小调整,利用了这一点。
loopcode <-
" integer i, j, k
do j = 1, n
k = 0
do i = m, 1, -1
if (C(j) >= D(i)) then
k = i
exit
end if
end do
A(j) = B(k)
end do
"
我不会将它包含在基准测试中,因为它将更加依赖于实际的数据点。但很明显,最坏情况的行为与前一个循环相同(例如,如果感兴趣的索引发生在开头,D
完全循环)并且最佳情况行为几乎完全消除了循环{{ 1}}(例如,如果条件保持在数组的末尾)。
答案 2 :(得分:1)
如果您渴望立即得到答案,请跳至结论。我为您提供单行R代码,效率最高。有关详细信息/想法,请阅读以下内容。
当OP询问以下循环的矢量化时:
for(j in 1:N) A[j, 1] <- B[max(which(C[j] >= D))]
我要做的第一件事就是把它变成一个很好的版本:
## stage 1: index computation (need vectorization)
id <- integer(N); for(j in 1:N) id[j] <- max(which(D <= C[j]))
## stage 2: shuffling (readily vectorized)
A[, 1] <- B[id]
现在我们看到只有阶段1需要进行矢量化。这个阶段基本上做了以下几点:
D[1] D[2] D[3] ... D[M]
C[1]
C[2]
C[3]
.
.
C[N]
对于每一行j
,找到k(j)
中的截止位置D
,D[k(j) + 1], D[k(j) + 2], ..., D[M] > C[j]
。
实际上有一种有效的算法可以做到这一点:
C
到CC
(记录排序索引iC
,以便C[iC] == CC
)D
到DD
(记录排序索引iD
,以便D[iD] == DD
)通过排序,我们大大降低了工作的复杂性。
如果数据未排序,那么我们必须显式扫描所有元素:D[1], D[2], ..., D[M]
以便决定k(j)
。因此,每行的费用为O(M)
,因此费用总计为O(MN)
。
但是,如果数据已排序,那么我们只需要执行以下操作:
j = 1: search `D[1], D[2], ..., D[k(1)]`, till `D[k(1) + 1] > C[1]`;
j = 2: search `D[k(1) + 1], D[k(1)+2], ..., D[k(2)]`, till `D[k(2) + 1] > C[2]`;
...
对于每一行,仅应用部分搜索,整体复杂度仅为O(M)
,即D
向量仅触摸一次,而不是{{1在简单的实现中。因此,排序后,算法的速度提高了N
倍!对于较大的N
和M
,这是一个巨大的差异!正如您在其他评论中所说的那样,此代码将被调用数百万次,然后我们肯定需要N
算法而不是O(M)
算法。
另请注意,此方法的内存成本为O(MN)
,即我们只将两个向量连接在一起,而不是将其扩展为O(M + N)
矩阵。因此,这种存储节省也很明显。
事实上,我们可以通过将此比较问题转换为匹配问题来进一步采取,这更容易在R中进行矢量化。
M-by-N
要了解这项工作的原因,请考虑另一种表述方式:
## version 1:
CCDD <- c(CC, DD) ## combine CC and DD
CCDD <- sort(CCDD, decreasing = TRUE) ## sort into descending order
id0 <- M + N - match(CC, CCDD) + 1
id <- id0 - 1:N
现在,下图说明了## version 2:
CCDD <- c(CC, DD) ## combine CC and DD
CCDD <- sort(CCDD) ## sort into ascending order
id0 <- match(CC, CCDD)
id <- id0 - 1:N
向量的外观:
CCDD
因此,CCDD: D[1] D[2] C[1] D[3] C[2] C[3] D[4] D[5] D[6] C[4] .....
id0: 3 5 6 10 .....
id : 2 3 3 6 .....
给出:CCDD[id]
,恰好是不超过D[2], D[3], D[3], D[6], ....
的最后一个元素,因此,C[1], C[2]. C[3], C[4], ....
只是我们想要的索引!
然后人们可能想知道为什么我建议做&#34;版本1&#34;而不是&#34;版本2&#34;。因为当id
,&#34;版本2&#34;将给出错误的结果,因为CCDD
将采用匹配的第一个元素,忽略以后的匹配。因此,不是从左到右匹配(在升序索引中),我们必须从右到左匹配(在降序索引中)。
考虑到这一点,我开始查看OP的数据。 现在令人惊讶的是,OP的数据已经排序:
match()
此外,OP已合并C <- c(0.10607, 0.14705, 0.43607, 0.56587, 0.76203, 0.95657, 1.03524, 1.22956, 1.39074, 2.36452)
D <- c(0.10607, 0.13980, 0.14571, 0.14705, 0.29412, 0.33693, 0.43607, 0.53968, 0.56587, 0.58848,
0.64189, 0.65475, 0.75518, 0.76203, 0.95657, 1.03524, 1.05454, 1.18164, 1.22956, 1.23760,
1.39074, 1.87604, 2.36452, 2.89497, 4.42393)
M <- length(D); N <- length(C)
is.unsorted(C)
# FALSE
is.unsorted(D)
#FALSE
和C
:
D
似乎OP和我在效率方面有相同的想法。据推测OP曾经有一个较短的all(C %in% D)
# TRUE
向量,而他提供的D
向量确实是我上面提到的D
向量!
现在,在这种情况下,事情很简单:我们只做一个单行:
CCDD
注意我放id <- M - match(C, rev(D)) + 1
因为OP按升序排序rev()
所以我需要反转它。这一行可能看起来与版本1&#34;非常不同。代码,但没有错。请注意,此处使用的D
实际上是&{34;版本1&#34;中的D
。代码,CCDD
这里真的是M
。此外,由于我们对M + N
的定义不同,因此无需从1:N
中减去id
。
现在,简单的R-loop给出:
D
好吧,我们的单行矢量化代码给出了:
id <- integer(N); for(j in 1:N) id[j] <- max(which(D <= C[j]))
id
# [1] 1 4 7 9 14 15 16 19 21 23
完美匹配,因此我们正在做正确的事。
所以,Laurbert,这就是你想要的答案:
id <- M - match(C, rev(D)) + 1
id
# [1] 1 4 7 9 14 15 16 19 21 23