如果我在 datatable 之上使用 dplyr 语法,在使用dplyr的语法时,是否可以获得数据表的所有速度优势?换句话说,如果我使用dplyr语法查询数据表,是否会误用数据表?或者我是否需要使用纯数据表语法来利用其所有功能 .
提前感谢任何建议 . 代码示例:
library(data.table)
library(dplyr)
diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut)
diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count))
结果:
# cut AvgPrice MedianPrice Count
# 1 Ideal 3457.542 1810.0 21551
# 2 Premium 4584.258 3185.0 13791
# 3 Very Good 3981.760 2648.0 12082
# 4 Good 3928.864 3050.5 4906
这是我想出的数据表等价 . 不确定它是否符合DT良好实践 . 但我想知道代码是否真的比场景背后的dplyr语法更有效:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
3 回答
没有直接/简单的答案,因为这两个方案的哲学在某些方面有所不同 . 因此,一些妥协是不可避免的 . 以下是您可能需要解决/考虑的一些问题 .
在dplyr中涉及i(== filter()和slice()的操作)
假设
DT
有10列 . 考虑这些data.table表达式:(1)给出
DT
中列a > 1
的行数 . (2)返回mean(b)
按c,d
分组,i
中的相同表达式为(1) .常用的
dplyr
表达式是:显然,data.table代码更短 . 此外,它们还具有更高的内存效率1 . 为什么?因为在(3)和(4)中,
filter()
首先返回所有10列的行,而在(3)中我们只需要行数,而在(4)中我们只需要列b, c, d
用于连续操作 . 要克服这一点,我们必须select()
列apriori:请注意,在(5)和(6)中,我们仍然是
a
的子集列,我们不知道如何避免这种情况 . 如果filter()
函数有一个参数来选择要返回的列,我们可以避免这个问题,但是该函数不会只执行一个任务(这也是一个dplyr设计选择) .通过引用进行子分配
例如,在data.table中,您可以执行以下操作:
它仅通过引用来更新列
a
,满足条件的那些行 . 目前,dplyr深度复制整个data.table以添加新列 . @BrodieG在他的回答中已经提到了这一点 .但是当实现FR #617时,深拷贝可以被浅拷贝替换 . 也相关:dplyr: FR#614 . 请注意,您修改的列将始终被复制(因此速度较慢/内存效率较低) . 将无法通过引用更新列 .
其他功能
dplyr的语法也不支持
data.table的rolling joins功能 .
我们最近在data.table中实现了重叠连接以加入区间范围(here's an example),这是一个单独的函数
foverlaps()
,因此可以与管道运算符一起使用(magrittr / pipeR? - 我自己从未尝试过) .但最终,我们的目标是将其整合到
[.data.table
中,这样我们就可以收集其他功能,例如分组,聚合等等 . 这将具有上述相同的限制 .DT[x == 1]
和DT[x %in% some_vals]
将在第一次运行时自动创建索引,然后使用二进制搜索将其从同一列的连续子集用于快速子集 . 此功能将继续发展 . 有关此功能的简短概述,请查看this gist .从为data.tables实现
filter()
的方式来看,它没有利用此功能 .所以,你必须权衡这些(可能还有其他点),并根据这些是否决定你可以接受这种权衡 .
HTH
(1)请注意,内存效率直接影响速度(特别是当数据变大时),因为大多数情况下的瓶颈是将数据从主内存移动到缓存(并尽可能多地利用缓存中的数据 - 减少缓存未命中 - 以减少访问主存储器) . 这里不详述 .
就试一试吧 .
在这个问题上,似乎data.table比使用data.table的dplyr快2.4倍:
Revised 基于Polymerase的评论 .
回答你的问题:
是的,您正在使用
data.table
但不如使用纯
data.table
语法那样有效在许多情况下,对于那些想要
dplyr
语法的人来说,这将是一个可接受的折衷方案,尽管它可能比使用普通数据帧的dplyr
慢 .一个重要因素似乎是
dplyr
将在分组时默认复制data.table
. 考虑(使用microbenchmark):过滤速度相当,但分组不是 . 我相信罪魁祸首是
dplyr:::grouped_dt
中的这一行:其中
copy
默认为TRUE
(并且't easily be changed to FALSE that I can see). This likely doesn' t可以占到差异的100%,但是对于diamonds
这个大小的东西而言,一般的开销很可能不是完全不同的 .问题是,为了获得一致的语法,
dplyr
分两步进行分组 . 它首先在与组匹配的原始数据表的副本上设置键,然后才对其进行分组 .data.table
只为最大的结果组分配内存,在这种情况下只是一行,这样就需要分配多少内存 .仅供参考,如果有人关心的话,我通过使用
treeprof
(install_github("brodieg/treeprof")
)找到了这个,这是一个用于Rprof
输出的实验性(并且仍然非常阿尔法)树查看器:注意以上目前仅适用于macs AFAIK . 另外,遗憾的是,
Rprof
将packagename::funname
类型的调用记录为匿名,因此它实际上可能是grouped_dt
内部的任何和所有datatable::
调用,但是从快速测试看起来像datatable::copy
是最重要的 .也就是说,您可以快速查看
[.data.table
调用周围没有那么多开销,但是对于分组还有一个完全独立的分支 .EDIT :确认复制: