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

时间:2014-01-29 15:21:45

标签: r data.table dplyr

概述

我对data.table比较熟悉,而不是dplyr。我已经阅读了一些dplyr vignettes以及在SO上出现的例子,到目前为止,我的结论是:

  1. data.tabledplyr在速度上具有可比性,除非有许多(即> 10-100K)组,并且在某些其他情况下(参见下面的基准)
  2. dplyr具有更易于使用的语法
  3. dplyr摘要(或将会)潜在的数据库交互
  4. 存在一些细微的功能差异(请参阅下面的“示例/用法”)
  5. 在我看来2.没有多大的重量,因为我对它很熟悉data.table,虽然我明白对于那些对这两者都不熟悉的用户来说这将是一个很重要的因素。我想避免争论哪个更直观,因为这与我从熟悉data.table的人的角度提出的具体问题无关。我还想避免讨论“更直观”如何导致更快的分析(当然是真的,但同样,不是我最感兴趣的)。

    问题

    我想知道的是:

    1. 对于那些熟悉软件包的人来说,是否需要使用一个或另一个软件包进行编码的分析任务更容易(即需要按键的一些组合与所需的深奥水平相结合,其中每个项目的好处都是好事)。
    2. 是否在一个包装与另一个包装中更有效地执行分析任务(即超过2倍)。
    3. 一个recent SO question让我更多地思考这个问题,因为在此之前我不认为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's question ,请注意:这似乎将在 {{3另外,@ beginneR在@ eddi问题的答案中显示了使用do的潜在解决方法。)
      • data.table支持 dplyr 0.5 (感谢@dholstius)以及 rolling joins
      • data.table通过使用二进制文件的自动索引在内部优化 speed 形式DT[col == value]DT[col %in% values]的表达式使用相同的基本R语法搜索overlap joins了解更多细节和一个小小的基准。
      • 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))
      

3 个答案:

答案 0 :(得分:354)

这是我尝试从dplyr角度全面回答的问题, 遵循阿伦答案的大致轮廓(但有些重新排列 基于不同的优先事项)。

语法

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

dplyr为你做的一件非常重要的事情就是它 约束您的选择。我声称大多数单表问题都可以 只用五个关键动词过滤,选择,变异,排列和解决 总结,以及&#34;按组&#34;副词。这种约束是一个很大的帮助 当你学习数据操作时,因为它有助于你的订单 想着这个问题。在dplyr中,每个动词都映射到a 单一功能。每个功能都可以完成一项工作,并且易于理解 孤立地。

通过将这些简单操作连接在一起,您可以创建复杂性 %>%。以下是其中一个帖子Arun linked to

的示例
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的{​​{1}}参数。

我也喜欢the pipe 并不仅限于一个包裹。你可以先整理你的 数据 tidyr,和 完成ggvis中的情节。而且你呢 不限于我写的包 - 任何人都可以编写一个函数 它构成了数据操作管道的无缝部分。事实上,我 而不是更喜欢先前用[.data.table重写的data.table代码:

%>%

diamonds %>% data.table() %>% .[cut != "Fair", .(AvgPrice = mean(price), MedianPrice = as.numeric(median(price)), Count = .N ), by = cut ] %>% .[order(-Count)] 一起管道的想法并不仅限于数据框和 很容易推广到其他上下文:interactive web graphicsweb scrapinggistsrun-time contracts,...)

记忆和表现

我把它们混在一起,因为对我来说,它们并不那么重要。 大多数R用户的工作量低于100万行,而dplyr则是 足够快,足以满足您不了解的大小数据 处理时间。我们优化dplyr以表达对中等数据的表达; 随时可以使用data.table获取更大数据的原始速度。

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

所有这一切,dplyr的表现将在长期内变得更好。我们&#39; 11 肯定会实现像radix这样的data.table的一些好主意 为连接排序和使用相同的索引&amp;过滤器。我们也是 致力于并行化,以便我们可以利用多个核心。

功能

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

  • %>%包,以便轻松地将文件从磁盘中取出 记忆,类似于readr

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

  • 更灵活的分组,如bootstrap样本,汇总等等

我也投入时间来改善R database connectors的谈话能力 web apis,并使其更容易 scrape html pages

答案 1 :(得分:53)

直接回应问题标题 ...

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出现后端性能问题时,您可以通过注册混合评估函数或(在数据库的情况下)在执行之前操作生成的查询来解决这些问题。

另请参阅when is plyr better than data.table?

的已接受答案

答案 2 :(得分:6)

阅读Hadley和Arun的答案会给人一种印象,即那些喜欢dplyr语法的人在某些情况下将不得不切换到data.table或长时间运行。

但是正如某些人已经提到的,dplyr可以使用data.table作为后端。这可以通过使用dtplyr软件包来完成,该软件包最近的版本为1.0.0 release。学习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操作。

data.table的作者马特·道尔(Matt Dowle)于2018年12月完成了基准分析,该案例分析涉及大量小组的行动情况。我发现dtplyr确实确实使大多数喜欢dplyr语法的人在享受data.table所提供的速度的同时继续使用它。