2016-09-29 4 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, группа В имела 2 наблюдения, последний разделен на 1 и предыдущего на 1. Далее в строке 6 результат: 1/1 + 4/2+ 3/3 = 4 так как во время 3, группа в имела 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

Если у вас есть запасная память, вы можете использовать декартовое соединение для предварительного распределения строк, чтобы операции, выполняемые внутри байтов, были более простыми и могут использовать оптимизацию GForce в data.table. Это может быть и не быстрее, чем другие решения, поскольку он в основном торгует памятью для использования более оптимизированного кода внутри.

> 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 Yep Мне больше нравится форматирование и читаемость, чем копия mem. Это только я, хотя –

Смежные вопросы