提问人:jeanlain 提问时间:5/5/2016 最后编辑:jeanlain 更新时间:5/5/2016 访问量:177
加速简单的 R 代码(矢量化?
Speed up simple R code (vectorize?)
问:
我有两个正整数向量,指定范围的开始和结束“位置”
starts <- sample(10^6,replace = T)
ends <- starts+sample(100:1000,length(starts),replace=T)
因此,它们指定了 1000000 个长度为 100 到 1000 个单位的范围。 现在我想知道一个位置(正整数)被一个范围“覆盖”了多少次。为此,我这样做:
coverage <- integer(max(ends))
for(i in seq(length(starts))) {
coverage[starts[i]:ends[i]] <- coverage[starts[i]:ends[i]] + 1
}
但是由于for循环,它相对较慢。对于数十亿个范围,可能需要很长时间。 我找不到矢量化此代码的方法。我可以拆分工作并使用多个 CPU,但速度提升微乎其微。apply、lapply 和其他元函数不会提高速度(正如预期的那样)。例如
coverage <- tabulate(unlist(Map(':', starts,ends)))
由于“地图”部分,速度也很慢。我担心这也需要更多的内存。
有什么想法吗?
答:
3赞
Gergely Danyi
5/5/2016
#1
您可以保留从任何特定索引开始和结束的范围的计数,然后对这些索引的差值应用累积总和。
- 聚合从每个索引开始的范围数
- 聚合在每个索引之前的一个位置结束的范围数(如果包括在内)
ends
- 计算净变化:
count of starts - count of ends
- 遍历索引并累积汇总净变化。这将给出早于此索引开始且尚未在此索引处结束的数字范围。
“覆盖”数字等于每个指数的累积总和。
我尝试了这种方法,使用稀疏向量来减少内存使用。虽然使用正常向量可能会更快,但不确定。
对于给定示例,它比循环方法快 5.7 倍。sparseVector
library(Matrix)
set.seed(123)
starts <- sample(10^6,replace = T)
ends <- starts+sample(100:1000,length(starts),replace=T)
v.cov <- NULL
fun1 <- function() {
coverage <- integer(max(ends))
for(i in seq(length(starts))) {
coverage[starts[i]:ends[i]] <- coverage[starts[i]:ends[i]] + 1
}
v.cov <<- coverage
}
# Testing "for loop" approach
system.time(fun1())
# user system elapsed
# 21.84 0.00 21.83
v.sum <- NULL
fun2 <- function() {
# 1. Aggregate the number of ranges that start at each index
t.starts <- table(starts)
i.starts <- strtoi(names(t.starts))
x.starts <- as.vector(t.starts)
sv.starts <- sparseVector(x=x.starts, i=i.starts, length=max(ends)+1) # to match length of sv.ends below
# 2. Aggregate the number of ranges that end at one position before each index
t.ends <- table(ends)
i.ends <- strtoi(names(t.ends))+1 # because "ends" are inclusive
x.ends <- as.vector(t.ends)
sv.ends <- sparseVector(x=x.ends, i=i.ends, length=max(ends)+1)
sv.diff <- sv.starts - sv.ends
v.sum <<- cumsum(sv.diff)[1:max(ends)] # drop last element
}
# Testing "cumulative sum" approach
system.time(fun2())
# user system elapsed
# 3.828 0.000 3.823
identical(v.cov, v.sum)
# TRUE
此外,可能有一种比使用构造函数更好的方法来提取 x 和 i,这可能会进一步提高速度。sparseVector
table
strtoi(names(x))
编辑
避免改用 1 列strtoi
sparseMatrix
v.sum.mat <- NULL
fun3 <- function() {
v.ones <- rep(1, length(starts))
m.starts <- sparseMatrix(i=starts, j=v.ones, x=v.ones, dims=c(max(ends)+1,1))
m.ends <- sparseMatrix(i=ends+1, j=v.ones, x=v.ones, dims=c(max(ends)+1,1))
m.diff <- m.starts - m.ends
v.sum.mat <<- cumsum(m.diff[,1])[1:max(ends)]
}
# Testing "cumulative sum" approach using matrix
system.time(fun3())
# user system elapsed
# 0.456 0.028 0.486
identical(v.cov, v.sum.mat)
# TRUE
编辑 2 - 超快,超短
根据 @alexis_laz 的评论,谢谢!
fun4 <- function() {
cumsum(tabulate(starts, max(ends) + 1L) - tabulate(ends + 1L, max(ends) + 1L))[1:max(ends)]
}
system.time(v.sum.tab <- fun4())
# user system elapsed
# 0.040 0.000 0.041
identical(as.integer(v.cov), v.sum.tab)
# TRUE
评论
3赞
alexis_laz
5/5/2016
好主意;非稀疏替代方案是cumsum(tabulate(starts, max(ends) + 1L) - tabulate(ends + 1L, max(ends) + 1L))[1:max(ends)]
0赞
David Arenburg
5/5/2016
这是一个很好的选择。不确定内存效率如何,但肯定很快。只是几点说明。 可能会比 .此外,不需要 .只需将结果分配给函数外部即可。-(max(ends) + 1)
1:max(ends)
<<-
v.sum.mat
0赞
Gergely Danyi
5/5/2016
@DavidArenburg感谢您的评论。我添加了没有 <<- 的 EDIT 2。不确定更正以前发布的代码以省略 <<- 是否是一种好习惯,是吗?我不明白那部分,你能指出那条线吗?- (max(ends) + 1)
0赞
jeanlain
5/5/2016
你的fun4()完全符合我的要求。谢谢!
上一个:在 R 中替换字符串的单独部分
评论
density
Map
:
10^6
set.seed
:
+
Map
starts+ends
Map
:
+
:
: