使用二进制搜索按范围子集data.table

时间:2014-03-11 08:35:50

标签: r data.table

如何使用数字范围对data.table进行子集化,以便使用二进制搜索?

例如:

require(data.table)
set.seed(1)

x<-runif(10000000,min=0,max=10)
y<-runif(10000000,min=0,max=10)

DF<-data.frame(x,y)
DT<-data.table(x,y)

system.time(DFsub<-DF[DF$x>5 & DF$y<7,])
# user  system elapsed 
# 1.529   0.250   1.821 

#subset DT
system.time(DTsub<-DT[x>5 & y<7])
# user  system elapsed 
#0.716   0.119   0.841 

以上不使用键(矢量扫描),加速不是那么引人注目。使用二进制搜索对data.table的数值范围进行子集化的语法是什么? 我在文档中找不到一个好例子;如果有人可以使用上面的玩具数据表提供一个例子,将会很有帮助。

编辑:这个问题类似,但仍未演示如何按范围进行子集化: data.table: vector scan v binary search with numeric columns - super-slow setkey

2 个答案:

答案 0 :(得分:13)

有趣的问题。首先让我们看一下示例数据:

> print(DT)
                     x        y
       1: 2.607703e-07 5.748127
       2: 8.894131e-07 5.233994
       3: 1.098961e-06 9.834267
       4: 1.548324e-06 2.016585
       5: 1.569279e-06 7.957730
      ---                      
 9999996: 9.999996e+00 9.977782
 9999997: 9.999998e+00 2.666575
 9999998: 9.999999e+00 6.869967
 9999999: 9.999999e+00 1.953145
10000000: 1.000000e+01 4.001616
> length(DT$x)
[1] 10000000
> length(unique(DT$x))
[1] 9988478
> length(DT$y)
[1] 10000000
> length(unique(DT$y))
[1] 9988225
> DT[,.N,by=x][,table(N)]
N
      1       2       3 
9976965   11504       9 
> DT[,.N,by="x,y"][,table(N)]
N
       1 
10000000 
> 

因此,第一列中有近1000万个唯一浮点值:一些大小为2行和3行但大多数为1行的组。一旦包含第二列,就有1000万个大小为1行的唯一组。这是一个非常棘手的问题,因为data.table更多地考虑了分组数据;例如,(id,date),(id1,id2,date,time)等。

但是,data.tablesetkey确实支持键中的浮点数据,所以让我们开始吧。

在我的慢上网本上:

> system.time(setkey(DT,x,y))
   user  system elapsed 
  7.097   0.520   7.650 

> system.time(DT[x>5 & y<7])
   user  system elapsed 
  2.820   0.292   3.122 

因此矢量扫描方法比设置密钥更快(我们甚至还没有使用密钥)。鉴于数据是浮点数并且几乎是独一无二的,那么这并不是太令人惊讶,但我认为setkey是一个非常快的时间来排序1000万完全随机和几乎独特的双打。

比较基数,例如,仅排序x甚至不y

> system.time(base::order(x))
   user  system elapsed 
 72.445   0.292  73.072 

假设这些数据代表您的真实数据,并且您不想只执行一次但几次,那么愿意支付setkey的价格,第一步非常清楚:< / p>

system.time(w <- DT[.(5),which=TRUE,roll=TRUE])
   user  system elapsed 
  0.004   0.000   0.003 
> w
[1] 4999902

但是我们被困住了。像DT[(w+1):nrow(DT)]这样的下一步是丑陋而复制的。我不能想到从这里使用密钥来做y<7部分的正确方法。在其他示例数据中,我们执行类似DT[.(unique(x), 7), which=TRUE, roll=TRUE]的操作,但在这种情况下,数据是如此独特且浮点会变慢。

理想情况下,此任务需要range joins (FR#203)实施。此示例中的语法可能是:

DT[.( c(5,Inf), c(-Inf,7) )]

或为了更容易,DT[x>5 & y<7]可以进行优化,以便在幕后进行。在i中允许连接到相应x列的两列范围可能非常有用并且已经出现了几次。

在我们继续这样的事情之前,需要首先完成v1.9.2中的加速。如果你在v1.8.10中对这些数据进行setkey,你会发现v1.9.2要快得多。

另见:

How to self join a data.table on a condition

Remove a range in data.table

答案 1 :(得分:2)

根据Matt Dowle的要求,我重新运行了代码和时间,以包含与现在包含在data.table包中的between函数的比较。似乎矢量扫描浮点列仍然是最有效的方法。

#OP's example data
require(data.table)
set.seed(1)

x<-runif(10000000,min=0,max=10)
y<-runif(10000000,min=0,max=10)

DF<-data.frame(x,y)
DT<-data.table(x,y)

子集为data.frame

system.time(DFsub<-DF[DF$x>5 & DF$y<7,])
# user  system elapsed 
# 0.506   0.062   0.576 

使用矢量扫描子集为data.table

system.time(DTsub<-DT[x>5 & y<7])
# user  system elapsed 
# 0.213   0.024   0.238 

子集DT与之间(对于x和y)

system.time(DTsub<-DT[between(x ,5, max(x)) & between(y, 0,7), ])
# user  system elapsed 
# 0.242   0.036   0.279  

替代混合矢量扫描和

之间
system.time(DTsub<-DT[x > 5 & between(y, 0,7), ])
# user  system elapsed 
# 0.203   0.017   0.221 

语法之间的替代

system.time(DTsub<-DT[x %between% c(5, max(x)) & y %between% c(0, 7)])
# user  system elapsed 
# 0.227   0.016   0.244  

混合矢量扫描和之间(使用替代语法)

system.time(DTsub<-DT[x>5 & y %between% c(0, 7)])
# user  system elapsed 
# 0.203   0.017   0.221

稍微更彻底的评估

library(microbenchmark)

mbm<-microbenchmark(
  "DFsub"={b1<-DF[DF$x>5 & DF$y<7,]},
  "DTsub1"={b2<-DT[x>5 & y<7]},
  "DTsub2"={b3<-DT[between(x ,5, max(x)) & between(y, 0, 7), ]},
  "DTsub3"={b4<-DT[x > 5 & between(y, 0,7), ]},
  "DTsub4"={b5<-DT[x %between% c(5, max(x)) & y %between% c(0, 7)]},
  "DTsub5"={b5<-DT[x>5 & y %between% c(0, 7)]}
)
mbm
Unit: milliseconds
Unit: milliseconds
# expr      min       lq     mean   median       uq       max neval
# DFsub 527.6842 582.3235 635.8846 622.1641 664.3243 1101.2365   100
# DTsub1 220.5086 245.7509 279.5451 263.5527 296.5736  411.5833   100
# DTsub2 249.2093 283.2025 325.4845 304.2361 333.6894  660.5021   100
# DTsub3 215.5454 243.3255 281.3596 270.1108 300.8462  491.8837   100
# DTsub4 250.9431 282.1896 330.0688 305.2094 352.9604  736.2690   100
# DTsub5 218.5458 238.8931 276.7932 262.6675 293.3524  467.5082   100

library(ggplot2)
autoplot(mbm)

enter image description here