data.table vs dplyr:一个人可以做得很好,另一个人不能做得不好吗?


758

总览

我相对熟悉data.table,而不是那么熟悉dplyr。我已经阅读了一些出现在SO上的dplyr插图和示例,到目前为止,我的结论是:

  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中实现,而且@beginneR显示了do在@eddi问题的答案中使用的一种可能的解决方法)。
  • 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))

9
在阅读方面dplyras.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
与之

7
对于#1 团队dplyrdata.table团队都在制定基准,因此有时会找到答案。#2(语法)imO严格来说是错误的,但这显然会进入观点领域,因此我也投票赞成关闭。
埃迪2014年

13
好吧,再次,我更清楚地表达了一系列问题,(d)plyr措施为0
eddi 2014年

28
@BrodieG的一两件事,真让我心烦大约都dplyrplyr与关于语法和基本是为什么我不喜欢自己的语法的主要原因是,我要学习太多的(阅读超过1)附加功能(其名称即仍然对我来说没有意义),记住他们所做的事情,他们采取什么论据等等。
埃迪2014年

43
@eddi [tongue-in-cheek]关于data.table语法的真正困扰我的一件事是,我必须学习太多函数参数之间的交互方式以及隐秘的快捷方式的含义(例如.SD)。[认真]我认为这些是合理的设计差异,它们会吸引不同的人
hadley 2014年

Answers:


532

我们需要以至少覆盖这些方面为客户提供全面的答案/比较(按重要性排名不分先后): ,,Speed 和。Memory usageSyntaxFeatures

我的意图是从data.table的角度尽可能清晰地介绍其中的每一个。

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


data.table语法的形式-是一致的DT[i, j, by]。为了保持ij并且by一起是由设计。通过将相关操作放在一起,它可以轻松优化操作以提高速度,更重要的是提高内存使用率,还提供一些强大的功能,同时保持语法的一致性。

1.速度

已经显示数据的问题中添加了相当多的基准测试(尽管主要针对分组操作)。随着按分组分组的组和/或行数的增加,table 比dplyr 更快,其中包括Matt的基准测试(1000万20亿行1亿至1000万个组和不同的分组列)(RAM中为100GB)也进行了比较pandas。另请参阅更新的基准,其中也包括Sparkpydatatable

在基准测试中,最好也涵盖以下方面:

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

  • 对其他操作进行基准测试,例如更新联接

  • 除运行时外,还要对每个操作的内存占用量进行基准测试。

2.内存使用

  1. 涉及dplyr filter()slice()in dplyr的操作可能会导致内存效率低下(在data.frame和data.tables上)。看到这篇文章

    请注意,Hadley的评论谈到了速度(dplyr对他来说速度很快),而这里的主要关注点是记忆力

  2. 目前,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对象可能并不总是理想的。但是,这是一个非常有用的功能:看到这个这个职位有趣的情况。我们希望保留它。

    因此,我们正在努力shallow()在data.table中导出功能,这将为用户提供两种可能性。例如,如果希望不修改函数中的输入数据表,则可以执行以下操作:

    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(),我们明白,您不想修改原来的对象。我们在内部负责所有事项,以确保在复制列的同时仅在绝对必要时才进行修改。在实施时,这应该完全解决参照透明性问题,同时为用户提供两种可能性。

    另外,一旦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 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) * mulDT2按列联接时获得每一行x,y。我们可以:

    • 1)聚合DT1得到sum(z),2)执行联接,3)乘法(或)

      # data.table way
      DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][]
      
      # dplyr equivalent
      DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
          right_join(DF2) %>% mutate(z = z * mul)
    • 2)一劳永逸(使用by = .EACHI功能):

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

    有什么好处?

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

    • 我们不必分组/哈希两次(一个用于聚合,另一个用于联接)。

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

    查看此帖子以获取有关的详细说明by = .EACHI。没有实现中间结果,并且连接+聚合是一次性执行的。

    看一下thisthisthis的实际使用情况。

    在in中,dplyr您必须先进行连接和聚合或聚合,然后再进行join,但就内存而言,这两种效率都不高(反过来又转化为速度)。

  4. 更新并加入:

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

    DT1[DT2, col := i.mul]

    增加/更新DT1的列colmul来自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)
  1. 基本的聚合/更新操作。

    # 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)中,我们必须filter()总结时在dplyr中使用。但是,在更新时,我们必须将逻辑移到内部mutate()。但是,在data.table中,我们用相同的逻辑表示这两种操作-在行上进行操作x > 2,但在第一种情况下为get 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 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返回的n()列数将是原来的那么多,而不是一次。在data.table中,我们要做的就是在中返回一个列表j。列表中的每个元素将成为结果中的一列。因此,我们可以再次使用熟悉的基本函数c()来连接.Nlist返回a的a 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 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的DT[i, j, by],或者merge()类似于基数R。

    • 但是dplyr join就是这样做的。而已。没什么。

    • data.tables可以在连接(2)时选择列,在dplyr中,您需要select()先在两个data.frames上先连接,然后如上所示。否则,您将使用不必要的列来实现连接,而只是在以后将其删除时,这样做效率低下。

    • data.tables可以在加入(3)时聚合,也可以在加入(4)时使用by = .EACHI功能进行更新。为什么要对整个联接结果进行汇总以仅添加/更新几列?

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

    • data.table还有一个mult =参数,它选择firstlast所有匹配项(6)。

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

语法再次DT[i, j, by]与其他参数保持一致,从而可以进一步控制输出。

  1. do()...

    dplyr的摘要是专门为返回单个值的函数设计的。如果您的函数返回多个/不相等的值,则必须诉诸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的联接,正如我在此处强调的那样。

但是,还应该考虑dplyr与data.table相比缺少的功能数量。

4.特点

我已经在这里和这篇文章中指出了大多数功能。此外:

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

  • fwrite的 -一个并行化快速文件作家现已推出。有关实现的详细说明,请参见这篇文章;有关进一步发展的信息,请参见#1664

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

  • 临时分组:可能不会总是希望dplyr通过对期间的变量进行分组来对结果进行自动排序summarise()

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

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

  • 最近在data.table中实现了重叠范围联接。查看此帖子以获取有关基准的概述。

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

  • dplyr 使用相同的语法提供与数据库的接口,而data.table目前不提供这种接口

  • data.table提供更快的等同物的一组操作(由Jan Gorecki写入) - ,,fsetdiff 并用另外的参数(如SQL)。fintersectfunionfsetequalall

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


最后:

  • 在数据库上-没有理由data.table无法提供类似的接口,但这并不是现在的优先事项。如果用户非常喜欢该功能,可能会遇到麻烦。

  • 关于并行性-除非有人继续努力,否则一切都会很困难。当然,这会很费力(线程安全)。

    • 当前(在v1.9.7开发中)正在朝着并行化已知耗时部件的方向发展,以使用来提高性能OpenMP

9
@bluefeet:我认为您无法通过将讨论转移到聊天来为我们其他人提供任何出色的服务。我的印象是Arun是开发人员之一,这可能带来了有用的见解。
IRTFM'1

2
当我使用您的链接聊天时,似乎所有注释开始于“您应使用过滤器”的内容都消失了。我是否缺少有关SO-聊天机制的信息?
IRTFM'1

6
我认为,每一个地方,你使用的是由基准(分配:=),dplyr相当于还应使用<-作为DF <- DF %>% mutate...,而不只是DF %>% mutate...
大卫Arenburg

4
关于语法。我相信dplyr对于习惯使用plyr语法的用户来说,data.table可能会更容易,但是对于习惯于查询语言语法(如SQL和其背后的关系代数)的用户,这可能会更容易,这一切都与表格数据转换有关。@Arun您应该注意,通过包装函数可以很容易地实现集合运算符data.table并且当然可以显着提高速度。
jangorecki 2015年

9
我已经阅读了很多篇文章,它对我理解data.table以及更好地使用它有很大帮助。在大多数情况下,我更喜欢data.table而不是dplyr或pandas或PL / pgSQL。但是,我不能停止思考如何表达它。语法容易,清晰或冗长。实际上,即使我经常使用data.table之后,我还是常常很难理解我自己的代码,我实际上是一周前写的。这是只写语言的生活示例。en.wikipedia.org/wiki/Write-only_language 因此,让我们希望,有一天,我们将能够在data.table上使用dplyr。
Ufos

385

这是我从dplyr角度寻求全面答案的尝试,遵循了Arun答案的概述(但根据不同的优先级进行了重新排列)。

句法

语法有一定的主观性,但我坚持我的说法,即data.table的简洁性使得学习和阅读变得更加困难。部分原因是dplyr解决了一个简单得多的问题!

dplyr为您做的一件非常重要的事情是,它 限制了您的选择。我声称大多数单表问题都可以通过仅五个关键动词过滤,选择,变异,排列和汇总以及“按组”副词来解决。当您学习数据操作时,该约束是一个很大的帮助,因为它可以帮助您对问题进行思考。在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,否则很难遵循此代码。(我也想不出如何[ 以我认为不错的方式缩进重复的字符)。就个人而言,当我看我六个月前编写的代码时,就像看一个陌生人编写的代码一样,所以我开始更喜欢直接的(如果是冗长的)代码。

我认为另外两个次要因素会稍微降低可读性:

  • 由于几乎每个数据表操作[都需要使用其他上下文来确定正在发生的情况。例如,是x[y] 联接两个数据表还是从数据框中提取列?这只是一个小问题,因为在编写良好的代码中,变量名称应说明正在发生的事情。

  • 我喜欢那group_by()是dplyr中的单独操作。它从根本上改变了计算方式,因此我认为在浏览代码时应该很明显,并且group_by()byto 的参数更容易发现[.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)]

而且,管道%>%传递的想法不仅限于数据帧,还可以很容易地推广到其他环境:交互式Web图形Web抓取要点运行时合同 ...)

内存和性能

我将它们融合在一起,因为对我而言,它们并不那么重要。大多数R用户使用的行数据不足100万行,而dplyr足以处理您不知道处理时间的数据大小。我们对dplyr进行优化以提高在中等数据上的表现力;随时使用data.table获取更大数据上的原始速度。

dplyr的灵活性还意味着您可以使用相同的语法轻松地调整性能特征。如果带有数据帧后端的dplyr的性能不足以使您满意,则可以使用data.table后端(尽管功能有所限制)。如果正在使用的数据不适合存储在内存中,则可以使用数据库后端。

综上所述,长期来看,dplyr的性能会更好。我们一定会实现data.table的一些很棒的主意,例如基数排序以及对联接和过滤器使用相同的索引。我们还在致力于并行化,因此我们可以利用多个内核。

特征

我们计划在2015年进行的一些工作:

  • readr包,可以很容易地下车磁盘和内存的文件,类似于fread()

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

  • 更灵活的分组,例如引导程序样本,汇总等

我还将投入时间来改进R的数据库连接器,与Web api进行通信的能力 ,并使其更容易 抓取html页面


27
附带一提,我确实同意您的许多论点(尽管我data.table自己更喜欢语法),但是如果您不喜欢样式,则可以轻松地使用%>%它来传递data.table操作[%>%不是特定于dplyr,而是来自单独的程序包(您恰好也是该程序包的合著者),因此我不确定我在大多数“ 语法”段落中要表达的意思。
David Arenburg

11
@DavidArenburg好点。我重新编写了语法,希望可以更清楚地说明我的要点,并强调可以%>%与data.table
一起

5
感谢Hadley,这是一个有用的观点。重新缩进我通常做的DT[\n\texpression\n][\texpression\n]gist)实际上效果很好。我将Arun的答案作为答案,因为他更直接回答了我的特定问题,而这些问题与语法的可访问性无关,但是我认为这对于试图大致了解dplyr和之间的差异/共性的人们来说是一个很好的答案。data.table
BrodieG 2015年

33
为什么要进行快速阅读fread()?在改善fread()或处理其他(未开发的)事情上,时间会不会花费得更好?
EDi

10
的API基于data.table对该[]符号的大量滥用。那是它最大的优点,也是最大的缺点。
Paul

65

直接回答问题标题 ...

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

你的观点3

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

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

看看dplyr作为后端无关的接口,与所有使用相同的语法,在那里你可以随意扩展目标和处理的目标。data.tabledplyr角度来看,是这些目标之一。

您(永远希望)不会有一天data.table尝试翻译查询以创建可用于磁盘或网络数据存储的SQL语句。

dplyr可能做某事data.table不会做或可能做得不好。

基于内存中工作的设计,data.table将自身扩展到并行处理查询的过程可能要困难得多dplyr


针对体内问题...

用法

对于熟悉软件包的人来说,是否有分析任务更容易用一个或另一个软件包编写代码(即,要求的击键与要求的深奥程度的某种组合,每个键都少是一件好事)。

这看似有些微不足道,但真正的答案是否定的。人们熟悉的工具似乎使用的无论是一个最熟悉的他们还是一个实际上是手头的工作是正确的。话虽这么说,有时您想要呈现一种特定的可读性,有时是一种性能水平,而当您需要两者都具有足够高的水平时,您可能只需要另一种工具来配合已经必须进行的更清晰的抽象。

性能

在一个程序包中,是否有比其他程序更有效地执行分析任务(即大于2倍)?

再说一次 data.table擅长于为一切有效确实在那里dplyr得到的在某些方面的基础数据存储和处理注册的限制的负担。

当您遇到性能问题,这意味着与data.table您可以相当肯定它在你的查询功能,如果它实际上的瓶颈data.table,那么你已经赢得了自己在提交报告的喜悦。时也是如此dplyr使用data.table作为后端; 您可能会看到一些开销,dplyr但很奇怪的是您的查询。

dplyr后端存在性能问题时,您可以通过注册用于混合评估的函数或(对于数据库而言)在执行之前操纵生成的查询来解决这些问题。

另请参见关于何时plyr比data.table更好的公认答案


3
不能dplyr用tbl_dt包装一个data.table吗?为什么不同时兼顾两全其美呢?
aaa90210

22
您忘了提到相反的陈述“ data.table绝对可以执行dplyr无法做到的事情”,这也是事实。
jangorecki 2015年

25
阿伦的答案很好地说明了这一点。最重要的(就性能而言)将是fread,按引用更新,滚动联接,重叠联接。我相信没有任何软件包(不仅是dplyr)可以与这些功能竞争。这个演示文稿的最后一张幻灯片就是一个很好的例子。
jangorecki 2015年

15
总的来说,data.table是为什么我仍然使用R的原因。否则,我会使用pandas。它比熊猫更好/更快。
2016年

8
我喜欢data.table,因为它简单且类似于SQL语法结构。我的工作涉及每天进行非常激烈的临时数据分析和图形统计建模,我真的需要足够简单的工具来完成复杂的事情。现在,在日常工作中,我可以将工具包简化为只有data.table的数据和点阵的图形。举个例子,我什至可以做这样的操作:$ DT [group == 1,y_hat:= predict(fit1,data = .SD),] $,这确实很简洁,我认为它是SQL中的一个很大的优势经典的R环境。
xappppp '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.