dplyr、data.table和setDT交互问题

3

这是一个非常简化的问题,实际上它在更大规模的情况下出现。我们的目标是利用 data.table 结构和 dplyr 命令,在多列上进行更快速的排序和分组。

正确的版本如下:

library(dplyr)
library(data.table)
library(dtplyr)
library(lubridate)

# data set
dt = data.frame(id = c("a","b", "a"),
                date = ymd(c("2016-01-03","2016-01-02","2016-01-01")),
                value = c(10,5,9), stringsAsFactors = F)

# process to get the id of the largest value
(setDT(dt, key=c("id","value")) %>% select(id,value) %>% arrange(desc(value)) %>% slice(1))$id -> picked_id

# return all rows of this id
dt %>% filter(id %in% picked_id)

# id          date value
# 1:  a 2016-01-01     9
# 2:  a 2016-01-03    10

但是当我尝试在脚本的不同位置使用setDT时,我得到了不同的结果:

dt = data.frame(id = c("a","b", "a"),
                date = ymd(c("2016-01-03","2016-01-02","2016-01-01")),
                value = c(10,5,9), stringsAsFactors = F)

(dt %>% select(id,value) %>% setDT(., key=c("id","value")) %>% arrange(desc(value)) %>% slice(1))$id -> picked_id

dt %>% filter(id %in% picked_id)

#   id       date value
# 1  a 2016-01-03     9
# 2  a 2016-01-02    10

显然,这个简单任务有更易于理解的脚本,但我想要了解为什么在这里会出现这个问题。

这两个 dt 变量没有定义相同的数据框。如果我使用其中一个,我会得到一致的结果。 - Michael Griffiths
1个回答

7

您不能将(i)使用按引用修改的data.table函数和(ii)不修改引用的dplyr链混合使用,这样会导致以下情况发生:

library(dplyr)
library(data.table)
library(dtplyr)
library(lubridate)

dt = data.frame(id = c("a","b", "a"),
                date = ymd(c("2016-01-03","2016-01-02","2016-01-01")),
                value = c(10,5,9), stringsAsFactors = FALSE)

dt


  id       date value
1  a 2016-01-03    10
2  b 2016-01-02     5
3  a 2016-01-01     9


dt %>% select(id,value) %>% setDT(., key=c("id","value"))

dt


  id       date value
1  a 2016-01-03     9
2  a 2016-01-02    10
3  b 2016-01-01     5

所以被选中的列已经被setDT调用修改了。你可以把这看作是dtplyr在实现select时的一个bug,也可以看作是操作者的误用。无论如何,我建议一次只使用一种范式(个人而言,我只使用带有magrittr的data.table,从未遇到这些问题)。现在,你可以在你的链中添加一个copy

dt %>% select(id,value) %>% copy %>% setDT(., key=c("id","value"))

不过我猜你需要在很多地方都这样做。

谢谢提供信息。基本上是我的错误。我错误地认为dtplyr包能够解决这些问题。 - AntoniosK
我还不被你的回答所说服。你的链式操作示例没有保存/打印结果,它只是在原地更新了select()的输出,但没有分配给新变量,应该分配给新变量,因为select()的结果没有保存到任何变量中。对于copy也不确定,参见:dt %>% select(id,value) %>% setDT(., key=c("id","value")) -> ans1; dt %>% select(id,value) %>% copy %>% setDT(., key=c("id","value")) -> ans2; all.equal(ans1, ans2) - jangorecki
@jangorecki,我认为你的例子不起作用。第一个已经直接修改了“dt”,所以第二个没有机会使用原始的“dt”。我的建议不使用“<-”、“->”或“=”保存结果,但我猜OP可以想出如何做到这一点,或者你是指其他什么? - Frank
@AntoniosK 或许它的目的就是解决这些问题。你可以在 dtplyr 问题跟踪器上询问以澄清。我不太确定。 - Frank
@Frank,不是这样的,我在运行这些命令后看到了class(dt) # [1] "data.frame"setDT会就地更新,但不会更新dt,只会更新未存储在任何地方的select()输出。 - jangorecki
@jangorecki 好的,我明白你的意思了。我们在聊天中继续吧。我会在那里发布我正在查看的内容。 - Frank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接