如何使用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中作为多列操作?
答案 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
您可以使用:=
将这些值分配给新列。我不知道为什么tail
和head
没有更容易获得......正如@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
解决方案最快。如果你把我的复制品拿出来,那么它会更接近弗兰克的时间,但他仍然在这个测试案例中获胜。