2016-05-03 74 views
6

我想重塑一个data.table,并包含每个变量的历史(累计求和)信息。变量No指示对象ID的测量时间顺序。每次测量都会找到附加信息。我想汇总每个时间戳No处的对象ID的已知信息。用累计金额重塑data.table

让我用一个例子证明:

对于以下data.table:

df <- data.table(ID=c(1,1,1,2,2,2,2), 
       No=c(1,2,3,1,2,3,4), 
       Variable=c('a','b', 'a', 'c', 'a', 'a', 'b'), 
       Value=c(2,1,3,3,2,1,5)) 
df 
    ID No Variable Value 
1: 1 1  a  2 
2: 1 2  b  1 
3: 1 3  a  3 
4: 2 1  c  3 
5: 2 2  a  2 
6: 2 3  a  1 
7: 2 4  b  5 

我想它重塑这个:

 ID No a b c 
    1: 1 1 2 NA NA 
    2: 1 2 2 1 NA 
    3: 1 3 5 1 NA 
    4: 2 1 NA NA 3 
    5: 2 2 2 NA 3 
    6: 2 3 3 NA 3 
    7: 2 4 3 5 3 

所以Value的总和值,每Variable(ID, No),累计超过No

ID No a b c 
1: 1 1 2 NA NA 
2: 1 2 NA 1 NA 
3: 1 3 3 NA NA 
4: 2 1 NA NA 3 
5: 2 2 2 NA NA 
6: 2 3 1 NA NA 
7: 2 4 NA 5 NA 

任何想法如何使这个累积:

我可以通过做

dcast(df, ID+No~Variable, value.var="Value") 

这会导致非累积变异获得不累积部分的结果?原始data.table有超过250,000行,所以效率很重要。

编辑:我只是用a,b,c作为例子,原始文件有大约40个不同的级别。此外,NA是重要的;也有Value - 值0,这意味着比NA

可能的解决方案

好了别的东西,所以我找到了一个可行的解决方案。它远没有效率,因为它扩大了原来的桌子。

这个想法是复制每一行TotalNo - No次,其中TotalNo是最大NoID。然后,可以使用原始dcast函数来提取数据帧。所以在代码中:

df[,TotalNo := .N, by=ID] 
df2 <- df[rep(seq(nrow(df)), (df$TotalNo - df$No + 1))] #create duplicates 
df3 <- df2[order(ID, No)]#, No:= seq_len(.N), by=.(ID, No)] 
df3[,No:= seq(from=No[1], to=TotalNo[1], by=1), by=.(ID, No)] 
df4<- dcast(df3, 
      formula = ID + No ~ Variable, 
      value.var = "Value", fill=NA, fun.aggregate = sum) 

这不是很好,因为重复的创建使用更多的内存。我认为它可以进一步优化,但到目前为止它适用于我的目的。在示例代码中,它从7行到16行,原始文件从241,670行到978,331。这超过了4倍。

解决方案 Eddi改进了我在完整数据集中的计算时间的解决方案(2.02秒的Eddi与4.36秒的雷区)。这些是我可以合作的数字!谢谢大家!

+1

你可能做'的cols < - 排序(独特的(DF $变量) ); res < - dcast(df,ID + No〜Variable,value.var =“Value”,fill = 0L)[,(cols):= lapply(.SD,cumsum),.SDcols = cols,by = ID]'如果你不介意有零而不是'NA' –

+0

零不是一个选项;也有0的值具有与NA不同的含义。感谢您的贡献! –

回答

3

你的解决方案是好的,但如果你计算cumsum事先你要添加太多的行,即是不必要的:

# add useful columns 
df[, TotalNo := .N, by = ID][, CumValue := cumsum(Value), by = .(ID, Variable)] 

# do a rolling join to extend the missing values, and then dcast 
dcast(df[df[, .(No = seq(No[1], TotalNo[1])), by = .(ID, Variable)], 
     on = c('ID', 'Variable', 'No'), roll = TRUE], 
     ID + No ~ Variable, value.var = 'CumValue') 
# ID No a b c 
#1: 1 1 2 NA NA 
#2: 1 2 2 1 NA 
#3: 1 3 5 1 NA 
#4: 2 1 NA NA 3 
#5: 2 2 2 NA 3 
#6: 2 3 3 NA 3 
#7: 2 4 3 5 3 
2

这里有一个标准的方法:

library(zoo) 

df[, cv := cumsum(Value), by = .(ID, Variable)] 
DT = dcast(df, ID + No ~ Variable, value.var="cv") 

lvls = sort(unique(df$Variable)) 
DT[, (lvls) := lapply(.SD, na.locf, na.rm = FALSE), by=ID, .SDcols=lvls] 


    ID No a b c 
1: 1 1 2 NA NA 
2: 1 2 2 1 NA 
3: 1 3 5 1 NA 
4: 2 1 NA NA 3 
5: 2 2 2 NA 3 
6: 2 3 3 NA 3 
7: 2 4 3 5 3 
1

一种替代方式,可以使用定制的累积和功能做。这正是@David Arenburg的评论中的方法,但在自定义累积汇总函数中取代。

编辑:使用@ eddi更高效的自定义累加和功能。

cumsum.na <- function(z){ 
Reduce(function(x, y) if (is.na(x) && is.na(y)) NA else sum(x, y, na.rm = T), z, accumulate = T) 
} 

cols <- sort(unique(df$Variable)) 
res <- dcast(df, ID + No ~ Variable, value.var = "Value")[, (cols) := lapply(.SD, cumsum.na), .SDcols = cols, by = ID] 
res 

    ID No a b c 
1: 1 1 2 NA NA 
2: 1 2 2 1 NA 
3: 1 3 5 1 NA 
4: 2 1 NA NA 3 
5: 2 2 2 NA 3 
6: 2 3 3 NA 3 
7: 2 4 3 5 3 

这肯定不是最有效的,但它能够完成任务,并为您提供了一个处理的NA想要的方式无可否认的很慢很慢累计汇总函数。

+1

这将是疯狂的慢 - 你有一个双循环里面你的函数 – eddi

+0

感谢您的评论,但这并没有给我预期的输出...我试着替换'cumsum.na < - function(z){Reduce (函数(x,y)sum(x,y,na.rm = T),z,accumulate = T)}'我在第2行和第3行获得了0而不是NAs,列“c”。 –

+0

是真的,它不能正常工作 - 那么添加一个指标来跟踪你的循环中的第一个非NA值或smth,而不是添加第二个循环(只是为了澄清 - 第二个循环是all(is.na (x [1:i]))') – eddi