我有几种算法取决于确定元素是否存在于向量中的效率。在我看来,%in%
(相当于is.element()
)应该是最有效的,因为它只返回一个布尔值。在测试了几种方法之后,令我惊讶的是,这些方法到目前为止效率最低。以下是我的分析(随着矢量大小的增加,结果变得更糟):
EfficiencyTest <- function(n, Lim) {
samp1 <- sample(Lim, n)
set1 <- sample(Lim, Lim)
print(system.time(for(i in 1:n) {which(set1==samp1[i])}))
print(system.time(for(i in 1:n) {samp1[i] %in% set1}))
print(system.time(for(i in 1:n) {is.element(samp1[i], set1)}))
print(system.time(for(i in 1:n) {match(samp1[i], set1)}))
a <- system.time(set1 <- sort(set1))
b <- system.time(for (i in 1:n) {BinVecCheck(samp1[i], set1)})
print(a+b)
}
> EfficiencyTest(10^3, 10^5)
user system elapsed
0.29 0.11 0.40
user system elapsed
19.79 0.39 20.21
user system elapsed
19.89 0.53 20.44
user system elapsed
20.04 0.28 20.33
user system elapsed
0.02 0.00 0.03
BinVecCheck
是我写的二进制搜索算法,返回TRUE
/ FALSE
。请注意,我包含使用最终方法对向量进行排序所需的时间。这是二进制搜索的代码:
BinVecCheck <- function(tar, vec) {
if (tar==vec[1] || tar==vec[length(vec)]) {return(TRUE)}
size <- length(vec)
size2 <- trunc(size/2)
dist <- (tar - vec[size2])
if (dist > 0) {
lower <- size2 - 1L
upper <- size
} else {
lower <- 1L
upper <- size2 + 1L
}
while (size2 > 1 && !(dist==0)) {
size2 <- trunc((upper-lower)/2)
temp <- lower+size2
dist <- (tar - vec[temp])
if (dist > 0) {
lower <- temp-1L
} else {
upper <- temp+1L
}
}
if (dist==0) {return(TRUE)} else {return(FALSE)}
}
平台信息:
> sessionInfo()
R version 3.2.1 (2015-06-18)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1
是否有更有效的方法来确定元素是否存在于R中的向量中?例如,是否有与Python set函数等效的R函数,这大大改进了这种方法?此外,为什么%in%
等等,即使与提供更多信息的which
函数相比也是如此低效(不仅它确定了存在,而且它还给出了所有真实账户的索引) ?
答案 0 :(得分:8)
我的测试并未证实你的所有主张,但似乎(?)是由于跨平台的差异(这使得问题更加神秘,并且可能值得考虑{{1}虽然可能不是因为下面的r-devel@r-project.org
解决方案无论如何都占主导地位......)
fastmatch
这表示 n <- 10^3; Lim <- 10^5
set.seed(101)
samp1 <- sample(Lim,n)
set1 <- sample(Lim,Lim)
library("rbenchmark")
library("fastmatch")
`%fin%` <- function(x, table) {
stopifnot(require(fastmatch))
fmatch(x, table, nomatch = 0L) > 0L
}
benchmark(which=sapply(samp1,function(x) which(set1==x)),
infun=sapply(samp1,function(x) x %in% set1),
fin= sapply(samp1,function(x) x %fin% set1),
brc= sapply(samp1,BinVecCheck,vec=sort(set1)),
replications=20,
columns = c("test", "replications", "elapsed", "relative"))
## test replications elapsed relative
## 4 brc 20 0.871 2.329
## 3 fin 20 0.374 1.000
## 2 infun 20 6.480 17.326
## 1 which 20 10.634 28.433
的速度约为%in%
的两倍 - 您的which
函数的效果提高了7倍,但来自here的BinVecCheck
解决方案得到了fastmatch
另一个因素是2.我不知道专门的Rcpp实现是否可以做得更好......
事实上,即使运行代码,我也会得到不同的答案:
## user system elapsed (which)
## 0.488 0.096 0.586
## user system elapsed (%in%)
## 0.184 0.132 0.315
## user system elapsed (is.element)
## 0.188 0.124 0.313
## user system elapsed (match)
## 0.148 0.164 0.312
## user system elapsed (BinVecCheck)
## 0.048 0.008 0.055
更新:on r-devel
Peter Dalgaard通过指向R NEWS条目解释平台差异(这是R版本差异,而不是操作系统差异):< / p>
感谢Haverty的PR#16491,当match(x, table)
的长度为1且不可比的不变时,
x
更快,有时甚至是一个数量级。
sessionInfo()
## R Under development (unstable) (2015-10-23 r69563)
## Platform: i686-pc-linux-gnu (32-bit)
## Running under: Ubuntu precise (12.04.5 LTS)
答案 1 :(得分:3)
%in%
只是匹配的糖,定义为:
"%in%" <- function(x, table) match(x, table, nomatch = 0) > 0
match
和which
都是.Internal()
调用的低级(编译C)函数。您可以使用pryr包实际查看源代码:
install.packages("pryr")
library(pryr)
pryr::show_c_source(.Internal(which(x)))
pryr::show_c_source(.Internal(match(x, table, nomatch, incomparables)))
您将被指向this page以及this page匹配。
which
不会执行match
执行的任何投射,检查等操作。这可能解释了它在测试中的更高性能(但我自己没有测试过你的结果)。
答案 2 :(得分:2)
经过多天研究这个主题,我发现确定存在的最快方法取决于被测元素的数量。根据@ ben-bolker给出的答案,%fin%
看起来像是明确的赢家。当被测元素的数量(samp1
中的所有元素)与向量(set1
)的大小相比较时,情况似乎就是这种情况。在我们进一步讨论之前,让我们看看上面的二进制搜索算法。
首先,原始算法中的第一行评估为TRUE
的可能性极低,为什么每次检查它?
if (tar==vec[1] || tar==vec[size]) {return(TRUE)}
相反,我将此语句放在最后的else
语句中。
其次,每次确定向量的大小是多余的,特别是当我提前知道测试向量的长度(set1
)时。所以,我添加size
作为算法的参数,并简单地将其作为变量传递。以下是修改后的二进制搜索代码。
ModifiedBinVecCheck <- function(tar, vec, size) {
size2 <- trunc(size/2)
dist <- (tar - vec[size2])
if (dist > 0) {
lower <- size2 - 1L
upper <- size
} else {
lower <- 1L
upper <- size2 + 1L
}
while (size2 > 1 && !(dist==0)) {
size2 <- trunc((upper-lower)/2)
temp <- lower+size2
dist <- (tar - vec[temp])
if (dist > 0) {
lower <- temp-1L
} else {
upper <- temp+1L
}
}
if (dist==0) {
return(TRUE)
} else {
if (tar==vec[1] || tar==vec[size]) {return(TRUE)} else {return(FALSE)}
}
}
众所周知,为了使用二进制搜索,必须对向量进行排序,这需要花费时间。 sort的默认排序方法是shell
,可用于所有数据类型,但缺点(一般来说)比quick
方法慢quick
只能用于双打或整数)。使用quick
作为我的排序方法(因为我们处理数字)与修改后的二进制搜索相结合,我们得到了显着的性能提升(根据具体情况从旧的二进制搜索)。应该注意的是,fmatch仅在数据类型为整数,实数或字符时才会在match上进行改进。
现在,让我们看看一些不同大小 n 的测试用例。
案例1 (n = 10^3
&amp; Lim = 10^6
,所以n to Lim ratio is 1:1000
):
n <- 10^3; Lim <- 10^6
set.seed(101)
samp1 <- sample(Lim,n)
set1 <- sample(Lim,Lim)
benchmark(fin= sapply(samp1,function(x) x %fin% set1),
brc= sapply(samp1,ModifiedBinVecCheck,vec=sort(set1, method = "quick"),size=Lim),
oldbrc= sapply(samp1,BinVecCheck,vec=sort(set1)),
replications=10,
columns = c("test", "replications", "elapsed", "relative"))
test replications elapsed relative
2 brc 10 0.97 4.217
1 fin 10 0.23 1.000
3 oldbrc 10 1.45 6.304
案例2 (n = 10^4
&amp; Lim = 10^6
,所以n to Lim ratio is 1:100
):
n <- 10^4; Lim <- 10^6
set.seed(101)
samp1 <- sample(Lim,n)
set1 <- sample(Lim,Lim)
benchmark(fin= sapply(samp1,function(x) x %fin% set1),
brc= sapply(samp1,ModifiedBinVecCheck,vec=sort(set1, method = "quick"),size=Lim),
oldbrc= sapply(samp1,BinVecCheck,vec=sort(set1)),
replications=10,
columns = c("test", "replications", "elapsed", "relative"))
test replications elapsed relative
2 brc 10 2.08 1.000
1 fin 10 2.16 1.038
3 oldbrc 10 2.57 1.236
案例3:(n = 10^5
&amp; Lim = 10^6
,所以n to Lim ratio is 1:10
):
n <- 10^5; Lim <- 10^6
set.seed(101)
samp1 <- sample(Lim,n)
set1 <- sample(Lim,Lim)
benchmark(fin= sapply(samp1,function(x) x %fin% set1),
brc= sapply(samp1,ModifiedBinVecCheck,vec=sort(set1, method = "quick"),size=Lim),
oldbrc= sapply(samp1,BinVecCheck,vec=sort(set1)),
replications=10,
columns = c("test", "replications", "elapsed", "relative"))
test replications elapsed relative
2 brc 10 13.13 1.000
1 fin 10 21.23 1.617
3 oldbrc 10 13.93 1.061
案例4:(n = 10^6
&amp; Lim = 10^6
,所以n to Lim ratio is 1:1
):
n <- 10^6; Lim <- 10^6
set.seed(101)
samp1 <- sample(Lim,n)
set1 <- sample(Lim,Lim)
benchmark(fin= sapply(samp1,function(x) x %fin% set1),
brc= sapply(samp1,ModifiedBinVecCheck,vec=sort(set1, method = "quick"),size=Lim),
oldbrc= sapply(samp1,BinVecCheck,vec=sort(set1)),
replications=10,
columns = c("test", "replications", "elapsed", "relative"))
test replications elapsed relative
2 brc 10 124.61 1.000
1 fin 10 214.20 1.719
3 oldbrc 10 127.39 1.022
正如您所看到的那样, n 相对于 Lim 变得更大,二元搜索的效率(两者都是他们)开始占主导地位。在案例1中,%fin%
比修改后的二进制搜索快4倍,在Case2中几乎没有差异,在案例3中我们真正开始看到二进制搜索优势,而在案例4中,修改后的二进制搜索是几乎是%fin%
的两倍。
因此,要回答“哪种方法更快?”的问题,这取决于。 %fin%
对于测试向量的少量元素检查更快,ModifiedBinVecCheck
对于测试向量的大量元素检查更快。
答案 3 :(得分:1)
any( x == "foo" )
没有NA,那么 x
应该足够快。如果您可能有NAs,则R 3.3的“%in%”速度会有所帮助。
对于二进制搜索,请在滚动自己之前查看findInterval
。除非x
是常量和排序的,否则这听起来不像二进制搜索的工作。