data.tablevs dplyr:一个人能把事情做好,另一个人不能或做得不好吗?

概览

我对data.table比较熟悉,对dplyr不太熟悉。我已经阅读了SO上出现的一些#1小插曲和示例,到目前为止我的结论是:

  1. data.tabledplyr在速度上是可比的,除非有很多(即>10-100K)组,以及在其他一些情况下(见下面的基准)
  2. dplyr具有更易于访问的语法
  3. dplyr抽象(或将)潜在的数据库交互
  4. 有一些细微的功能差异(参见下面的“示例/用法”)

在我看来,2.没有多大分量,因为我对它相当熟悉data.table,尽管我知道对于两者的新手来说,这将是一个很大的因素。我想避免争论哪个更直观,因为这与我从已经熟悉data.table的人的角度提出的具体问题无关。我还想避免讨论“更直观”如何导致更快的分析(当然是真的,但同样,这不是我最感兴趣的)。

问题

我想知道的是:

  1. 对于熟悉软件包的人来说,是否有更容易使用一个或另一个软件包进行编码的分析任务(即所需的击键与所需的深奥程度的组合,其中每个都少是一件好事)。
  2. 是否有分析任务在一个包与另一个包中更有效地执行(即超过2倍)。

一个最近的SO问题让我更多地思考了这个问题,因为在那之前,我认为dplyr不会提供比我在data.table中已经能做的更多的东西。这是dplyr的解决方案(Q结尾的数据):

dat %.%group_by(name, job) %.%filter(job != "Boss" | year == min(year)) %.%mutate(cumu_job2 = cumsum(job2))

这比我尝试data.table解决方案要好得多。也就是说,好的data.table解决方案也相当不错(感谢Jean-Robert、Arun,请注意,在这里,我更喜欢单一语句而不是严格意义上的最佳解决方案):

setDT(dat)[,.SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)],by=list(id, job)]

后者的语法可能看起来非常深奥,但如果您习惯于data.table(即不使用一些更深奥的技巧),它实际上非常简单。

理想情况下,我想看到的是一些好的例子,dplyrdata.table的方式更简洁或性能更好。

示例

用法
  • dplyr不允许返回任意行数的分组操作(从eddi的问题开始,注意:这看起来像是在dplyr 0.5中实现的,此外,在回答@eddi的问题时,@eginneR显示了使用do的潜在解决方法)。
  • data.table支持滚动连接(感谢@dholstius)以及重叠连接
  • data.table在内部优化了速度自动索引DT[col == value]DT[col %in% values]形式的表达式,它使用二分查找,同时使用相同的基本R语法。看这里用于更多细节和一个小基准。
  • dplyr提供了函数的标准评估版本(例如regroupsummarize_each_),可以简化dplyr的编程使用(注意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))
156642 次浏览

作为对问题标题的直接回应…

dplyr绝对做了data.table做不到的事情。

你的观点#3

dplyr抽象(或将)潜在的数据库交互

是对您自己问题的直接回答,但没有提升到足够高的水平。dplyr确实是多个数据存储机制的可扩展前端,其中data.table是单个数据存储机制的扩展。

dplyr视为一个与后端无关的接口,所有目标都使用相同的语法,您可以随意扩展目标和处理程序。从dplyr的角度来看,data.table是这些目标之一。

您永远不会(我希望)看到data.table尝试转换您的查询以创建使用磁盘或网络数据存储的SQL语句的一天。

dplyr可能会做data.table不会或可能不会做的事情。

基于在内存中工作的设计,data.table扩展到查询的并行处理可能比dplyr困难得多。


作为对体内问题的回应…

用法

是否有分析任务更容易使用一个或另一个包对于熟悉包裹的人来说进行编码(即所需的击键与所需的深奥程度的组合,其中每个都少是一件好事)。

这可能看起来像是一个赌注,但真正的答案是否定的。拥有工具的人似乎要么使用他们最熟悉的工具,要么使用实际上适合手头工作的工具。话虽如此,有时你想呈现一个特定的易读性,有时是一个性能水平,当你需要足够高的两者水平时,你可能只需要另一个工具来配合你已经拥有的东西,以做出更清晰的抽象。

性能

是否有分析任务在一个包与另一个包中更有效地执行(即超过2倍)。

同样,No.0擅长在所做的一切中保持高效,其中dplyr在某些方面受到底层数据存储和注册处理程序的限制。

这意味着当你遇到data.table的性能问题时,你可以非常确定它在你的查询函数中,如果它实际上是data.table的瓶颈,那么你已经为自己赢得了提交报告的乐趣。当dplyr使用data.table作为后端时也是如此;你可能看到dplyr一些开销,但很可能是你的查询。

dplyr存在后端性能问题时,您可以通过注册用于混合评估的函数或(在数据库的情况下)在执行之前操作生成的查询来解决它们。

另见接受的答案什么时候plyr比data.table?

我们至少需要涵盖这些方面,以提供一个全面的答案/比较(没有特别的重要性顺序):SpeedMemory usageSyntaxFeatures

我的意图是从data.table角度尽可能清楚地涵盖其中的每一个。

注意:除非另有明确说明,否则通过引用dplyr,我们指的是dplyr的data.frame接口,其内部C++使用Rcpp。


data.table语法在形式上是一致的-DT[i, j, by]。保持ijby在一起是设计的。通过保持相关操作在一起,它允许速度和更重要的内存使用进行轻松优化操作,并提供一些强大的功能,同时保持语法的一致性。

1.速度

相当多的基准测试(尽管主要是在分组操作上)已经被添加到已经显示data.table得到更快比dplyr的组和/或行的数量增加,包括100-1000万上从1000万20亿排(100GB内存)分组的Matt的基准和不同的分组列,这也比较了pandas。另见更新的基准,其中包括Sparkpydatatable

关于基准,最好也涵盖这些剩余的方面:

  • 涉及行的子集的分组操作-即DT[x > val, sum(y), by = z]类型的操作。

  • 基准测试其他操作,例如更新加入

  • 除了运行时之外,还对每个操作进行基准测试内存占用

2.内存使用

  1. 在dplyr中涉及filter()slice()的操作可能会导致内存效率低下(data.frames和data.tables)。

    请注意,哈德利的评论谈论的是速度(dplyr对他来说很快),而这里主要关注的是内存

  2. 目前data.table接口允许修改/更新引用列(请注意,我们不需要将结果重新分配回变量)。

     # sub-assign by reference, updates 'y' in-placeDT[x >= 1L, y := NA]

    但是dplyr永远不会通过引用更新。dplyr等效的是(注意结果需要重新分配):

     # copies the entire 'y' columnans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))

    对此的一个担忧是引用透明。通过引用更新data.table对象,特别是在函数中可能并不总是可取的。但这是一个非常有用的功能:请参阅这个这个帖子以获取有趣的案例。我们希望保留它。

    因此,我们正在努力在data.table中导出shallow()函数,为用户提供两种可能性。例如,如果希望不修改函数中的输入data.table,可以这样做:

     foo <- function(DT) {DT = shallow(DT)          ## shallow copy DTDT[, newcol := 1L]        ## does not affect the original DTDT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DTDT[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 referenceDT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.}

    通过使用shallow()创建浅拷贝,我们了解您不想修改原始对象。我们在内部处理一切,以确保同时确保复制您修改只有在绝对必要的时候的列。实现后,这应该完全解决参照透明问题,同时为用户提供两种可能性。

    此外,一旦shallow()被导出dplyr的data.table界面应该避免几乎所有的副本。所以那些喜欢dplyr语法的人可以data.tables.

    但它仍然缺乏data.table提供的许多功能,包括引用(子)赋值。

  3. 加入时聚合:

    假设你有两个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 8DT2 = 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

    并且您希望在通过列x,y连接时为DT2中的每一行获得sum(z) * mul。我们可以:

      1. 聚合DT1得到sum(z),2)执行连接和3)乘以(或)

        data.table方式

        DT1[,.(z=sum(z)), keyby=.(x, y)][DT2][, z:=z*mul][]

        dplyr等效

        DF1%>%group_by(x, y)%>%汇总(z=sum(z))%>%right_join(DF2)%>%突变(z=z*mul)

      1. 一次性完成(使用by = .EACHI功能):

        DT1[DT2, list(z=sum(z)*mul), by=. EACHI]

    优势是什么?

    • 我们不必为中间结果分配内存。

    • 我们不必对/哈希进行两次分组(一次用于聚合,另一次用于加入)。

    • 更重要的是,我们想要执行的操作通过查看(2)中的j很清楚。

    检查这篇文章以获得by = .EACHI的详细解释。没有实现中间结果,并且一次完成了连接+聚合。

    看看这个这个这个的帖子,了解真实的使用场景。

    dplyr中,您必须使用加入并聚合或先聚合后加入,就内存而言(这反过来转化为速度),两者都没有那么高效。

  4. 更新并加入:

    考虑下面显示的data.table代码:

     DT1[DT2, col := i.mul]

    DT2的键列与DT1匹配的行上添加/更新DT1的列colDT2的列mul。我认为在dplyr中没有完全等效的操作,即,没有避免*_join操作,它必须复制整个DT1只是为了添加一个新列,这是不必要的。

    检查这篇文章以获得真实的使用场景。

总而言之,重要的是要意识到每一点优化都很重要。正如<强>格蕾丝·霍珀所说,注意你的纳秒

3.语法

现在让我们看看语法。哈德利评论这里

数据表非常快,但我认为它们的简洁性使其更难学习使用它的代码在编写后更难阅读

我觉得这句话毫无意义,因为它非常主观。我们也许可以尝试对比语法一致性。我们将并排比较data.table和dplyr语法。

我们将使用如下所示的虚拟数据:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))DF = as.data.frame(DT)
  1. 基本聚合/更新操作。

     # case (a)DT[, sum(y), by = z]                       ## data.table syntaxDF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntaxDT[, 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中使用filter()。但是在更新时,我们必须将逻辑移动到mutate()中。然而,在data.table,我们用相同的逻辑表达这两个操作-对x > 2的行进行操作,但在第一种情况下,获得sum(y),而在第二种情况下,使用其累积和更新y的行。

      这就是我们所说的DT[i, j, by]形式是一致的意思。

    • 类似于情况(c),当我们有if-else条件时,我们能够用data.table和dplyr表达逻辑“现状”。然而,如果我们只想返回if条件满足的那些行并跳过其他条件,我们不能直接使用summarise()(AFAICT)。我们必须先filter(),然后总结,因为summarise()总是期望单一值

      虽然它返回相同的结果,但在这里使用filter()会使实际操作不那么明显。

      在第一种情况下也可以使用filter()(对我来说似乎并不明显),但我的观点是我们不应该这样做。

  2. 多个列的聚合/更新

     # case (a)DT[, lapply(.SD, sum), by = z]                     ## data.table syntaxDF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntaxDT[, (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将返回n()的列数是其列数的两倍,而不仅仅是一次。在data.table中,我们需要做的就是在j中返回一个列表。列表中的每个元素都将成为结果中的一列。因此,我们可以再次使用熟悉的基函数c().N连接到list,该函数返回list

    注意:同样,在data.table中,我们需要做的就是在j中返回一个列表。列表中的每个元素都将成为结果中的一列。您可以使用c()as.list()lapply()list()等…基函数来完成此操作,而无需学习任何新函数。

    您只需要学习特殊变量-至少.N.SD。dplyr中的等价物是n().

  3. 加入

    dplyr为每种类型的连接提供单独的函数,data.table允许使用相同的语法DT[i, j, by](并且有理由)进行连接。它还提供等效的merge.data.table()函数作为替代。

     setkey(DT1, x, y)
    # 1. normal joinDT1[DT2]            ## data.table syntaxleft_join(DT2, DT1) ## dplyr syntax
    # 2. select columns while joinDT1[DT2, .(z, i.mul)]left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
    # 3. aggregate while joinDT1[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 joinDT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]??
    # 5. rolling joinDT1[DT2, roll = -Inf]??
    # 6. other arguments to control outputDT1[DT2, mult = "first"]??
  • 有些人可能会为每个连接找到一个单独的函数(左,右,内,反,半等),而其他人可能喜欢data.table的DT[i, j, by]merge(),类似于基数R。

  • 然而dplyr连接就是这样做的。仅此而已。仅此而已。

  • data.tables可以在连接(2)时选择列,在dplyr中,您需要在data.frames上首先select()才能连接,如上所示。否则,您将使用不必要的列来实现连接,以便稍后删除它们,这是低效的。

  • 使用=. EACHI功能,data.tables可以在加入(3)时聚合并在加入(4)时更新。为什么要实现整个连接结果以添加/更新几列?

  • data.table能够滚动接头(5)-卷前进,LOCF向后滚,NOCB最近

  • data.table也有mult =参数选择第一最后所有匹配(6)。

  • data.table有allow.cartesian = TRUE个参数来防止意外的无效连接。

同样,语法与DT[i, j, by]一致,带有额外的参数,允许进一步控制输出。

  1. do()

    dplyr的摘要是专门为返回单个值的函数设计的。如果您的函数返回多个/不相等的值,您将不得不求助于do()。您必须事先了解所有函数的返回值。

     DT[, list(x[1], y[1]), by = z]                 ## data.table syntaxDF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntaxDT[, 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的连接,正如我强调的这里

但也应该考虑到dplyr与data.table.相比缺乏的功能数量

4.特点

我已经指出了这里和这篇文章中的大部分功能。此外:

  • fread-快速文件阅读器已经存在很长时间了。

  • f可不写-现在可以使用并行快速文件写入器。有关实现的详细说明,请参阅这篇文章,有关进一步发展的跟踪,请参阅#1664

  • 自动索引-另一个方便的功能,可以在内部优化基本R语法。

  • 特设分组dplyrsummarise()期间通过分组变量自动对结果进行排序,这可能并不总是可取的。

  • 上面提到的data.table连接(速度/内存效率和语法)的许多优点。

  • 非Equi加入:允许使用其他运算符<=, <, >, >=以及data.table连接的所有其他优点进行连接。

  • 重叠范围连接最近在data.table中实现。检查这篇文章以获得基准的概述。

  • data.table中的setorder()函数,允许通过引用快速重新排序data.tables。

  • dplyr使用相同的语法提供数据库接口,data.table目前没有。

  • data.table提供了集合操作(由Jan Gorecki编写)-fsetdifffintersectfunionfsetequal的更快等价物以及额外的all参数(如SQL)。

  • data.table加载干净,没有屏蔽警告,并且在传递给任何R包时具有这里描述的[.data.frame兼容性机制。dplyr更改可能导致问题的基本函数filterlag[;例如这里这里


最后:

  • 在数据库上-data.table没有理由不能提供类似的界面,但现在这不是优先事项。如果用户非常喜欢该功能,它可能会被提升…不确定。

  • 关于并行-一切都很困难,直到有人继续做。当然这需要努力(线程安全)。

    • 目前正在取得进展(在v1.9.7 devel中),使用OpenMP并行化已知耗时的部分以提高性能。

这是我试图从dplyr的角度给出一个全面的答案,遵循阿伦答案的大致轮廓(但有些重新排列)不同的优先事项)。

语法

语法有一些主观性,但我坚持我的说法data.table的简洁使得它更难学习和阅读。这部分是因为dplyr解决了一个更容易的问题!

dplyr为您做的一件非常重要的事情是限制你的选择。我声称大多数单表问题都可以只需五个关键动词即可解决过滤、选择、突变、排列和总结,以及“按组”副词。这个约束是一个很大的帮助当你学习数据操作时,因为它有助于你在dplyr中,这些动词中的每一个都映射到一个单一功能。每个功能做一项工作,易于理解#36825;的隔绝

您可以通过将这些简单的操作与以下操作结合在一起来创建复杂性%>%。这是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]连接两个数据表还是从数据框中提取列?这只是一个小问题,因为在编写良好的代码中变量名应该表明发生了什么。

  • 我喜欢group_by()在dplyr中是一个单独的操作。它从根本上改变了计算,所以我认为应该是显而易见的在浏览代码时,发现group_by()比发现group_by()更容易[.data.tableby参数。

我也喜欢管道不仅仅局限于一个包裹。你可以从整理你的数据与tidyr和以ggvis的情节结束。你是不限于我编写的包——任何人都可以编写函数形成数据处理管道的无缝部分。事实上,我更喜欢用%>%重写的data.table代码:

diamonds %>%data.table() %>%.[cut != "Fair",.(AvgPrice = mean(price),MedianPrice = as.numeric(median(price)),Count = .N),by = cut] %>%.[order(-Count)]

使用%>%管道的想法不仅限于数据帧和很容易推广到其他上下文:交互式web图形web刮擦Gist运行时合同…)

记忆和性能

我把它们放在一起,因为对我来说,它们并不那么重要。大多数R用户在100万行数据下工作得很好,而dplyr是足够快的速度来处理你不知道的数据量流转时长:优化dplyr对中数据的表达能力;随意使用data.table在更大的数据上的原始速度。

dplyr的灵活性也意味着您可以轻松调整性能特性使用相同的语法。如果dplyr的性能与数据帧后端对你来说不够好,你可以使用data.table后端(尽管功能有些受限)。如果您正在处理的数据不适合内存,那么您可以使用数据库后端。

总而言之,从长远来看,dplyr的性能会变得更好。我们将肯定会实现一些data.table的伟大想法,比如基数排序并对连接和过滤器使用相同的索引。我们也正在进行并行化,因此我们可以利用多个内核。

功能

以下是我们计划在2015年要做的几件事:

  • readr包,使文件从磁盘和磁盘中轻松获取到内存,类似于fread()

  • 更灵活的连接,包括对非等连接的支持。

  • 更灵活的分组,如引导示例、汇总等

我也在花时间改进R的数据库连接器,能够与webapi,并使其更容易抓取html页面.

阅读Hadley和Arun的答案,人们会得到这样的印象:那些喜欢dplyr语法的人在某些情况下会切换到data.table,或者在长时间运行时妥协。

但是正如一些人已经提到的,dplyr可以使用data.table作为后端。这是使用dtplyr包完成的,该包最近有它的版本1.0.0发布。学习dtplyr几乎不需要额外的努力。

当使用dtplyr时,使用函数lazy_dt()声明一个惰性data.table,之后使用标准的dplyr语法来指定对它的操作。

new_table <- mtcars2 %>%lazy_dt() %>%filter(wt < 5) %>%mutate(l100k = 235.21 / mpg) %>% # liters / 100 kmgroup_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操作。

我已经重建data.table作者Matt Dowle在2018年12月完成的基准分析,其中涵盖了对大量组的操作的情况。我发现dtplyr确实在很大程度上使那些喜欢dplyr语法的人能够在享受data.table提供的速度的同时继续使用它。