我不明白如何根据data.table
中的多个键进行过滤。使用内置的mtcars
数据集。
DT <- data.table(mtcars)
setkey(DT, am, gear, carb)
在vignette之后,我知道如果我想要对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
它给出了正确的结果。此外,如果我想拥有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
然而,当我想拥有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
失败。能否请您向我解释一下这里的正确方法是什么?
答案 0 :(得分:14)
您没有从查询中收到错误的原因是data.table会在其他值的倍数时重用值。换句话说,因为1
的{{1}}可以使用2次,所以它会在不告诉您的情况下执行此操作。如果您要进行查询,其中允许值的数量不是彼此的倍数,那么它会给您一个警告。例如
am
会向您发出警告,抱怨其余的1个项目,您在输入DT[.(c(1,0),c(5,4,3),c(8,6,4))]
时会看到同样的错误。合并data.table(c(1,0),c(5,4,3),c(8,6,4))
时,X[Y]
和X
都应被视为data.tables。
如果您改为使用Y
,
CJ
然后它将为您和data.table创建所有值的每个组合将给出您期望的结果。
从小插图(粗体是我的):
这里发生了什么?再读一遍。提供的价值 第二个键列“MIA”必须在dest键中找到匹配的值 第一个键列原点提供的匹配行上的列。 我们之前不能跳过关键列的值。因此,我们提供 键列原点的所有唯一值。 “MIA”是自动的 回收以适应3的独特(原点)长度。
为了完整起见,矢量扫描语法无需使用DT[CJ(c(1,0),c(5,4,3),c(8,6,4))]
CJ
或
DT[am == 1 & gear == 4 & carb == 4]
您如何知道是否需要二元搜索?如果子集的速度无法忍受,那么您需要二进制搜索。例如,我有一个48M的行数据。我正在玩,二元搜索和向量之间的差异相对于彼此是惊人的。具体而言,矢量扫描在经过的时间内需要1.490秒,但二进制搜索仅需要0.001秒。当然,这假设我已经键入了data.table。如果我包括设置密钥所需的时间,则设置密钥和执行子集的组合是1.628。所以你必须选择你的毒药
答案 1 :(得分:10)
此问题现已成为重复问题的目标,我觉得可以改进现有答案,以帮助新手data.table
用户。
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
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
请注意,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
请注意,参数nomatch = 0
将删除不匹配的行,即包含NA
的行。
%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
请注意,CJ()
需要键入data.table
,而其他两个变体也适用于未加密的data.table
。
为了测试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
为了得到公平的比较,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)
我们得到了
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)
显然,setkey()
是一项相当昂贵的操作。所以,对于一次性任务
矢量扫描操作可能比在键控表上使用二进制搜索更快。
基准测试使用R
版本3.3.2(x86_64,mingw32),data.table
1.10.4,microbenchmark
1.4-2.1。