在data.table中采用列式差异

时间:2016-08-17 16:27:58

标签: r data.table difference

如何使用data.table语法生成data.table,其中每列包含原始data.table列与下一列之间的差异?

示例:我有一个data.table,其中每一行都是一个组,每一列都是在第0年之后,第1年,第2年等之后存活的人口。例如:

pop <- data.table(group_id = c(1, 2, 3), 
                   N = c(4588L, 4589L, 4589L), 
                   N_surv_1 = c(4213, 4243, 4264), 
                   N_surv_2 = c(3703, 3766, 3820), 
                   N_surv_3 = c(2953, 3054, 3159) )
# group_id    N N_surv_1 N_surv_2 N_surv_3
#        1 4588     4213     3703     2953
#        2 4589     4243     3766     3054
#        3 4589     4264     3820     3159

(数据类型不同,因为N是一个真正的整数计数,N_surv_1等是可能是小数的投影。)

我做了什么:使用基础diff和矩阵换位,我们可以:

diff <- data.table(t(diff(t(as.matrix(pop[,-1,with=FALSE])))))
setnames(diff, paste0("deaths_",1:ncol(diff)))
cbind(group_id = pop[,group_id],diff) 
# produces desired output:
#    group_id deaths_1 deaths_2 deaths_3
#           1     -375     -510     -750
#           2     -346     -477     -712
#           3     -325     -444     -661

我知道我可以在diff生成的单个列上按组使用基础melt.data.table,所以这样可行,但不是很好:

melt(pop, 
     id.vars = "group_id"
     )[order(group_id)][, setNames(as.list(diff(value)),
                                   paste0("deaths_",1:(ncol(pop)-2)) ),
                          keyby = group_id]

这是最常用的data.table-riffic方式,还是有办法在data.table中作为多列操作?

3 个答案:

答案 0 :(得分:6)

好吧,你可以减去子集:

ncols = grep("^N(_surv_[0-9]+)?", names(pop), value=TRUE)
pop[, Map(
  `-`, 
   utils:::tail.default(.SD, -1), 
   utils:::head.default(.SD, -1)
), .SDcols=ncols]

#    N_surv_1 N_surv_2 N_surv_3
# 1:     -375     -510     -750
# 2:     -346     -477     -712
# 3:     -325     -444     -661

您可以使用:=将这些值分配给新列。我不知道为什么tailhead没有更容易获得......正如@akrun指出的那样,您可以使用with=FALSE代替pop[, .SD[, -1, with=FALSE] - .SD[, -ncol(.SD), with=FALSE], .SDcols=ncols]。< / p>

无论如何,与简单重塑相比,这是非常复杂的:

melt(pop, id="group_id")[, tail(value, -1) - head(value, -1), by=group_id]
#    group_id   V1
# 1:        1 -375
# 2:        1 -510
# 3:        1 -750
# 4:        2 -346
# 5:        2 -477
# 6:        2 -712
# 7:        3 -325
# 8:        3 -444
# 9:        3 -661

答案 1 :(得分:2)

如果不重新整理数据和每个具有唯一ID的行,您可以按ID列进行分组,然后计算每行diff的差异,即unlist(.SD)

pop[, setNames(as.list(diff(unlist(.SD))), paste0("deaths_", 1:(ncol(pop)-2))), group_id]

#    group_id deaths_1 deaths_2 deaths_3
# 1:        1     -375     -510     -750
# 2:        2     -346     -477     -712
# 3:        3     -325     -444     -661

基本上,如果您忽略设置列名称,则类似这样的事情:

pop[, as.list(diff(unlist(.SD))), group_id]

答案 2 :(得分:2)

以下是另一种方法,无需重新整形或分组可能使其更快。如果它的行数很少,那么它可能不会有明显的差异。

cols<-names(pop)[-1]
combs<-list()
for(i in 2:length(cols)) {
  combs[[length(combs)+1]]<-c(cols[i-1], cols[i])
}
newnames<-sapply(combs,function(x) gsub('N_surv','death',x[2]))
deathpop<-copy(pop)
deathpop[,(newnames):=lapply(combs,function(x) get(x[2])-get(x[1]))]
deathpop[,(cols):=NULL]

我做了一些基准测试

rows<-10000000
pop <- data.table(group_id = 1:rows, 
                  N = runif(rows,3000,4000), 
                  N_surv_1 = runif(rows,3000,4000), 
                  N_surv_2 = runif(rows,3000,4000), 
                  N_surv_3 = runif(rows,3000,4000))
system.time({
    cols<-names(pop)[-1]
    combs<-list()
    for(i in 2:length(cols)) {
      combs[[length(combs)+1]]<-c(cols[i-1], cols[i])
    }
    newnames<-sapply(combs,function(x) gsub('N_surv','death',x[2]))
    deathpop<-copy(pop)
    deathpop[,(newnames):=lapply(combs,function(x) get(x[2])-get(x[1]))]
    deathpop[,(cols):=NULL]})

然后它返回

user  system elapsed 
0.192   0.808   1.003 

相反,我做了

system.time(pop[, as.list(diff(unlist(.SD))), group_id])

然后它返回

   user  system elapsed 
169.836   0.428 170.469 

我也做了

system.time({
  ncols = grep("^N(_surv_[0-9]+)?", names(pop), value=TRUE)
  pop[, Map(
    `-`, 
    utils:::tail.default(.SD, -1), 
    utils:::head.default(.SD, -1)
  ), .SDcols=ncols]
})

返回

 user  system elapsed 
0.044   0.044   0.089 

最后,做

system.time(melt(pop, id="group_id")[, tail(value, -1) - head(value, -1), by=group_id])

返回

   user  system elapsed 
223.360   1.736 225.315 

弗兰克Map解决方案最快。如果你把我的复制品拿出来,那么它会更接近弗兰克的时间,但他仍然在这个测试案例中获胜。