首页 文章

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

提问于
浏览
609

概述

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

  • data.tabledplyr 在速度上具有可比性,除非有许多(即> 10-100K)组,并且在某些其他情况下(参见下面的基准)

  • dplyr 具有更易于访问的语法

  • dplyr 摘要(或将)潜在的数据库交互

  • 存在一些细微的功能差异(请参阅下面的"Examples/Usage")

在我看来2.没有多大的重量,因为我对它非常熟悉 data.table ,虽然我明白对于两者都是新用户来说这将是一个很重要的因素 . 我想避免争论哪个更直观,因为这与我从熟悉 data.table 的人的角度提出的具体问题无关 . 我还想避免讨论"more intuitive"如何导致更快的分析(当然是真的,但同样,不是我最感兴趣的) .

问题

我想知道的是:

  • 对于熟悉软件包的人来说,是否需要使用一个或另一个软件包进行编码的分析任务更容易(即需要按键的一些组合与所需的深奥水平相结合,其中每个小组都是好事) .

  • 是否存在在一个包装与另一个包装中更有效地执行分析任务(即,大于2倍)的分析任务 .

一个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 解决方案也相当不错(感谢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 ,注意:这看起来好像它将在 dplyr 0.5 中实现,同样,@ beginneR在回答@ eddi的问题时显示了使用 do 的潜在解决方法) .

  • data.table 支持 rolling joins (感谢@dholstius)以及 overlap joins

  • data.table 在内部通过自动索引优化表单 DT[col == value]DT[col %in% values] 的表达式,自动索引使用二进制搜索,同时使用相同的基本R语法 . See here了解更多细节和一个小基准 .

  • dplyr 提供了函数的标准评估版本(例如 regroupsummarize_each_ ),可以简化 dplyr 的编程使用(注意编程使用 data.table 绝对是可能的,只需要仔细考虑,替换/引用等,至少据我所知)

基准

  • 我跑了 my own benchmarks 并发现两个包在"split apply combine"样式分析中具有可比性,除非有非常多的组(> 100K),此时 data.table 变得非常快 .

  • @Arun运行了一些 benchmarks on joins ,显示 data.tabledplyr 更好地扩展,因为组的数量增加(使用最近增强的包和最新版本的R更新) . 此外,尝试获得 unique values 时的基准测试速度提高了 data.table ~6倍 .

  • (未验证)在较大版本的组/ apply / sort上的速度提高了 data.table ,而 dplyr 在较小版本的组件上速度提高了40%( another SO question from comments ,感谢danas) .

  • 马特, data.table 的主要作者,benchmarked grouping operations on data.table, dplyr and python pandas on up to 2 billion rows (~100GB in RAM) .

  • older benchmark on 80K groups 的速度快了 data.table ~8倍

数据

这是我在问题部分中展示的第一个例子 .

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 回答

  • 420

    这是我从dplyr角度全面回答的尝试,遵循Arun答案的大致轮廓(但根据不同的优先级进行了一些重新安排) .

    语法

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

    dplyr为您做的一件非常重要的事情是它限制了您的选择 . 我声称大多数单表问题都可以通过五个关键动词过滤,选择,变异,排列和汇总以及"by group"副词来解决 . 当您学习数据操作时,这种约束是一个很大的帮助,因为它有助于您对问题进行思考 . 在dplyr中,每个动词都映射到一个函数 . 每个功能都有一份工作,很容易理解 .

    您可以通过将这些简单操作与 %>% 一起管道来创建复杂性 . 以下是其中一个帖子Arun linked to的示例:

    diamonds %>%
      filter(cut != "Fair") %>%
      group_by(cut) %>%
      summarize(
        AvgPrice = mean(price),
        MedianPrice = as.numeric(median(price)),
        Count = n()
      ) %>%
      arrange(desc(Count))
    

    即使你发生了因为这些功能都是英语动词 . 英语动词的缺点是它们需要比 [ 更多的打字,但我认为可以通过更好的自动完成来大大减轻这种情况 .

    这是等效的data.table代码:

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

    它's harder to follow this code unless you'已经熟悉data.table . (我也无法弄清楚如何以一种看起来很好看的方式缩进重复的 [ ) . 就个人而言,当我看到我在6个月前写的代码时,它已经变得更加直接,如果详细,代码 .

    我认为其他两个小因素会略微降低可读性:

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

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

    我也喜欢the pipe不仅限于一个包 . 您可以先使用tidyr整理数据,然后使用ggvis中的绘图结束 . 而且你不仅限于我写的软件包 - 任何人都可以编写一个函数来构成数据操作管道的无缝部分 . 实际上,我更喜欢先前用 %>% 重写的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的表现会在长期内变得更好 . 我们肯定会实现data.table的一些好主意,比如基数排序和联接和过滤器使用相同的索引 . 我们还致力于并行化,因此我们可以利用多个核心 .

    功能

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

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

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

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

    我'm also investing time into improving R' s database connectors,能够与web apis交谈,并且更容易scrape html pages .

  • 44

    直接回答问题 Headers ......

    dplyr肯定会做data.table不能做的事情 .

    你的观点#3

    dplyr摘要(或将)潜在的DB交互

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

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

    您永远不会(我希望)看到 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?的接受答案

  • 319

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

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

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


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

    1.速度

    相当多的基准测试(尽管主要是分组操作)已经添加到已经显示data.table的问题比dplyr更快,因为要分组的组和/或行的数量增加,包括benchmarks by Matt分组从1000万到20亿在100-1000万个组和不同的分组列上的行(100GB RAM),也比较了 pandas .

    在基准测试中,覆盖这些剩余方面也很棒:

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

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

    • 除运行时外,还为每个操作 Build 基准内存占用量 .

    2.内存使用情况

    • 涉及dplyr中 filter()slice() 的操作可能内存效率低(在data.frames和data.tables上) . See this post .

    请注意,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))
    

    对此的担忧是referential transparency . 通过引用更新data.table对象,尤其是在函数内,可能并不总是需要 . 但这是一个非常有用的功能:请参阅thisthis帖子以了解有趣的案例 . 我们想保留它 .

    因此,我们正在努力在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提供的许多功能,包括通过引用的(子)分配 .

    • 加入时汇总:

    假设您有两个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
    

    并且您希望在 DT2 中的每一行获得 sum(z) * mul ,同时按列 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]
    

    有什么好处?

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

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

    • 更重要的是,通过查看(2)中的 j ,可以清楚地了解我们想要执行的操作 .

    检查this post以获取 by = .EACHI 的详细说明 . 没有实现中间结果,并且一次性执行连接聚合 .

    查看实际使用场景的thisthisthis帖子 .

    dplyr 中,你必须join and aggregate or aggregate first and then join,在内存方面都不是那么有效(这反过来转化为速度) .

    • 更新和加入:

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

    DT1[DT2, col := i.mul]
    

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

    检查this post以获取实际使用情况 .

    总而言之,重要的是要意识到每一点优化都很重要 . 正如Grace Hopper所说,记住你的纳秒!

    3.语法

    我们现在看一下语法 . 哈德利评论here

    数据表非常快,但我认为它们的简洁性使得学习起来更加困难,使用它的代码在编写完成后难以阅读...

    我发现这句话毫无意义,因为它非常主观 . 我们可以尝试的是对比语法的一致性 . 我们将并排比较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中使用 filter() . 但在更新时,我们不得不在 mutate() 内移动逻辑 . 但是,在data.table中,我们使用相同的逻辑表示两个操作 - 在 x > 2 的行上操作,但在第一种情况下,获取 sum(y) ,而在第二种情况下,使用其累积总和更新 y 的那些行 .

    当我们说 DT[i, j, by] 表格一致时,这就是我们的意思 .

    • 类似于情况(c),当我们有 if-else 条件时,我们能够在data.table和dplyr中表达逻辑"as-is" . 但是,如果我们只想返回 if 条件满足的那些行,否则我们不能直接使用 summarise() (AFAICT) . 我们必须首先 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将返回 n() 列的次数,而不是一次 . 在data.table中,我们需要做的就是在 j 中返回一个列表 . 列表的每个元素都将成为结果中的一列 . 因此,我们可以再次使用熟悉的基函数 c().N 连接到 list ,返回 list .

    注意:再次,在data.table中,我们需要做的就是在j中返回一个列表 . 列表的每个元素都将成为结果中的列 . 您可以使用c(),as.list(),lapply(),list()等...基本函数来完成此任务,而无需学习任何新功能 . 您至少需要学习特殊变量 - .N和.SD . dplyr中的等价物是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的 DT[i, j, by]merge() ,类似于基础R.

    • 然而dplyr加入就是这么做的 . 而已 . 没什么 .

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

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

    • data.table能够滚动连接(5) - 滚动forward, LOCFroll backward, NOCBnearest .

    • data.table也有 mult = 参数,用于选择第一个,最后一个或所有匹配(6) .

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

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

    • 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中,你可以在_161989中抛出几乎任何东西 - 唯一要记住的是它返回一个列表,以便列表的每个元素都转换为一列 .

    • 在dplyr中,不能那样做 . 必须求助于 do() ,具体取决于您的功能是否始终返回单个值 . 它很慢 .

    再次,data.table的语法与DT [i,j,by]一致 . 我们可以继续在j中抛出表达式,而不必担心这些事情 .

    看看this SO questionthis one . 我想知道使用dplyr的语法是否可以直截了当地表达答案...

    总而言之,我特别强调了几个实例,其中dplyr的语法效率低,有限或无法使操作简单明了 . 这尤其是因为data.table对“难以阅读/学习”的语法(如上面粘贴/链接的语法)有很强的反对意见 . 大多数关于dplyr的帖子都谈到了最简单的操作 . 这很棒 . 但重要的是要实现其语法和功能限制,我还没有看到它的帖子 . data.table也有它的怪癖(其中一些我已经指出我们正试图修复) . 我们也试图改进data.table的连接,正如我在这里强调的那样 . 但是,与data.table相比,还应该考虑dplyr缺少的功能数量 .

    4.功能

    我已经指出了大部分功能here以及这篇文章 . 此外:

    • fread - 快速文件阅读器已经有很长一段时间了 .

    • fwrite - 当前devel版本v1.9.7中的新功能,现在可以使用并行快速文件编写器 . 有关实施的详细说明,请参见this post;有关进一步发展的信息,请参阅#1664 .

    • Automatic indexing - 另一个在内部优化基本R语法的便利功能 .

    • Ad-hoc groupingdplyrsummarise() 期间通过对变量进行分组来自动对结果进行排序,这可能并不总是令人满意 .

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

    • Non-equi joins :是v1.9.7中的新功能 . 它允许使用其他运算符 <=, <, >, >= 连接以及data.table连接的所有其他优点 .

    • Overlapping range joins最近在data.table中实现 . 检查this post以获得基准测试的概述 .

    data.table中的

    • setorder() 函数允许通过引用真正快速重新排序data.tables .

    • dplyr使用相同的语法提供interface to databases,而data.table目前不提供 .

    • data.table 从v1.9.7(由Jan Gorecki编写)提供更快的等效设置操作 - fsetdifffintersectfunionfsetequal 以及额外的 all 参数(如在SQL中) .

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


    最后:

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

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

    • 目前正在取得进展(在v1.9.7开发中),使用 OpenMP 将已知耗时部件并行化,以实现增量性能增益 .

相关问题