动态地对数据表进行子集化

时间:2013-10-26 15:35:04

标签: r data.table

我有一个关于动态子集化数据表的问题。我知道stackoverflow上有很多线程,它们的命名方式相似,但遗憾的是它们没有引导我找到想要的解决方案。

示例数据集:

require(data.table)
dt <- data.table(date=c(rep(1,5),rep(2,5)),id=rep(1:5,2),var=c(1:10))

对于每个ID ,我希望之前找到 所有期间的所有其他 ID的子集。在示例数据集中,有5个ID和两个句点。如果在时段2中查看ID = 5,则相应的子集将是ID = {1,2,3,4)和date = 1的子集。在这个简单的数据集中,我当然可以手工编写代码:

dt[,dt[-.I][date<2],by=id]

但我想自动这样做。我试过像

这样的东西
dt[,dt[-.I][date < unique(dt$date[.I])],by=id] 

但不幸的是,这不起作用。

任何有用的评论表示赞赏!谢谢!

3 个答案:

答案 0 :(得分:3)

您必须意识到组合会随着唯一日期/ ID数量的增加而爆炸。即使对于date = 1:10和id = 1:10,答案是4050行(需要0.7秒),而对于date = 1:50和id = 1:50,它已经是3001250行(需要6.2秒)。话虽如此,这应该按预期工作:

setkey(dt, date, id)
ans <- dt[!J(1), {d.tmp = date-1; id.tmp = id; dt[CJ(1:d.tmp, 
        setdiff(id, id.tmp))]}, by=list(date, id)]
setnames(ans, make.unique(names(ans)))
setkey(ans, date, id, date.1)

    date id date.1 id.1 var
 1:    2  1      1    2   2
 2:    2  1      1    3   3
 3:    2  1      1    4   4
 4:    2  1      1    5   5
 5:    2  2      1    1   1
 6:    2  2      1    3   3
 7:    2  2      1    4   4
 8:    2  2      1    5   5
 9:    2  3      1    1   1
10:    2  3      1    2   2
11:    2  3      1    4   4
12:    2  3      1    5   5
13:    2  4      1    1   1
14:    2  4      1    2   2
15:    2  4      1    3   3
16:    2  4      1    5   5
17:    2  5      1    1   1
18:    2  5      1    2   2
19:    2  5      1    3   3
20:    2  5      1    4   4

答案 1 :(得分:2)

我认为这是更快的解决方案:

dta <- data.table(date=c(rep(1,5),rep(2,5)),id=rep(1:5,2),var=c(1:10))
dta[,dta[dta[.I]$id!=dta$id & dta[.I]$date>dta$date],by=list(id,date)]

有关如何更快地制作此代码的任何评论都非常感谢。

答案 2 :(得分:0)

从1.9.8版开始(2016年11月25日,CRAN),data.table具有 non-equi-joins 的功能。

非装备自我加入

dta[dta, on = .(date > date), allow.cartesian = TRUE, nomatch = 0L, 
    .(id, x.date, i.date, i.id, i.var)][
      id != i.id][order(id)]
    id x.date i.date i.id i.var
 1:  1      2      1    2     2
 2:  1      2      1    3     3
 3:  1      2      1    4     4
 4:  1      2      1    5     5
 5:  2      2      1    1     1
 6:  2      2      1    3     3
 7:  2      2      1    4     4
 8:  2      2      1    5     5
 9:  3      2      1    1     1
10:  3      2      1    2     2
11:  3      2      1    4     4
12:  3      2      1    5     5
13:  4      2      1    1     1
14:  4      2      1    2     2
15:  4      2      1    3     3
16:  4      2      1    5     5
17:  5      2      1    1     1
18:  5      2      1    2     2
19:  5      2      1    3     3
20:  5      2      1    4     4

如Arun所指出的,组合随着唯一日期/ id数量的增加而爆炸。因此,必须设置allow.cartesian = TRUE

不幸的是,在非等位联接中,只能使用>=><=,<和==二进制运算符,但不能使用!=。因此,之后必须对联接的结果进行相等的id过滤。

基准

OP已发布his own answer,要求进一步加快代码速度。 Arun's answer包含针对不同问题大小的时间安排。

因此,下面的基准试图重复Arun的做法,并比较了迄今为止发布的三种不同方法。

library(bench)
library(magrittr)

bm <- press(
  n_date = c(2, 10, 50),
  n_id = c(5, 10, 50),
  {
    dt0 <- CJ(date = seq_len(n_date), id = seq_len(n_id))
    dt0[, var := .I]
    mark(
      arun = {
        dt <- copy(dt0)
        setkey(dt, date, id)
        dt[!J(1), {
          d.tmp = date-1
          id.tmp = id 
          dt[CJ(1:d.tmp, setdiff(id, id.tmp))]
        }, by=list(id, date)] -> arun
      },
      chameau13 = {
        dta <- copy(dt0)
        dta[,dta[dta[.I]$id!=dta$id & dta[.I]$date>dta$date],by=list(id,date)]
      },
      uwe = {
        dta <- copy(dt0)
        dta[dta, on = .(date > date), allow = TRUE, nomatch = 0L, 
            .(id = x.id, date = x.date, date.1 = i.date, id.1 = i.id, var = i.var)][
              id != id.1]
      },
      check = my_check
    )
  }
)

由于Arun的解决方案通过引用修改数据集,因此所有运行均从新副本开始。三种解决方案在列名和行顺序上有所不同。因此,使用自定义检查功能来确保结果相同:

my_check <- function(x, y) {
  setnames(x, make.unique(names(x)))
  setorder(x, id, date, date.1, id.1)
  setnames(y, make.unique(names(y)))
  setorder(y, id, date, date.1, id.1)
  all.equal(x, y, check.attributes = FALSE) %T>% 
    {if (!isTRUE(.)) print(.)}
}

基准时间可以通过以下方式可视化

ggplot2::autoplot(bm)

enter image description here

到目前为止,对于所有问题大小而言,非等额联接都是最快的方法,而尽管OP期望如此,OP自己的解决方案几乎总是最慢。