Существует ли быстрая функция R, такая как rollapplyr, с увеличением размера окна? - PullRequest
0 голосов
/ 30 января 2019

Я хочу вычислить сумму по скользящему окну для сгруппированных данных.

Поскольку я хотел бы придерживаться официальных функций, если это возможно, я начал с rollapplyr, как это:

library(tidyverse)
library(reshape2)
library(zoo)  

data = data.frame(Count=seq(1,10,1),
                  group=c("A","B","A","A","B","B","B","B","A","A"))


window_size <- 3    

data_rolling <- data %>%
  arrange(group) %>%
  group_by(group) %>%
  mutate(Rolling_Count = rollapplyr(Count, width=window_size, FUN=sum, fill = NA)) %>%
  ungroup()

для первых записей, которые меньше ширины (в данном случае 3), она заполняется NA, как определено, но на самом деле я хотел бы иметь сумму возможных данных там, как это:

 Count group Rolling_Count expected_Result
 1     A            NA    1
 3     A            NA    4
 4     A            8     8
 9     A            16    16
10     A            23    23
 2     B            NA    2
 5     B            NA    7
 6     B            13    13
 7     B            18    18
 8     B            21    21

Iзнаю, что я могу заменить width=window_size на что-то вроде этого:

c(rep(1:window_size,1),rep(window_size:window_size,(n()-window_size)))

, чтобы получить то, что я хочу, но это действительно медленно.Кроме того, этот подход предполагает, что n () больше, чем window_size.

Итак: Существует ли уже функция R / zoo, которая может обрабатывать сгруппированные данные, как указано выше, и, кроме того, данные с меньшим количеством записей window_size и быстрее по сравнению с вышеупомянутым подходом?

Спасибо за любые подсказки!

Ответы [ 2 ]

0 голосов
/ 30 января 2019

Вот еще одно решение, которое немного более базовое, и все же не должно отставать в производительности.Это может быть на самом деле быстрее, поскольку в нем отсутствуют все функции, которые добавляет roll функции.Мы могли бы заменить функцию shift из data.table на операцию base-R, тогда она должна быть самой быстрой, которую вы можете получить в базе R.
Обратите внимание, что эта функция будет плохо работать, если некоторые входные данные будут присутствовать на входе, такжеболее вероятно, что пострадает от ошибки округления с плавающей запятой.

data = data.frame(Count=seq(1,10,1),
                  group=c("A","B","A","A","B","B","B","B","A","A"))
window_size = 3

library(data.table)
setDT(data)
# base R fast rolling sum
bRfrs = function(x, n) {
  cumx = cumsum(x)
  cumx - shift(cumx, n, fill=0)
}
data[, .(Count, Rolling_Count=bRfrs(Count, window_size)), group]
#    group Count Rolling_Count
# 1:     A     1             1
# 2:     A     3             4
# 3:     A     4             8
# 4:     A     9            16
# 5:     A    10            23
# 6:     B     2             2
# 7:     B     5             7
# 8:     B     6            13
# 9:     B     7            18
#10:     B     8            21

В версии 1.12.4 data.table мы планируем уже добавить функцию frollsum, тогда это будет еще один высокопроизводительный вариант для достижения того, чтоВы ищете.

0 голосов
/ 30 января 2019

Решение, основанное на data.table и RcppRoll, которое должно быть намного более производительным.

Это не так чисто, как хотелось бы - на самом деле в RcppRoll::roll_sum() есть аргумент partial, который неЭто еще не было реализовано, что теоретически решило бы это чисто, но не похоже, что это будет работать в ближайшее время - см. GH Issue # 18 .

В любом случае, пока кто-то не введет скользящую сумму в R, которая позволит вам получить то, что вам нужно, добавление cumsum в первые n - 1 строки представляется разумным решением.

library(data.table)
library(RcppRoll)

data = data.frame(Count=seq(1,10,1),
                  group=c("A","B","A","A","B","B","B","B","A","A"))

## Convert to a `data.table` by reference
setDT(data)
window_size <- 3    

## Add a counter row so that we can go back and fill in rows
## 1 & 2 of each group
data[,Group_RowNumber := seq_len(.N), keyby = .(group)]

## Do a rolling window -- this won't fill in the first 2 rows
data[,Rolling_Count := RcppRoll::roll_sum(Count,
                                          n = window_size,
                                          align = "right",
                                          fill = NA), keyby = .(group)]

## Go back and fill in the ones we missed
data[Group_RowNumber < window_size, Rolling_Count := cumsum(Count), by = .(group)]

data

#     Count group Group_RowNumber Rolling_Count
#  1:     1     A               1             1
#  2:     3     A               2             4
#  3:     4     A               3             8
#  4:     9     A               4            16
#  5:    10     A               5            23
#  6:     2     B               1             2
#  7:     5     B               2             7
#  8:     6     B               3            13
#  9:     7     B               4            18
# 10:     8     B               5            21
...