2016-09-29 76 views
7

好的,我想用一个高效优雅的解决方案(如data.table或dplyr)来解决这个问题。不同分母的分数累积和R

定义:

DT = data.table(group=c(rep("A",3),rep("B",5)),value=c(2,9,2,3,4,1,0,3)) 

    time group value 
1: 1  A  2 
2: 2  A  9 
3: 3  A  2 
4: 1  B  3  
5: 2  B  4 
6: 3  B  1 
7: 4  B  0 
8: 5  B  3 

我想要得到的是通过他们已经观察到时间的时刻通过逆顺序划分值的组累计总和。

time group value RESULT 
1: 1  A  2 2.000000 
2: 2  A  9 10.000000 
3: 3  A  2 7.166667 
4: 1  B  3 3.000000 
5: 2  B  4 5.500000 
6: 3  B  1 4.000000 
7: 4  B  0 2.583333 
8: 5  B  3 4.933333 

在管道5中的结果是: 4/1 + 3/2 = 5.5 因为在时刻2,基团B具有2个观察值,最后被除以1和在管线6中的结果是先前由1 下一页: 1/1 + 4/2+ 3/3 = 4 由于在时间3时,基团B具有3周的观察,最后是由1,以前的除以2及静止以前由3.在第7行,0/1 + 1/2 + 4/3 + 3/4 = 2.583333,等等...

的数据很大,所以避免循环是必不可少的!

回答

6

我会使用矩阵代数:

n_max = DT[, .N, by=group][, max(N)] 
m  = matrix(0, n_max, n_max) 
m[] = ifelse(col(m) >= row(m), 1/(col(m) - row(m) + 1), m) 

DT[, res := value %*% m[seq_len(.N), seq_len(.N)], by=group ] 

    group value  res 
1:  A  2 2.000000 
2:  A  9 10.000000 
3:  A  2 7.166667 
4:  B  3 3.000000 
5:  B  4 5.500000 
6:  B  1 4.000000 
7:  B  0 2.583333 
8:  B  3 4.933333 
3

您可以*apply横跨长度组的序列,使得序列索引value和,反相,通过将其分摊。随着dplyr

library(tidyverse) 

DT %>% group_by(group) %>% 
    mutate(result = sapply(seq(n()), function(x){sum(value[seq(x)]/rev(seq(x)))})) 

## Source: local data frame [8 x 3] 
## Groups: group [2] 
## 
## group value result 
## <fctr> <dbl>  <dbl> 
## 1  A  2 2.000000 
## 2  A  9 10.000000 
## 3  A  2 7.166667 
## 4  B  3 3.000000 
## 5  B  4 5.500000 
## 6  B  1 4.000000 
## 7  B  0 2.583333 
## 8  B  3 4.933333 

或使用purrr::map_dbl代替sapply

DT %>% group_by(group) %>% 
    mutate(result = map_dbl(seq(n()), ~sum(value[seq(.x)]/rev(seq(.x))))) 

返回同样的事情。您可以翻译相同的逻辑基础R,以及:

DT$result <- ave(DT$value, 
       DT$group, 
       FUN = function(v){sapply(seq_along(v), 
              function(x){sum(v[seq(x)]/rev(seq(x)))})}) 

DT 

## group value result 
## 1  A  2 2.000000 
## 2  A  9 10.000000 
## 3  A  2 7.166667 
## 4  B  3 3.000000 
## 5  B  4 5.500000 
## 6  B  1 4.000000 
## 7  B  0 2.583333 
## 8  B  3 4.933333 

虽然我没有基准,这些方法应该是足够快的大多数工作。不过,如果速度非常重要,我怀疑@弗兰克的答案可能会更快。

2

如果您有足够的内存空间,您可以使用笛卡尔联接来预先分配行,以便在by中完成的操作更简单,并且可以利用data.table的GForce优化。这可能会/可能不会比其他解决方案更快,因为它基本上交易内存以便在内部使用更优化的代码。

> DT[, .SD 
    ][DT, on='group', allow.cartesian=T 
    ][, setnames(.SD, 'i.time', 'groupRow') 
    ][time <= groupRow 
    ][, timeRev := .N:1, .(group, groupRow) 
    ][, res := value/timeRev 
    ][, .(res=sum(res)), .(group, groupRow, i.value) 
    ][, groupRow := NULL 
    ][, setnames(.SD, 'i.value', 'value') 
    ] 
    group value res 
1:  A  2 2.000 
2:  A  9 10.000 
3:  A  2 7.167 
4:  B  3 3.000 
5:  B  4 5.500 
6:  B  1 4.000 
7:  B  0 2.583 
8:  B  3 4.933 
> 
+1

'DT [,.SD]'和'DT'是一样的,所以你只是为了让括号很好地对齐? – Frank

+1

@Frank是的我更关心格式和可读性比内存副本。这只是我而已 –