846

概述

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

  1. data.table并且dplyr在速度上具有可比性,除非有许多(即 >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(即不使用一些更深奥的技巧)。

理想情况下,我希望看到一些很好的例子,即dplyrordata.table方法更简洁或性能更好。

例子

用法
  • dplyr不允许返回任意行数的分组操作(来自eddi 的问题,请注意:这看起来将在dplyr 0.5中实现,此外,@beginneR 显示了do在@eddi 问题的答案中使用的潜在解决方法)。
  • data.table支持滚动连接(感谢@dholstius)以及重叠连接
  • data.table通过使用二分搜索同时使用相同的基本 R 语法的自动索引在内部优化表单的表达式DT[col == value]DT[col %in% values]提高速度。有关更多详细信息和一个小基准,请参见此处。
  • dplyr提供标准评估版本的函数(例如regroup, summarize_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))
4

4 回答 4

605

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

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

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


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

1.速度

相当多的基准(尽管主要是关于分组操作)已经添加到已经显示 data.table比 dplyr更快的问题中,因为要分组的组和/或行数增加,包括Matt对从1000 万到分组的基准100-1000 万组和不同的分组列上有20 亿行(RAM 中 100GB) ,这也比较了pandas. 另请参阅更新的基准,其中包括Sparkpydatatable

在基准测试中,最好也涵盖这些剩余方面:

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

  • 对其他操作进行基准测试,例如updatejoins

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

2.内存使用

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

    请注意,Hadley 的评论谈到了速度(dplyr 对他来说非常快),而这里主要关注的是memory

  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 中导出函数,这将为用户提供两种可能性。例如,如果不希望在函数中修改输入 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) * mul每一行。我们可以:DT2x,y

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

        数据表方式

        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)

      1. 一口气完成所有操作(使用by = .EACHI功能):

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

    有什么好处?

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

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

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

    查看此帖子以获取有关by = .EACHI. 没有中间结果被具体化,并且连接+聚合是一次性执行的。

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

    dplyr这种情况下,您必须先加入和聚合或聚合,然后再加入,这两种方法在内存方面都没有效率(这反过来又转化为速度)。

  4. 更新和加入:

    考虑如下所示的 data.table 代码:

     DT1[DT2, col := i.mul]
    

    在's 键列匹配的那些行上添加/更新DT1's 列。我不认为在 中存在与此操作完全等效的操作,即,在不避免操作的情况下,该操作必须复制整个操作以向其中添加新列,这是不必要的。colmulDT2DT2DT1dplyr*_joinDT1

    查看这篇文章以了解真实的使用场景。

总而言之,重要的是要意识到每一点优化都很重要。正如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 。但是在更新的时候,我们不得不移动里面的逻辑。然而,在 data.table 中,我们用相同的逻辑来表达这两个操作 - 对行进行操作 where ,但在第一种情况下, get ,而在第二种情况下,使用其累积和更新这些行。mutate()x > 2sum(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's DT[i, j, by],或者merge()类似于基础 R。

  • 然而 dplyr 加入就是这样做的。而已。一点也不差。

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

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

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

  • data.table 还具有mult =选择第一个最后一个所有匹配项的参数 (6)。

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

再一次,语法与DT[i, j, by]允许进一步控制输出的附加参数一致。

  1. 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 question这个。我想知道是否可以使用 dplyr 的语法直接表达答案......

总而言之,我特别强调了dplyr 的语法效率低下、有限或无法使操作简单的几个实例。这尤其是因为 data.table 对“更难阅读/学习”语法(如上面粘贴/链接的语法)有相当多的强烈反对。大多数涵盖 dplyr 的帖子都在谈论最直接的操作。这很棒。但认识到它的语法和功能限制也很重要,我还没有看到关于它的帖子。

data.table 也有其怪癖(其中一些我已经指出我们正在尝试修复)。正如我在这里强调的那样,我们也在尝试改进 data.table 的连接。

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

4. 特点

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

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

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

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

  • Ad-hoc groupingdplyr通过在 期间对变量进行分组来自动对结果进行排序summarise(),这可能并不总是可取的。

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

  • Non-equi joins:允许使用其他运算符进行连接<=, <, >, >=以及 data.table 连接的所有其他优点。

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

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

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

  • data.table提供更快的集合操作等价物(由 Jan Gorecki 编写) - fsetdiff, fintersect,funionfsetequal带有附加all参数(如 SQL 中)。

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


最后:

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

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

    • 目前(在 v1.9.7 开发版中)正在朝着并行化已知耗时的部分以使用OpenMP.
于 2014-12-31T08:27:00.477 回答
419

这是我从 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,否则很难遵循此代码。(我也不知道如何以[ 一种对我来说很好看的方式缩进重复的部分)。就我个人而言,当我看我 6 个月前写的代码时,就像看一个陌生人写的代码,所以我开始更喜欢简单明了的代码。

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

  • 由于几乎每个数据表操作都使用[您需要额外的上下文来弄清楚发生了什么。例如,是x[y] 连接两个数据表还是从数据框中提取列?这只是一个小问题,因为在编写良好的代码中,变量名应该表明正在发生的事情。

  • 我喜欢这group_by()是 dplyr 中的单独操作。它从根本上改变了计算,所以我认为在浏览代码时应该很明显,而且它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 的一些很棒的想法,比如基数排序和使用相同的索引进行连接和过滤器。我们还致力于并行化,以便我们可以利用多个内核。

特征

我们计划在 2015 年开展的一些工作:

  • readr包,以便轻松将文件从磁盘和内存中取出,类似于fread().

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

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

我还花时间改进 R 的数据库连接器、与web api对话的能力 ,以及更容易 抓取 html 页面

于 2015-01-08T12:39:02.707 回答
74

直接回答问题标题...

dplyr 绝对data.table不了的事。

你的观点#3

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

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

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

您永远不会(我希望)有一天会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 更好的公认答案?

于 2014-11-16T22:39:36.430 回答
14

阅读 Hadley 和 Arun 的答案,人们会觉得那些喜欢dplyr's 语法的人在某些情况下会切换到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 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.

于 2020-06-14T17:00:55.743 回答