R中的data.table - 使用多个键的多个过滤器 - 二进制搜索

pal*_*czy 15 r data.table

我不明白我如何根据多个键进行过滤data.table.采用内置mtcars数据集.

DT <- data.table(mtcars)
setkey(DT, am, gear, carb)
Run Code Online (Sandbox Code Playgroud)

小插图之后,我知道如果我想要过滤相应的am == 1 & gear == 4 & carb == 4,我可以说

> DT[.(1, 4, 4)]
   mpg cyl disp  hp drat    wt  qsec vs am gear carb
1:  21   6  160 110  3.9 2.620 16.46  0  1    4    4
2:  21   6  160 110  3.9 2.875 17.02  0  1    4    4
Run Code Online (Sandbox Code Playgroud)

它给出了正确的结果.此外,如果我想拥有am == 1 & gear == 4 & (carb == 4 | carb == 2),这也有效

> DT[.(1, 4, c(4, 2))]
    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
1: 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
2: 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
3: 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
4: 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2
Run Code Online (Sandbox Code Playgroud)

但是,当我想拥有时am == 1 & (gear == 3 | gear == 4) & (carb == 4 | carb == 2),似乎有道理

> DT[.(1, c(3, 4), c(4, 2))]
    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
1:   NA  NA    NA  NA   NA    NA    NA NA  1    3    4
2: 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
3: 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2
Run Code Online (Sandbox Code Playgroud)

失败.能否请您解释一下这里的正确方法是什么?

Dea*_*gor 14

您没有从查询中得到错误的原因是data.table会在值为其他值的倍数时重用值.换句话说,因为1for am可以使用2次,所以它不会告诉你.如果您要进行查询,其中允许值的数量不是彼此的倍数,那么它会给您一个警告.例如

DT[.(c(1,0),c(5,4,3),c(8,6,4))]
Run Code Online (Sandbox Code Playgroud)

会给你一个警告抱怨1项的剩余部分,你输入时会看到同样的错误data.table(c(1,0),c(5,4,3),c(8,6,4)).每当合并X[Y],双方XY应被视为data.tables.

如果你改用CJ,

DT[CJ(c(1,0),c(5,4,3),c(8,6,4))]
Run Code Online (Sandbox Code Playgroud)

然后它将为您和data.table创建所有值的每个组合将给出您期望的结果.

从小插图(加粗是我的):

这里发生了什么事?再读一遍.为第二个键列"MIA"提供的值必须在第一个键列原点提供的匹配行上的dest键列中找到匹配的值.我们之前不能跳过关键列的值.因此,我们提供关键列来源的所有独特值."MIA"会自动回收以适应3的独特(原点)长度.

为了完整起见,矢量扫描语法无需使用即可工作 CJ

DT[am == 1 & gear == 4 & carb == 4]
Run Code Online (Sandbox Code Playgroud)

要么

DT[am == 1 & (gear == 3 | gear == 4) & (carb == 4 | carb == 2)]
Run Code Online (Sandbox Code Playgroud)

你怎么知道你是否需要二元搜索?如果子集的速度无法忍受,那么您需要二进制搜索.例如,我有一个48M行data.table我正在玩,二元搜索和矢量之间的差异相对于彼此是惊人的.具体而言,矢量扫描在经过的时间内需要1.490秒,但二进制搜索仅需要0.001秒.当然,这假设我已经键入了data.table.如果我包括设置密钥所需的时间,则设置密钥和执行子集的组合是1.628.所以你必须挑选毒药


Uwe*_*Uwe 10

这个问题现在已经成为一个重复问题的目标,我觉得现有的答案可以改进,以帮助新手data.table用户.

1. DT[.()]和之间有什么区别 DT[CJ()]

根据?data.table,.()是一个别名list()和一个list提供的参数i 被转换为data.table内部.所以,DT[.(1, c(3, 4), c(2, 4))]就相当于DT[data.table(1, c(3, 4), c(2, 4))]

data.table(1, c(3, 4), c(2, 4))
#   V1 V2 V3
#1:  1  3  2
#2:  1  4  4
Run Code Online (Sandbox Code Playgroud)

所述data.table由两行,其是最长的矢量的长度的.1被回收.

这与交叉连接不同,交叉连接创建所提供矢量的所有组合.

CJ(1, c(3, 4), c(2, 4))
   V1 V2 V3
#1:  1  3  2
#2:  1  3  4
#3:  1  4  2
#4:  1  4  4
Run Code Online (Sandbox Code Playgroud)

请注意,setDT(expand.grid())会产生相同的结果.

这解释了为什么OP得到两个不同的结果:

DT[.(1, c(3, 4), c(2, 4))]
#   mpg cyl disp  hp drat    wt  qsec vs am gear carb
#1:  NA  NA   NA  NA   NA    NA    NA NA  1    3    2
#2:  21   6  160 110  3.9 2.620 16.46  0  1    4    4
#3:  21   6  160 110  3.9 2.875 17.02  0  1    4    4

DT[CJ(1, c(3, 4), c(2, 4))]
#    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#1:   NA  NA    NA  NA   NA    NA    NA NA  1    3    2
#2:   NA  NA    NA  NA   NA    NA    NA NA  1    3    4
#3: 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
#4: 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2
#5: 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#6: 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Run Code Online (Sandbox Code Playgroud)

请注意,该参数nomatch = 0将删除不匹配的行,即包含的行NA.

2.使用 %in%

除了CJ()和之外am == 1 & (gear == 3 | gear == 4) & (carb == 2 | carb == 4),还有第三个使用值匹配的等效选项:

DT[am == 1 & gear %in%  c(3, 4) & carb %in% c(2, 4)]
#    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#1: 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
#2: 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2
#3: 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#4: 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Run Code Online (Sandbox Code Playgroud)

请注意,CJ()需要data.table键入,而其他两个变体也适用于未键控的data.tables.

3.基准测试

数据

为了测试3个选项的执行速度,我们需要data.table比32行大得多mtcars.这是通过反复加倍mtcars直到达到100万行(89 MB)来实现的.然后将data.table其复制以获取相同输入数据的键控版本.

library(data.table)
# create unkeyed data.table
DT_unkey <- data.table(mtcars)
for (i in 1:15) {
  DT_unkey <- rbindlist(list(DT_unkey, DT_unkey))
  print(nrow(DT_unkey))
}

#create keyed data.table
DT_keyed <- copy(DT_unkey)
setkeyv(DT_keyed, c("am", "gear", "carb"))

# show data.tables
tables()
#     NAME          NROW NCOL MB COLS                                         KEY         
#[1,] DT_keyed 1,048,576   11 89 mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb am,gear,carb
#[2,] DT_unkey 1,048,576   11 89 mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb             
#Total: 178MB
Run Code Online (Sandbox Code Playgroud)

为了得到公平的比较,setkey()操作包含在时间中.此外,data.tables明确复制以排除来自data.table引用更新的效果.

result <- microbenchmark::microbenchmark(
  setkey = {
    DT_keyed <- copy(DT)
    setkeyv(DT_keyed, c("am", "gear", "carb"))},
  cj_keyed = {
    DT_keyed <- copy(DT)
    setkeyv(DT_keyed, c("am", "gear", "carb")) 
    DT_keyed[CJ(1, c(3, 4), c(2, 4)), nomatch = 0]},
  or_keyed = {
    DT_keyed <- copy(DT)
    setkeyv(DT_keyed, c("am", "gear", "carb")) 
    DT_keyed[am == 1 & (gear == 3 | gear == 4) & (carb == 2 | carb == 4)]},
  or_unkey = {
    copy = DT_unkey <- copy(DT)
    DT_unkey[am == 1 & (gear == 3 | gear == 4) & (carb == 2 | carb == 4)]},
  in_keyed =  {
    DT_keyed <- copy(DT)
    setkeyv(DT_keyed, c("am", "gear", "carb")) 
    DT_keyed[am %in% c(1) & gear %in%  c(3, 4) & carb %in% c(2, 4)]},
  in_unkey = {
    copy = DT_unkey <- copy(DT)
    DT_unkey[am %in% c(1) & gear %in%  c(3, 4) & carb %in% c(2, 4)]},
  times = 10L)
Run Code Online (Sandbox Code Playgroud)

我们得到

print(result)
#Unit: milliseconds
#     expr       min        lq     mean    median       uq      max neval
#   setkey 198.23972 198.80760 209.0392 203.47035 213.7455 245.8931    10
# cj_keyed 210.03574 212.46850 227.6808 216.00190 254.0678 259.5231    10
# or_keyed 244.47532 251.45227 296.7229 287.66158 291.3811 404.8678    10
# or_unkey  69.78046  75.61220 103.6113  89.32464 111.5240 231.6814    10
# in_keyed 269.82501 270.81692 302.3453 274.42716 321.2935 431.9619    10
# in_unkey  93.75537  95.86832 119.4371 100.19446 126.6605 251.4172    10

ggplot2::autoplot(result)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

显然,这setkey()是一项相当昂贵的运营.因此,对于一次性任务,矢量扫描操作可能比在键控表上使用二进制搜索更快.

基准测试使用R版本3.3.2(x86_64,mingw32),data.table1.10.4,1.4-2.1运行microbenchmark.