![](/img/trans.png)
[英]Faster alternative to for loop in R which calls a function with another loop
[英]Faster alternative to `range(which(..))`
設R中的序列為TRUE和FALSE
v = c(F,F,F,F,F,F,T,F,T,T,F,T,T,T,T,T,F,T,F,T,T,F,F,F,T,F,F,F,F,F)
我想獲得第一個和最后一個TRUE的位置。 實現這一目標的一種方法是
range(which(v)) # 7 25
但是這個解決方案相對較慢,因為它必須檢查向量的每個元素以獲得每個TRUE的位置然后遍歷所有位置,在每個位置評估兩個if
語句(我認為)以獲得最大值和最小值。 從頭開始搜索第一個TRUE,從頭開始搜索第一個TRUE並返回那些位置將更具戰略意義。
是否有更快的替代range(which(..))
?
我能想到的最簡單的方法是不涉及搜索整個向量,這將是一個Rcpp解決方案:
library(Rcpp)
cppFunction(
"NumericVector rangeWhich(LogicalVector x) {
NumericVector ret(2, NumericVector::get_na());
int n = x.size();
for (int idx=0; idx < n; ++idx) {
if (x[idx]) {
ret[0] = idx+1; // 1-indexed for R
break;
}
}
if (R_IsNA(ret[0])) return ret; // No true values
for (int idx=n-1; idx >= 0; --idx) {
if (x[idx]) {
ret[1] = idx + 1; // 1-indexed for R
break;
}
}
return ret;
}")
rangeWhich(v)
# [1] 7 25
我們可以使用隨機條目對相當長的向量(長度為100萬)進行基准測試。 我們希望從通過與整個事情不是搜索得到相當大的提高效率which
:
set.seed(144)
bigv <- sample(c(F, T), 1000000, replace=T)
library(microbenchmark)
# range_find from @PierreLafortune
range_find <- function(v) {
i <- 1
while(!v[i]) {
i <- i +1
}
j <- length(v)
while(!v[j]) {
j <- j-1
}
c(i,j)
}
# shortCircuit from @JoshuaUlrich
shortCircuit <- compiler::cmpfun({
function(x) {
first <- 1
while(TRUE) if(x[first]) break else first <- first+1
last <- length(x)
while(TRUE) if(x[last]) break else last <- last-1
c(first, last)
}
})
microbenchmark(rangeWhich(bigv), range_find(bigv), shortCircuit(bigv), range(which(bigv)))
# Unit: microseconds
# expr min lq mean median uq max neval
# rangeWhich(bigv) 1.476 2.4655 9.45051 9.0640 13.7585 46.286 100
# range_find(bigv) 1.445 2.2930 8.06993 7.2055 11.8980 26.893 100
# shortCircuit(bigv) 1.114 1.6920 7.30925 7.0440 10.2210 30.758 100
# range(which(bigv)) 6821.180 9389.1465 13991.84613 10007.9045 16698.2230 58112.490 100
該RCPP解決方案是一個很好的協議更快(超過500倍的速度)比max(which(v))
因為它並不需要通過與全矢量迭代which
。 對於此示例,它與range_find
的range_find和shortCircuit
的shortCircuit具有幾乎相同的運行時間(實際上稍慢)。
使用約書亞的一些最壞情況行為的優秀例子,其中真值是在向量的中間(我正在重復他對所有提議函數的實驗,所以我們可以看到整個圖片),我們看到一個非常不同的情況:
bigv2 <- rep(FALSE, 1e6)
bigv2[5e5-1] <- TRUE
bigv2[5e5+1] <- TRUE
microbenchmark(rangeWhich(bigv2), range_find(bigv2), shortCircuit(bigv2), range(which(bigv2)))
# Unit: microseconds
# expr min lq mean median uq max neval
# rangeWhich(bigv2) 546.206 555.3820 593.1385 575.3790 599.055 979.924 100
# range_find(bigv2) 400057.083 406449.0075 434515.1142 411881.4145 427487.041 697529.163 100
# shortCircuit(bigv2) 74942.612 75663.7835 79095.3795 76761.5325 79703.265 125054.360 100
# range(which(bigv2)) 632.086 679.0955 761.9610 700.1365 746.509 3924.941 100
對於這個向量,循環基R解決方案比原始解決方案慢得多(100-600x慢)並且Rcpp解決方案幾乎比range(which(bigv2))
快range(which(bigv2))
(這是有道理的,因為它們都在整個循環中矢量一次)。
像往常一樣,這需要一個免責聲明 - 你需要編譯你的Rcpp函數,這也需要時間,所以這只有一個好處,如果你有非常大的向量或多次重復此操作。 從您對問題的評論來看,您確實擁有大量的大型向量,因此這對您來說可能是一個不錯的選擇。
match
很快,因為它在找到搜索的值時停止:
c(match(T,v),length(v)-match(T,rev(v))+1)
[1] 7 25
但你必須測試速度。
更新:
range_find <- function(v) {
i <- 1
j <- length(v)
while(!v[i]) {
i <- i+1
}
while(!v[j]) {
j <- j-1
}
c(i,j)
}
基准
v <- rep(v, 5e4)
microbenchmark(
rangeWhich = rangeWhich(v),
range_find = range_find(v),
richwhich = {w <- which(v)
w[c(1L, length(w))]},
match = c(match(T,v),length(v)-match(T,rev(v))+1)
)
Unit: microseconds
expr min lq mean median uq max neval
rangeWhich 1.284 3.2090 16.50914 20.211 26.7875 29.836 100
range_find 9.945 21.4945 32.02652 26.948 34.1660 144.042 100
richwhich 2941.756 3022.5975 3243.02081 3130.227 3247.6405 5403.911 100
match 45696.329 46771.8175 50662.45708 47359.526 48718.6055 131439.661 100
此方法符合您提出的策略:
“從頭開始搜索第一個TRUE,從頭開始搜索第一個TRUE,然后返回那些位置將會更具戰略意義。”
純娛樂。 我能想到的最簡單的方法不涉及搜索整個向量或 Rcpp:P
shortCircuit <- compiler::cmpfun({
function(x) {
first <- 1
while(TRUE) if(x[first]) break else first <- first+1
last <- length(x)
while(TRUE) if(x[last]) break else last <- last-1
c(first, last)
}
})
set.seed(144)
bigv <- sample(c(F, T), 1000000, replace=T)
library(microbenchmark)
microbenchmark(rangeWhich(bigv), shortCircuit(bigv))
# Unit: microseconds
# expr min lq median uq max neval
# rangeWhich(bigv) 1.722 1.8875 1.9995 2.1400 6.850 100
# shortCircuit(bigv) 1.053 1.1905 1.3245 1.4545 9.207 100
哇哦,我贏了! 哦,等等......讓我們在最糟糕的情況下比較兩者。
v <- rep(FALSE, 1e6)
v[5e5-1] <- TRUE
v[5e5+1] <- TRUE
library(microbenchmark)
microbenchmark(rangeWhich(v), shortCircuit(v))
# Unit: microseconds
# expr min lq median uq max neval
# rangeWhich(v) 751.252 884.8805 1109.527 1115.995 1163.135 100
# shortCircuit(v) 60712.586 61004.2760 61396.715 61994.517 72382.216 100
哦不...我輸了,很糟糕。 哦,好吧,至少我很開心。 :)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.