Pol*_*ase 83 r dplyr data.table
如果我使用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))
Run Code Online (Sandbox Code Playgroud)
结果:
# 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
Run Code Online (Sandbox Code Playgroud)
这是我想出的数据表等价.不确定它是否符合DT良好做法.但我想知道代码是否比场景背后的dplyr语法更有效:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
Run Code Online (Sandbox Code Playgroud)
Aru*_*run 73
没有直接/简单的答案,因为这两个方案的哲学在某些方面有所不同.因此,一些妥协是不可避免的.以下是您可能需要解决/考虑的一些问题.
i(== filter()和slice()dplyr)的操作假设DT有10列.考虑这些data.table表达式:
DT[a > 1, .N] ## --- (1)
DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
Run Code Online (Sandbox Code Playgroud)
(1)给出的行的数量DT,其中柱a > 1.(2)对于(1)中的相同表达式进行mean(b)分组的返回.c,di
常用的dplyr表达式是:
DT %>% filter(a > 1) %>% summarise(n()) ## --- (3)
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
Run Code Online (Sandbox Code Playgroud)
显然,data.table代码更短.此外,它们还具有更高的内存效率1.为什么?因为在(3)和(4)中,首先filter()返回所有10列的行,而在(3)中我们只需要行数,而在(4)中我们只需要b, c, d连续操作的列.为了解决这个问题,我们必须select()列出apriori:
DT %>% select(a) %>% filter(a > 1) %>% summarise(n()) ## --- (5)
DT %>% select(a,b,c,d) %>% filter(a > 1) %>% group_by(c,d) %>% summarise(mean(b)) ## --- (6)
Run Code Online (Sandbox Code Playgroud)
必须强调两个包之间的主要哲学差异:
在
data.table,我们希望将这些相关操作保持在一起,并且允许查看j-expression(来自相同的函数调用)并且意识到不需要(1)中的任何列.i得到的表达式得到,并且.N只是给出行数的逻辑向量的总和; 整个子集永远不会实现.在(2)中,只有列b,c,d在子集中具体化,其他列被忽略.但是
dplyr,该理念是具有这样的功能正好做一件事好.(至少目前)没有办法判断后面的操作是否filter()需要我们过滤的所有列.如果您想要有效地执行此类任务,您需要提前考虑.在这种情况下,我个人认为它具有反竞争性.
请注意,在(5)和(6)中,我们仍然a是我们不需要的子列.但我不确定如何避免这种情况.如果filter()函数有一个参数来选择要返回的列,我们可以避免这个问题,但是这个函数不会只做一个任务(这也是一个dplyr设计选择).
dplyr 永远不会通过引用更新.这是两个包之间的另一个巨大(哲学)差异.
例如,在data.table中,您可以执行以下操作:
DT[a %in% some_vals, a := NA]
Run Code Online (Sandbox Code Playgroud)
它 仅a 通过引用来更新满足条件的那些行.目前,dplyr深度复制整个data.table以添加新列.@BrodieG在他的回答中已经提到了这一点.
但是当实现FR#617时,深拷贝可以被浅拷贝替换.也相关:dplyr:FR#614.请注意,您修改的列将始终被复制(因此速度较慢/内存效率较低).无法通过引用更新列.
在data.table中,您可以在加入时进行聚合,这是更直接的理解和内存效率,因为中间连接结果永远不会实现.查看此帖子以获取示例.你不能(目前?)使用dplyr的data.table/data.frame语法来做到这一点.
dplyr的语法也不支持data.table的滚动连接功能.
我们最近在data.table中实现了重叠连接以连接区间范围(这是一个例子),这是一个单独的函数foverlaps(),因此可以与管道运算符一起使用(magrittr/pipeR? - 我自己从未尝试过).
但最终,我们的目标是将其整合到一起,[.data.table以便我们可以收集其他功能,如分组,聚合,加入等等.这将具有上述相同的限制.
从1.9.4开始,data.table使用辅助密钥实现自动索引,以便在常规R语法上使用基于快速二进制搜索的子集.例如:DT[x == 1]并将DT[x %in% some_vals]在第一次运行时自动创建索引,然后使用二进制搜索将其从同一列的连续子集用于快速子集.此功能将继续发展.请查看此要点以获得此功能的简短概述.
从filter()为data.tables实现的方式,它没有利用此功能.
因此,您必须权衡这些(可能还有其他要点),并根据您是否接受这些权衡来决定.
HTH
(1)请注意,内存效率直接影响速度(特别是当数据变大时),因为大多数情况下的瓶颈是将数据从主内存移动到缓存(并尽可能多地利用缓存中的数据 - 减少缓存未命中 - 以减少访问主存储器).这里不详述.
G. *_*eck 23
就试一试吧.
library(rbenchmark)
library(dplyr)
library(data.table)
benchmark(
dplyr = diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count)),
data.table = diamondsDT[cut != "Fair",
list(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by = cut][order(-Count)])[1:4]
Run Code Online (Sandbox Code Playgroud)
在这个问题上,似乎data.table比使用data.table的dplyr快2.4倍:
test replications elapsed relative
2 data.table 100 2.39 1.000
1 dplyr 100 5.77 2.414
Run Code Online (Sandbox Code Playgroud)
根据Polymerase的评论进行修订.
Bro*_*ieG 20
回答你的问题:
data.tabledata.table语法那样有效在许多情况下,对于那些想要dplyr语法的人来说,这将是一个可接受的折衷方案,尽管它可能比dplyr普通数据帧慢.
一个重要因素似乎是在分组时默认dplyr复制data.table.考虑(使用microbenchmark):
Unit: microseconds
expr min lq median
diamondsDT[, mean(price), by = cut] 3395.753 4039.5700 4543.594
diamondsDT[cut != "Fair"] 12315.943 15460.1055 16383.738
diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price)) 9210.670 11486.7530 12994.073
diamondsDT %>% filter(cut != "Fair") 13003.878 15897.5310 17032.609
Run Code Online (Sandbox Code Playgroud)
过滤速度相当,但分组不是.我相信罪魁祸首是这一行dplyr:::grouped_dt:
if (copy) {
data <- data.table::copy(data)
}
Run Code Online (Sandbox Code Playgroud)
其中copy缺省为TRUE(并且不能轻易地改变为FALSE,我可以看到).这可能不会占到差异的100%,但仅在一般情况下,对于diamonds最有可能的大小的开销不是完全不同的.
问题是为了获得一致的语法,dplyr分两步进行分组.它首先在与这些组匹配的原始数据表的副本上设置键,然后才对其进行分组. data.table只为最大的结果组分配内存,在这种情况下只是一行,这样就需要分配多少内存.
仅供参考,如果有人关心,我发现这是通过使用treeprof(install_github("brodieg/treeprof")),一个实验(并且仍然非常阿尔法)树查看器Rprof输出:

注意以上目前仅适用于macs AFAIK.此外,遗憾的是,Rprof将该类型的调用记录packagename::funname为匿名,因此它实际上可能是datatable::内部的任何和所有调用grouped_dt都是负责任的,但是从快速测试看起来它datatable::copy是最重要的.
也就是说,您可以快速了解[.data.table调用周围没有那么多开销,但是对于分组还有一个完全独立的分支.
编辑:确认复制:
> tracemem(diamondsDT)
[1] "<0x000000002747e348>"
> diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price))
tracemem[0x000000002747e348 -> 0x000000002a624bc0]: <Anonymous> grouped_dt group_by_.data.table group_by_ group_by <Anonymous> freduce _fseq eval eval withVisible %>%
Source: local data table [5 x 2]
cut AvgPrice
1 Fair 4358.758
2 Good 3928.864
3 Very Good 3981.760
4 Premium 4584.258
5 Ideal 3457.542
> diamondsDT[, mean(price), by = cut]
cut V1
1: Ideal 3457.542
2: Premium 4584.258
3: Good 3928.864
4: Very Good 3981.760
5: Fair 4358.758
> untracemem(diamondsDT)
Run Code Online (Sandbox Code Playgroud)