2016-09-29 75 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是的我更關心格式和可讀性比內存副本。這只是我而已 –