提问人:BrodieG 提问时间:1/29/2014 最后编辑:MarkBrodieG 更新时间:10/16/2023 访问量:163352
Data.Table vs DPLYR:一个人能把某件事做好,而另一个人不能或做得不好吗?
data.table vs dplyr: can one do something well the other can't or does poorly?
问:
概述
我比较熟悉,没那么多。我已经阅读了一些在 SO 上出现的 dplyr
小插曲和示例,到目前为止,我的结论是:data.table
dplyr
data.table
并且在速度上具有可比性,除非有许多(即 >10-100K)组,以及在某些其他情况下(见下面的基准)dplyr
dplyr
具有更易于访问的语法dplyr
抽象(或将)潜在的数据库交互- 有一些细微的功能差异(请参阅下面的“示例/用法”)
在我心目中 2.没有太大的重量,因为我相当熟悉,尽管我知道对于两者的新用户来说,这将是一个重要因素。我想避免关于哪个更直观的争论,因为这与我从已经熟悉的人的角度提出的具体问题无关。我还想避免讨论“更直观”如何导致更快的分析(当然是正确的,但同样,这不是我在这里最感兴趣的)。data.table
data.table
问题
我想知道的是:
- 对于熟悉这些软件包的人来说,是否有更容易使用一个或另一个软件包进行编码的分析任务(即所需的击键与所需的深奥程度的某种组合,其中每个都少是一件好事)。
- 是否存在分析任务,在一个软件包中执行效率比另一个软件包高出 2 倍以上。
最近的一个 SO 问题让我对此进行了更多思考,因为在那之前,我认为除了我已经可以做的事情之外,没有提供太多东西。这是解决方案(Q 末尾的数据):dplyr
data.table
dplyr
dat %.%
group_by(name, job) %.%
filter(job != "Boss" | year == min(year)) %.%
mutate(cumu_job2 = cumsum(job2))
这比我尝试解决的要好得多。也就是说,好的解决方案也相当不错(感谢 Jean-Robert、Arun,请注意,这里我更喜欢单个语句而不是严格最优的解决方案):data.table
data.table
setDT(dat)[,
.SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)],
by=list(id, job)
]
后者的语法可能看起来非常深奥,但如果你习惯了,它实际上非常简单(即不使用一些更深奥的技巧)。data.table
理想情况下,我希望看到的是一些很好的例子,即 or 方式更加简洁或性能要好得多。dplyr
data.table
例子
用法
dplyr
不允许返回任意行数的分组操作(来自 Eddi 的问题,注意:这看起来将在 DPLyr 0.5 中实现,@beginneR显示了在 @eddi 问题的答案中使用的潜在解决方法)。do
data.table
支持滚动联接(感谢@dholstius)以及重叠联接data.table
通过自动索引在内部优化表单的表达式或速度,该索引使用二进制搜索,同时使用相同的基本 R 语法。有关更多详细信息和一个小基准,请参阅此处。DT[col == value]
DT[col %in% values]
dplyr
提供函数的标准评估版本(例如,),可以简化编程使用(注意,编程使用绝对是可能的,只需要一些仔细思考、替换/引用等,至少据我所知)regroup
summarize_each_
dplyr
data.table
基准
- 我运行了自己的基准测试,发现这两个包在“拆分应用组合”样式分析中具有可比性,除非有非常多的组 (>100K),此时速度会快得多。
data.table
- @Arun对联接进行了一些基准测试,表明扩展效果比组数量的增加要好(更新了包和最新版本的 R 中的最新增强功能)。此外,尝试获取唯一值时的基准测试速度提高了 ~6 倍。
data.table
dplyr
data.table
- (未验证)在较大版本的组/应用/排序上快 75%,而在较小的版本上快 40%(评论中的另一个 SO 问题,谢谢 danas)。
data.table
dplyr
- Matt 是 的主要作者,对
data.table
、dplyr
和 pythonpandas
的分组操作进行了基准测试,最多 20 亿行(RAM 为 ~100GB)。data.table
- 针对 80K 组的旧基准测试速度提高了 ~8 倍
data.table
数据
这是我在问题部分展示的第一个示例。
dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L,
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane",
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob",
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L,
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L,
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager",
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager",
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L,
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id",
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA,
-16L))
答:
直接回答问题标题...
dplyr
绝对做不了的事情。data.table
你的观点 #3
dplyr 抽象(或将)潜在的数据库交互
是你自己问题的直接答案,但没有提升到足够高的水平。 是真正可扩展到多个数据存储机制的前端,其中 AS 是单个数据存储机制的扩展。dplyr
data.table
看作一个与后端无关的接口,所有目标都使用相同的语法,您可以在其中随意扩展目标和处理程序。 从这个角度来看,是这些目标之一。dplyr
data.table
dplyr
您永远不会(我希望)看到有一天尝试转换您的查询以创建与磁盘或网络数据存储一起操作的 SQL 语句。data.table
dplyr
可以做 data.table
不会或可能不会做的事情。
基于在内存中工作的设计,与 相比,将自身扩展到查询的并行处理中可能要困难得多。data.table
dplyr
在回答体内问题时......
用法
对于熟悉这些软件包的人来说,是否有更容易使用一个或另一个软件包进行编码的分析任务(即所需的击键与所需的深奥程度的某种组合,其中每个都少是一件好事)。
这似乎是一个平底船,但真正的答案是否定的。熟悉工具的人似乎要么使用他们最熟悉的工具,要么使用实际上适合手头工作的工具。话虽如此,有时你想要呈现一种特定的可读性,有时是某种性能水平,当你需要足够高的两者时,你可能只需要另一个工具来配合你已经拥有的东西,以做出更清晰的抽象。
性能
是否存在分析任务,在一个软件包中执行效率比另一个软件包高出 2 倍以上。
同样,没有。 擅长在它所做的每一件事上都保持高效,在某些方面受到底层数据存储和注册处理程序的限制。data.table
dplyr
这意味着当你遇到性能问题时,你可以非常确定它出在你的查询函数中,如果它实际上是一个瓶颈,那么你就为自己赢得了提交报告的乐趣。当用作后端时也是如此;您可能会看到一些开销,但很可能是您的查询。data.table
data.table
dplyr
data.table
dplyr
当后端出现性能问题时,可以通过注册用于混合评估的函数或(对于数据库)在执行之前操作生成的查询来解决它们。dplyr
另请参阅公认的答案,什么时候 plyr 比 data.table 好?
评论
我们至少需要涵盖这些方面,以提供全面的答案/比较(没有特别的重要性顺序):、 和 。Speed
Memory usage
Syntax
Features
我的目的是从 data.table 的角度尽可能清楚地介绍其中的每一个。
注意:除非另有明确说明,否则通过引用 dplyr,我们指的是 dplyr 的 data.frame 接口,其内部使用 Rcpp 在 C++ 中。
data.table 语法的形式是一致的 - 。保持 , 和在一起是设计使然。通过将相关操作放在一起,它可以轻松优化速度和更重要的是内存使用率的操作,并提供一些强大的功能,同时保持语法的一致性。DT[i, j, by]
i
j
by
1.速度
相当多的基准测试(尽管主要是关于分组操作)已经添加到问题中,表明随着组和/或行数的增加,data.table 变得比 dplyr 更快,包括 Matt 关于在 100 到 1000 万个组上从 1000 万到 20 亿行(RAM 中为 100GB )进行分组的基准测试和不同的分组列, 这也比较了.另请参阅更新的基准,其中包括 和。pandas
Spark
Polars
在基准测试中,最好还涵盖以下剩余方面:
涉及行子集的分组操作 - 即类型操作。
DT[x > val, sum(y), by = z]
对其他操作(如更新和联接)进行基准测试。
此外,除了运行时之外,还要对每个操作的内存占用进行基准测试。
2. 内存使用情况
涉及 dplyr 或 dplyr 中的操作可能内存效率低下(在 data.frames 和 data.tables 上)。请参阅此帖子。
filter()
slice()
请注意,Hadley 的评论谈到了速度(dplyr 对他来说速度很快),而这里的主要关注点是内存。
Data.Table 接口目前允许通过引用修改/更新列(请注意,我们不需要将结果重新分配回变量)。
# sub-assign by reference, updates 'y' in-place DT[x >= 1L, y := NA]
但 dplyr 永远不会通过引用进行更新。dplyr 等效项为(请注意,需要重新分配结果):
# copies the entire 'y' column ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
对此的一个关注点是引用透明度。通过引用更新 data.table 对象(尤其是在函数中)可能并不总是可取的。但这是一个非常有用的功能:有关有趣的案例,请参阅此帖子和此帖子。我们想保留它。
因此,我们正在努力在 data.table 中导出函数,这将为用户提供这两种可能性。例如,如果不希望在函数中修改输入 data.table,则可以执行以下操作:
shallow()
foo <- function(DT) { DT = shallow(DT) ## shallow copy DT DT[, newcol := 1L] ## does not affect the original DT DT[x > 2L, newcol := 2L] ## no need to copy (internally), as this column exists only in shallow copied DT DT[x > 2L, x := 3L] ## have to copy (like base R / dplyr does always); otherwise original DT will ## also get modified. }
通过不使用 ,将保留旧功能:
shallow()
bar <- function(DT) { DT[, newcol := 1L] ## old behaviour, original DT gets updated by reference DT[x > 2L, x := 3L] ## old behaviour, update column x in original DT. }
通过使用 创建浅拷贝,我们了解您不想修改原始对象。我们在内部处理所有事情,以确保在确保仅在绝对必要时复制您修改的列。实施后,这应该可以完全解决引用透明度问题,同时为用户提供这两种可能性。
shallow()
此外,一旦导出,dplyr 的 data.table 接口应该避免几乎所有的副本。因此,那些喜欢 dplyr 语法的人可以将其与 data.tables 一起使用。
shallow()
但它仍然缺少 data.table 提供的许多功能,包括(子)引用赋值。
加入时聚合:
假设您有两个 data.tables,如下所示:
DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y")) # x y z # 1: 1 a 1 # 2: 1 a 2 # 3: 1 b 3 # 4: 1 b 4 # 5: 2 a 5 # 6: 2 a 6 # 7: 2 b 7 # 8: 2 b 8 DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y")) # x y mul # 1: 1 a 4 # 2: 2 b 3
并且您希望在按列连接时获取每一行。我们可以:
sum(z) * mul
DT2
x,y
-
聚合得到 , 2) 执行连接 和 3) 乘以 (或)
DT1
sum(z)
data.table 方式
DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][]
DPLYR 等效项
DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% right_join(DF2) %>% mutate(z = z * mul)
-
一气呵成(使用功能):
by = .EACHI
DT1[DT2, list(z=sum(z) * mul), by = .EACHI]
有什么优势?
我们不必为中间结果分配内存。
我们不必进行两次分组/哈希处理(一次用于聚合,另一次用于联接)。
更重要的是,通过查看(2)中,我们想要执行的操作很清楚。
j
查看这篇文章以获取 .没有实现中间结果,并且一次性执行连接+聚合。
by = .EACHI
看看这个,这个和这个帖子,了解真实的使用场景。
在其中,您必须先加入和聚合或聚合,然后再加入,就内存而言,这两者都没有那么高效(这反过来又转化为速度)。
dplyr
-
更新和加入:
考虑如下所示的 data.table 代码:
DT1[DT2, col := i.mul]
添加/更新 的列 与 的 键列匹配的行上的 from 。我不认为在 中有一个与此操作完全等效的操作,即在不避免操作的情况下,该操作必须复制整个操作才能向其添加新列,这是不必要的。
DT1
col
mul
DT2
DT2
DT1
dplyr
*_join
DT1
查看这篇文章了解真实的使用场景。
总而言之,重要的是要意识到每一点优化都很重要。正如格蕾丝·霍珀(Grace Hopper)所说,注意你的纳秒!
3. 语法
现在让我们看一下语法。哈德利在这里评论道:
数据表非常快,但我认为它们的简洁性使学习变得更加困难,并且使用它的代码在编写后更难阅读......
我觉得这句话毫无意义,因为它非常主观。我们也许可以尝试对比语法的一致性。我们将并排比较 data.table 和 dplyr 语法。
我们将使用如下所示的虚拟数据:
DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
基本聚合/更新操作。
# case (a) DT[, sum(y), by = z] ## data.table syntax DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax DT[, y := cumsum(y), by = z] ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y)) # case (b) DT[x > 2, sum(y), by = z] DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y)) DT[x > 2, y := cumsum(y), by = z] ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y))) # case (c) DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z] DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L]) DT[, if(any(x > 5L)) y[1L] - y[2L], by = z] DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
data.table 语法很紧凑,dplyr 非常冗长。在情况(a)中,事情或多或少是等价的。
在情况 (b) 中,我们必须在总结时使用 dplyr。但是在更新时,我们不得不将逻辑移到里面。然而,在 data.table 中,我们用相同的逻辑来表达这两种操作 - 对行进行操作,但在第一种情况下,get ,而在第二种情况下,使用其累积总和更新这些行。
filter()
mutate()
x > 2
sum(y)
y
当我们说形式是一致的时,这就是我们的意思。
DT[i, j, by]
同样,在情况 (c) 中,当我们有条件时,我们能够在 data.table 和 dplyr 中“按原样”表达逻辑。但是,如果我们只想返回条件满足的那些行,否则跳过,我们不能直接使用 (AFAICT)。我们必须先总结,然后总结,因为总是需要一个值。
if-else
if
summarise()
filter()
summarise()
虽然它返回相同的结果,但在此处使用会使实际操作不那么明显。
filter()
在第一种情况下也可以使用(对我来说似乎并不明显),但我的观点是,我们不应该这样做。
filter()
多列的聚合/更新
# case (a) DT[, lapply(.SD, sum), by = z] ## data.table syntax DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax DT[, (cols) := lapply(.SD, sum), by = z] ans <- DF %>% group_by(z) %>% mutate_each(funs(sum)) # case (b) DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z] DF %>% group_by(z) %>% summarise_each(funs(sum, mean)) # case (c) DT[, c(.N, lapply(.SD, sum)), by = z] DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
在情况(a)中,代码或多或少是等效的。data.table 使用熟悉的基函数,而引入了一堆函数。
lapply()
dplyr
*_each()
funs()
data.table 需要提供列名,而 dplyr 会自动生成它。
:=
在案例(b)中,dplyr的语法相对简单。改进多个函数的聚合/更新在 data.table 的列表中。
但是,在情况 (c) 中,dplyr 将返回与列数一样多的列,而不是只返回一次。在 data.table 中,我们需要做的就是返回一个列表。列表的每个元素都将成为结果中的一列。因此,我们可以再次使用熟悉的基函数来连接到返回 .
n()
j
c()
.N
list
list
注意:同样,在data.table中,我们需要做的就是在 中返回一个列表。列表的每个元素都将成为结果中的一列。您可以使用 、 、 等...基本函数来实现这一点,而无需学习任何新函数。
j
c()
as.list()
lapply()
list()
你只需要学习特殊变量 - 至少。dplyr 中的等价物是 和
.N
.SD
n()
.
加入
dplyr 为每种类型的联接提供了单独的函数,其中 data.table 允许使用相同的语法(并有原因)进行联接。它还提供了一个等效的功能作为替代。
DT[i, j, by]
merge.data.table()
setkey(DT1, x, y) # 1. normal join DT1[DT2] ## data.table syntax left_join(DT2, DT1) ## dplyr syntax # 2. select columns while join DT1[DT2, .(z, i.mul)] left_join(select(DT2, x, y, mul), select(DT1, x, y, z)) # 3. aggregate while join DT1[DT2, .(sum(z) * i.mul), by = .EACHI] DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul) # 4. update while join DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI] ?? # 5. rolling join DT1[DT2, roll = -Inf] ?? # 6. other arguments to control output DT1[DT2, mult = "first"] ??
有些人可能会发现每个连接的单独函数要好得多(左、右、内部、反、半等),而其他人可能会喜欢 data.table 的,或者类似于基 R。
DT[i, j, by]
merge()
但是,dplyr 连接就是这样做的。而已。仅此而已。
data.tables 可以在联接 (2) 时选择列,在 dplyr 中,您需要先在两个 data.frame 上进行联接,然后才能联接,如上所示。否则,您将使用不必要的列实现连接,只是为了稍后删除它们,这是低效的。
select()
data.tables 可以在联接 (3) 时进行聚合,也可以在联接 (4) 时进行更新,使用 by = 。EACHI功能。为什么要将整个联接结果材料化为仅添加/更新几列?
data.table 还具有选择第一个、最后一个或所有匹配项的参数 (6)。
mult =
data.table 具有
allow.cartesian = TRUE
参数,以防止意外的无效联接。
同样,语法与允许进一步控制输出的附加参数一致。
DT[i, j, by]
do()
...DPLYR 的 Summarise 专为返回单个值的函数而设计。如果函数返回多个/不相等的值,则必须求助于 .您必须事先了解所有函数的返回值。
do()
DT[, list(x[1], y[1]), by = z] ## data.table syntax DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax DT[, list(x[1:2], y[1]), by = z] DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1])) DT[, quantile(x, 0.25), by = z] DF %>% group_by(z) %>% summarise(quantile(x, 0.25)) DT[, quantile(x, c(0.25, 0.75)), by = z] DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75)))) DT[, as.list(summary(x)), by = z] DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
.SD
的等价物是.
在 data.table 中,你几乎可以抛出任何东西 - 唯一要记住的是它返回一个列表,以便列表的每个元素都转换为一列。
j
在 dplyr 中,不能这样做。必须求助于取决于您对函数是否始终返回单个值的确定性。而且它很慢。
do()
同样,data.table 的语法与 一致。我们可以不停地抛出表情,而不必担心这些事情。
DT[i, j, by]
j
看看这个 SO 问题和这个问题。我想知道是否有可能使用 dplyr 的语法将答案表达为简单明了......
总而言之,我特别强调了几个例子,在这些例子中,dplyr 的语法要么效率低下,要么受到限制,要么无法使操作变得简单明了。这主要是因为 data.table 在“更难阅读/学习”语法(如上面粘贴/链接的语法)方面遭到了相当多的反对。大多数涵盖 dplyr 的帖子都谈到了最直接的操作。这太好了。但意识到它的语法和功能限制也很重要,我还没有看到关于它的帖子。
data.table 也有它的怪癖(我已经指出了我们正在尝试修复的一些怪癖)。正如我在这里强调的那样,我们也在尝试改进 data.table 的连接。
但与 data.table 相比,还应该考虑 dplyr 缺乏的功能数量。
4. 特点
我已经在这里和这篇文章中指出了大部分功能。另外:
FREAD - 快速文件读取器已经存在了很长时间。
fwrite - 现在可以使用并行化的快速文件编写器。有关实施的详细说明,请参阅此帖子,并查看 #1664 以跟踪进一步的发展。
自动索引 - 另一个方便的功能,用于在内部按原样优化基本 R 语法。
临时分组:通过对 期间的变量进行分组来自动对结果进行排序,这可能并不总是可取的。
dplyr
summarise()
上面提到的 data.table 连接(速度/内存效率和语法)的众多优点。
非等价联接:允许使用其他运算符进行联接,以及 data.table 联接的所有其他优点。
<=, <, >, >=
setorder()
data.table 中的函数,允许通过引用对 data.tables 进行非常快速的重新排序。dplyr 使用相同的语法为数据库提供接口,而 data.table 目前没有。
data.table
提供更快的集合操作(由 Jan Gorecki 编写)- 、 和附加参数(如在 SQL 中)。fsetdiff
fintersect
funion
fsetequal
all
data.table 干净地加载,没有屏蔽警告,并且具有此处所述的机制,以便在传递给任何 R 包时实现兼容性。dplyr 更改基本函数,这可能会导致问题;例如,这里和这里。
[.data.frame
filter
lag
[
最后:
在数据库上 - data.table 没有理由不能提供类似的接口,但这现在不是优先事项。如果用户非常喜欢该功能,它可能会受到冲击。不确定。
关于并行性 - 一切都很困难,直到有人继续去做。当然,这需要付出努力(确保线程安全)。
- 目前(在 v1.9.7 开发版中)正在取得进展,以使用 并行化已知耗时的部件,以提高性能。
OpenMP
- 目前(在 v1.9.7 开发版中)正在取得进展,以使用 并行化已知耗时的部件,以提高性能。
评论
:=
dplyr
<-
DF <- DF %>% mutate...
DF %>% mutate...
dplyr
plyr
data.table
SQL
data.table
这是我试图从 dplyr 的角度给出一个全面的答案, 遵循阿伦回答的大致轮廓(但有些重新排列 基于不同的优先级)。
语法
语法有一些主观性,但我坚持我的说法 Data.Table 的简洁性使其更难学习和阅读。 这在一定程度上是因为 dplyr 正在解决一个更容易的问题!
dplyr 为您做的一件非常重要的事情是它限制了您的选择。我声称大多数单表问题都可以 只需五个关键动词即可解决 filter、select、mutate、arrange 和 总结,以及“按组”副词。这种限制是一个很大的帮助 当您学习数据操作时,因为它有助于订购您的 思考问题。在 dplyr 中,这些动词中的每一个都映射到 单一功能。每个函数只做一项工作,并且易于理解 孤立地。
通过将这些简单操作与 一起传递,可以创建复杂性。这是 Arun 链接的帖子之一的示例
到:%>%
diamonds %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(
AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()
) %>%
arrange(desc(Count))
即使你以前从未见过 dplyr(甚至 R!),你仍然可以得到
正在发生的事情的要点,因为函数都是英文的
动词。英语动词的缺点是它们需要比 更多的输入,但我认为这可以通过更好的自动完成来在很大程度上缓解。[
下面是等效的 data.table 代码:
diamondsDT <- data.table(diamonds)
diamondsDT[
cut != "Fair",
.(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N
),
by = cut
][
order(-Count)
]
除非你已经熟悉了,否则很难遵循此代码
data.table. (我也无法弄清楚如何以一种看起来不错的方式缩进重复的内容)。就我个人而言,当我查看代码时,我
6个月前写的,就像看陌生人写的代码一样,
因此,我更喜欢简单明了的代码。[
我认为略微降低可读性的另外两个小因素:
由于几乎每个数据表操作都使用额外的 上下文来弄清楚发生了什么。例如,是联接两个数据表还是从数据框中提取列? 这只是一个小问题,因为在编写良好的代码中, 变量名称应表明正在发生的事情。
[
x[y]
我喜欢这是 dplyr 中的一个单独操作。它 从根本上改变了计算,所以我认为应该是显而易见的 在浏览代码时,它比 的参数。
group_by()
group_by()
by
[.data.table
我也喜欢管道不仅限于一个包装。你可以从整理你的
带有 tidyr 的数据,以及
在GGVIS中完成一个情节。而你是
不限于我写的包 - 任何人都可以写一个函数
这构成了数据操作管道的无缝部分。事实上,我
而是更喜欢用以下方法重写的以前的 data.table 代码:%>%
diamonds %>%
data.table() %>%
.[cut != "Fair",
.(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N
),
by = cut
] %>%
.[order(-Count)]
管道的想法不仅限于数据帧和
很容易推广到其他上下文:交互式网络
图形, 网络
抓取、要点、运行时
合同,...%>%
内存和性能
我把它们混为一谈,因为对我来说,它们并不那么重要。 大多数 R 用户使用的数据行数不到 100 万行,而 dplyr 是 足够快,足以处理您不知道的数据大小 处理时间。我们优化了 dplyr 在中等数据上的表现力; 随意使用 Data.Table 以获得处理更大数据的原始速度。
dplyr 的灵活性还意味着您可以轻松调整性能 特征,使用相同的语法。如果 dplyr 的性能与 数据帧后端对你来说还不够好,你可以使用 data.table 后端(尽管功能集有些受限)。 如果您正在处理的数据不适合内存,则可以使用 数据库后端。
综上所述,从长远来看,dplyr 的性能会变得更好。我们将 一定要实现 Data.Table 的一些好主意,比如 radix 对联接和筛选器进行排序和使用相同的索引。我们也是 致力于并行化,以便我们可以利用多个内核。
特征
我们计划在 2015 年开展以下几项工作:
该软件包,以便于从磁盘和 到内存,类似于 .
readr
fread()
更灵活的联接,包括对非等式联接的支持。
更灵活的分组,如引导示例、汇总等
我还投入时间改进 R 的数据库 连接器,能够与 Web API 通信,并更轻松地抓取 HTML 页面。
评论
data.table
%>%
data.table
[
%>%
dplyr
%>%
DT[\n\texpression\n][\texpression\n]
dplyr
data.table
fread()
data.table
[]
阅读 Hadley 和 Arun 的答案,人们会觉得那些喜欢语法的人在某些情况下会为了长时间的运行时间而切换或妥协。dplyr
data.table
但正如一些人已经提到的,可以用作后端。这是使用最近发布 1.0.0 版的软件包完成的。学习几乎不需要额外的努力。dplyr
data.table
dtplyr
dtplyr
使用时,使用该函数声明一个惰性 data.table,之后使用标准语法来指定对其的操作。这如下所示:dtplyr
lazy_dt()
dplyr
new_table <- mtcars2 %>%
lazy_dt() %>%
filter(wt < 5) %>%
mutate(l100k = 235.21 / mpg) %>% # liters / 100 km
group_by(cyl) %>%
summarise(l100k = mean(l100k))
new_table
#> Source: local data table [?? x 2]
#> Call: `_DT1`[wt < 5][, `:=`(l100k = 235.21/mpg)][, .(l100k = mean(l100k)),
#> keyby = .(cyl)]
#>
#> cyl l100k
#> <dbl> <dbl>
#> 1 4 9.05
#> 2 6 12.0
#> 3 8 14.9
#>
#> # Use as.data.table()/as.data.frame()/as_tibble() to access results
在调用对象之前不会对对象进行评估 // 此时将执行基础操作。new_table
as.data.table()
as.data.frame()
as_tibble()
data.table
我重新创建了作者 Matt Dowle 在 2018 年 12 月完成的基准分析,其中涵盖了大量组的操作情况。我发现,这确实使那些喜欢语法的人在大多数情况下能够继续使用它,同时享受 .data.table
dtplyr
dplyr
data.table
评论
?data.table
示例,我提到的所有示例,除了重叠连接。有吗
我从data.table开始,但使用dplyr来适应工作团队(因为我更喜欢语法)。由于链接,两者都很难调试。一个重要的限制是,两者都难以处理需要来自多个行或列的信息的计算。对于 data.table,可以使用 mapply、map 或仅使用 lapply 添加参数来完成多列函数。我没有在 dplyr 中追求它,但我认为这是可能的。但是,我没有找到一种通过直接指向另一行中的信息来计算的 dplyr 方法。这可以在 data.table 中完成,如下所示: DT[, DDspawn := DD - .例如,SD[jday==Jday.spawn, 'DD'], by=bys]。我认为在数据库术语中,这种能力被称为“直接访问”,但我不是专家。有没有dplyr方法?有滞后函数,所以它一定是可能的,但我没有找到在 dplyr 中执行此操作的语法。
评论
dplyr
as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
(d)plyr
dplyr
plyr
.SD
.SD
.SD