如果我在一个datatable之上使用dplyr语法,我是否可以在使用dplyr语法的同时获得datatable的所有速度优势?换句话说,如果我使用dplyr语法查询它,我是否会误用datatable?或者我是否需要使用纯datatable语法来利用它的所有功能。
代码示例:
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) ]
4条答案
按热度按时间mwecs4sa1#
因为这两个软件包的理念在某些方面不同,所以没有直接/简单的答案。因此,一些妥协是不可避免的。以下是您可能需要解决/考虑的一些问题。
涉及
i
的操作(== dplyr中的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()
列:必须强调这两套方案之间的一个重大哲学差异:
data.table
中,我们喜欢把这些相关的操作放在一起,这样就可以查看j-expression
(来自同一个函数调用),并意识到在(1)中不需要任何列。i
中的表达式得到计算,.N
只是给出行数的逻辑向量的和;在(2)中,只有列b,c,d
在子集中具体化,其它列被忽略。dplyr
中,其理念是让一个函数精确地做一件事。(至少目前)没有办法判断filter()
之后的操作是否需要我们过滤的所有列。如果你想高效地执行这样的任务,你需要提前考虑。我个人认为在这种情况下,这是违反直觉的。注意在(5)和(6)中,我们仍然把不需要的列
a
作为子集,但我不知道如何避免,如果filter()
函数有一个参数来选择要返回的列,我们可以避免这个问题,但这样函数就不会只做一个任务(这也是dplyr设计的选择)。通过引用进行子分配
dplyr将永远不会通过引用更新,这是两个包之间另一个巨大的(哲学上的)差异。
例如,在data.table中,您可以执行以下操作:
它只在满足条件的行上更新列
a
* by reference *。此时dplyr deep在内部复制整个data.table以添加新列。@BrodieG在他的回答中已经提到了这一点。但在实施FR #617时,深层副本可以替换为浅层副本。dplyr: FR#614。请注意,您修改的列将始终被复制(因此稍慢/内存效率较低)。将无法通过引用更新列。
其他功能
foverlaps()
,因此可以与管道操作符一起使用(magrittr/pipeR?-我自己从未尝试过)。但最终,我们的目标是将其集成到
[.data.table
中,以便我们可以收获其他特性,如分组、聚合和加入等,这些特性将具有与上述相同的限制。DT[x == 1]
和DT[x %in% some_vals]
将在第一次运行时自动创建索引,然后使用二分搜索将该索引用于从同一列到快速子集的连续子集。此功能将继续发展。有关此功能的简短概述,请查看this gist。从为data.tables实现
filter()
的方式来看,它没有利用这个特性。因此,您必须权衡这些(可能还有其他几点),并根据这些权衡是否为您所接受来做出决定。
高温加热
(1)请注意,内存效率直接影响速度(尤其是当数据变得更大时),因为大多数情况下的瓶颈是将数据从主内存移动到缓存(并尽可能多地利用缓存中的数据-减少缓存未命中-从而减少访问主内存)。
a2mppw5e2#
你试试看
在这个问题上,似乎data.table比使用data.table的dplyr快2.4倍:
根据Polymerase的意见修订。
nwlls2ji3#
要回答您的问题:
data.table
data.table
语法时高效在许多情况下,对于那些想要
dplyr
语法的人来说,这是一个可以接受的折衷方案,尽管它可能比使用普通 Dataframe 的dplyr
慢。一个重要的因素似乎是
dplyr
在分组时默认复制data.table
。考虑(使用微基准测试):过滤的速度相当,但分组的速度却不一样,我相信罪魁祸首是
dplyr:::grouped_dt
中的这一行:其中
copy
默认为TRUE
(并且不能轻易地更改为FALSE,我可以看到)。这可能没有解释100%的差异,但是diamonds
大小的东西的一般开销很可能不是全部差异。问题在于,为了保持语法的一致性,
dplyr
分两步进行分组:首先在原始数据表的副本上设置与分组相匹配的键,然后才进行分组。data.table
只为最大的结果组分配内存,在本例中,最大的结果组只有一行,因此需要分配的内存量有很大差异。仅供参考,如果有人关心的话,我是通过使用
treeprof
(install_github("brodieg/treeprof")
)发现这一点的,Rprof
输出的一个实验性(仍然非常alpha)树查看器:请注意,上述内容目前仅适用于macs AFAIK。此外,不幸的是,
Rprof
将packagename::funname
类型的调用记录为匿名,因此它实际上可能是grouped_dt
内部负责的任何和所有datatable::
调用,但从快速测试来看,datatable::copy
似乎是一个大调用。也就是说,您可以很快看到
[.data.table
调用并没有太多的开销,但是分组也有一个完全独立的分支。EDIT:确认复制:
eit6fx6z4#
现在可以使用dtplyr了,它是tidyverse的一部分。它允许您像往常一样使用dplyr样式的语句,但是利用了惰性求值,并在幕后将您的语句转换为data.table代码。转换的开销很小,但是您可以派生所有的,如果不是,data.table的大部分好处。更多细节请访问官方git repo here和tidyverse page。