dplyr on data.table,我真的在使用data.table吗?
如果我在数据表的基础上使用dplyr语法,那么在仍然使用dplyr语法的情况下,我是否能获得数据表的所有速度优势?换句话说,如果我用dplyr语法查询数据表,我是否会误用它?或者我需要使用纯数据表语法来利用它的所有功能 提前谢谢你的建议。代码示例:dplyr on data.table,我真的在使用data.table吗?,r,data.table,dplyr,R,Data.table,Dplyr,如果我在数据表的基础上使用dplyr语法,那么在仍然使用dplyr语法的情况下,我是否能获得数据表的所有速度优势?换句话说,如果我用dplyr语法查询数据表,我是否会误用它?或者我需要使用纯数据表语法来利用它的所有功能 提前谢谢你的建议。代码示例: library(data.table) library(dplyr) diamondsDT <- data.table(ggplot2::diamonds) setkey(diamondsDT, cut) diamondsDT %>
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))
这是我提出的数据表等价性。不确定是否符合DT良好实践。但我想知道在幕后,代码是否真的比dplyr语法更有效:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
试试看
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]
在这个问题上,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
根据多聚酶的评论进行修订。回答您的问题:
- 是的,您正在使用
数据。表
- 但是没有使用纯
语法时那么有效data.table
dplyr
语法的人来说,这将是一个可接受的折衷方案,尽管对于普通数据帧,它可能比dplyr
慢
一个重要因素似乎是分组时默认情况下,dplyr
将复制数据表。考虑(使用微基准):
过滤速度相当,但分组速度不一样。我相信罪魁祸首是dplyr:::grouped\u dt
:
if (copy) {
data <- data.table::copy(data)
}
没有直接/简单的答案,因为这两个包的理念在某些方面有所不同。因此,一些妥协是不可避免的。以下是您可能需要解决/考虑的一些问题
涉及i
(==filter()
和slice()
的操作,在dplyr中)
假设DT
有10列。考虑这些DATA表表达式:
DT[a > 1, .N] ## --- (1)
DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
(1) 给出DT
中的行数,其中列a>1
。(2) 对于i
中的同一表达式,返回按c,d
分组的mean(b)
,与(1)相同
常用的dplyr
表达式为:
DT %>% filter(a > 1) %>% summarise(n()) ## --- (3)
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
显然,数据表代码较短。此外,它们还具有更高的内存效率1。为什么?因为在(3)和(4)中,filter()
首先返回所有10列的行,在(3)中,我们只需要行数,在(4)中,我们只需要列b、c、d
来进行后续操作。要克服这一点,我们必须select()
columns 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)
必须强调两个方案之间的主要哲学差异:
- 在
data.table
中,我们希望将这些相关操作放在一起,这样就可以查看j表达式(来自同一个函数调用),并意识到不需要(1)中的任何列。计算i
中的表达式,.N
只是给出行数的逻辑向量的总和;整个子集从未实现。在(2)中,只有列b、c、d
在子集中具体化,其他列被忽略
- 但在dplyr中,理念是让函数精确地做好一件事。(至少目前)无法判断
filter()
之后的操作是否需要我们筛选的所有列。如果你想高效地完成这些任务,你需要提前思考。我个人认为在这种情况下这是反直觉的
请注意,在(5)和(6)中,我们仍然将不需要的列a
子集化。但我不知道如何避免这种情况。如果filter()
函数有一个参数来选择要返回的列,我们可以避免这个问题,但是该函数不会只执行一个任务(这也是dplyr设计的选择)
通过引用进行子分配
dplyr永远不会通过引用进行更新。这是两个软件包之间的另一个巨大(哲学)差异
例如,在data.table中,您可以执行以下操作:
DT[a %in% some_vals, a := NA]
它仅在满足条件的行上通过引用更新列a
。此时,dplyr deep在内部复制整个data.table以添加新列@布罗迪格在回答中已经提到了这一点
但在实现时,深度副本可以被浅层副本替换。也相关:。请注意,您修改的列将始终被复制(因此速度较慢/内存效率较低)。无法通过引用更新列
其他功能
- 在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%部分值]
将在第一次运行时自动创建索引,然后使用二进制搜索将索引用于从同一列到快速子集的连续子集。此功能将继续发展。请查看此功能的简要概述
从data.tables的filter()
实现方式来看,它没有利用此功能
- 一个dplyr f
DT %>% filter(a > 1) %>% summarise(n()) ## --- (3)
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
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)
DT[a %in% some_vals, a := NA]