通过大数字位置向量非常快速地顺序对数字位置列上的大数据帧(或数据表)进行子集化

时间:2015-04-18 06:41:26

标签: r data.table

我想更快地做到这一点:

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的向量并从数据帧中提取每一行,该数据帧在&#34; pos&#34;落在定义为[X - 10000,X + 10000]的范围之间的列。我希望在&#34;输出&#34 ;;中可以多次表示一些行。这是可取的。排序不必与此问题中发布的代码返回的顺序相同。

这个玩具示例基于更大的数据集,我估计在上面编程的单核上运行需要大约10,000小时。因此,对这个问题有一个更快速的解决方案对我来说很有价值。我喜欢纯R解决方案,但我对涉及其他语言的解决方案持开放态度。

1 个答案:

答案 0 :(得分:2)

在这个解决方案中,我将假设行排序并不重要(不幸的是,如果您需要原始帖子中的确切行排序,它将无法工作)。我建议的方法是:

  1. 使用cumsum确定您需要来自pos变量中每个范围的元素的次数。
  2. 使用对df$pos函数的单次调用,确定cut的每个元素落入的范围。
  3. 抓住每一行适当的次数,仅对df进行一次子集化。
  4. 这种方法减少了扫描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的行一次,行为{{ 1}}值190000-210000一次。

    要确定元素属于哪个范围,我们可以使用pos函数,在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 
    

    即使使用相对较少的查询(在此处为300),我们也可以获得非常可靠的加速:

    output <- df[rep(1:nrow(df), times=num.rep),]
    

    随着查询集的大小增加,新方法的优势应该变得更大,因为它仍然仅通过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中的每个元素进行一次传递{1}}(又称新方法渐近更快)。