我想更快地做到这一点:
set.seed(100)
pos <- sample(1:100000000, 10000000, replace=F)
df <- data.table(pos, name="arbitrary_string")
query <- sample(1:100000000, 10000, replace=F)
df_list <- lapply(query, function(x) subset(df, pos >= x - 10000 & pos <= x + 10000))
output <- rbindlist(df_list)
所以基本上,我循环遍历位置X的向量并从数据帧中提取每一行,该数据帧在“pos”列中的数字落在定义为[X – 10000,X 10000]的范围之间.我希望在“输出”中多次表示一些行;这是可取的.排序不必与此问题中发布的代码返回的顺序相同.
这个玩具示例基于更大的数据集,我估计在上面编程的单核上运行大约需要10,000小时.因此,对这个问题有一个更快速的解决方案对我来说很有价值.我想要一个纯粹的R解决方案,但我对涉及其他语言的解决方案持开放态度.
最佳答案 在这个解决方案中,我将假设行排序无关紧要(不幸的是,如果您需要原始帖子中的确切行排序,它将无法工作).我建议的方法是:
>使用cumsum确定您需要来自pos变量中每个范围的元素的次数.
>使用对cut函数的单个调用确定df $pos的每个元素落入的范围.
>抓住每一行适当的次数,仅将df子集化一次.
这种方法减少了扫描df并抓取子集的次数,这应该会产生显着的加速.让我们从一个可重现的例子开始:
library(data.table)
set.seed(144)
pos <- sample(1:100000000, 10000000, replace=F)
df <- data.table(pos, name="arbitrary_string")
query <- c(100000, 101000, 200000)
现在,让我们确定每个范围内需要行的范围和次数:
query.cut <- rbind(data.frame(x=query-10000, y=1), data.frame(x=query+10001, y=-1))
query.cut <- query.cut[order(query.cut$x),]
query.cut$y <- cumsum(query.cut$y)
query.cut
# x y
# 1 90000 1
# 2 91000 2
# 4 110001 1
# 5 111001 0
# 3 190000 1
# 6 210001 0
我们将取pos值为90000-90999的行,pos值为91000-110000的行为两次,pos值为110001-111000的行为一次,行的值为190000-210000一次.
要确定元素属于哪个范围,我们可以使用cut函数,在query.cut表中查找相关的复制数:
num.rep <- query.cut$y[as.numeric(cut(df$pos, query.cut$x))]
num.rep[is.na(num.rep)] <- 0
table(num.rep)
# num.rep
# 0 1 2
# 9995969 2137 1894
对于我们当前的小查询,几乎所有行都不会被采用.最后一步是抓住每一行适当的次数.
output <- df[rep(1:nrow(df), times=num.rep),]
即使使用相对较少的查询集(我这里300),我们也可以获得相当稳定的加速:
OP <- function(query) {
df_list <- lapply(query, function(x) subset(df, pos >= x - 10000 & pos <= x + 10000))
rbindlist(df_list)
}
josilber <- function(query) {
query.cut <- rbind(data.frame(x=query-10000, y=1), data.frame(x=query+10001, y=-1))
query.cut <- query.cut[order(query.cut$x),]
query.cut$y <- cumsum(query.cut$y)
query.cut <- query.cut[!duplicated(query.cut$x, fromLast=T),]
num.rep <- query.cut$y[as.numeric(cut(df$pos, query.cut$x))]
num.rep[is.na(num.rep)] <- 0
df[rep(1:nrow(df), times=num.rep),]
}
set.seed(144)
big.query <- sample(df$pos, 300)
system.time(OP(big.query))
# user system elapsed
# 196.693 17.824 217.141
system.time(josilber(big.query))
# user system elapsed
# 3.418 0.124 3.673
随着查询集的大小增加,新方法的优势应该变得更大,因为它仍然只通过df $pos进行一次传递,而原始方法为查询中的每个元素进行一次传递(也就是新方法渐近更快) .