如何使用数字范围对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
答案 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.table
和setkey
确实支持键中的浮点数据,所以让我们开始吧。
在我的慢上网本上:
> 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要快得多。
另见:
答案 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)