Скользящее средневзвешенное значение по двум уровням фактора или моментам времени

Я хотел бы создать скользящее среднее значение за 2 квартала для альфы, браво и чарли (и многих других переменных. Исследования приводят меня к зоопарку и смазке пакетов, но, кажется, всегда возвращаюсь к скользящему внутри одной переменной или группировке

set.seed(123)

dates <-  c("Q4'15", "Q1'16", "Q2'16","Q3'16", "Q4'16", "Q1'17", "Q2'17" ,"Q3'17", "Q4'17","Q1'18")

df <- data.frame(dates = sample(dates, 100,  replace = TRUE, prob=rep(c(.03,.07,.03,.08, .05),2)), 
                           alpha = rnorm(100, 5), bravo = rnorm(100, 10), charlie = rnorm(100, 15))

Я ищу что-то вроде

x <- df %>% mutate_if(is.numeric, funs(rollmean(., 2, align='right', fill=NA)))

Желаемый результат: средневзвешенное значение по "Q4'15" и "Q1'16", "Q1'16" и "Q2'16" и т. д. для каждого столбца данных (альфа, браво, чарли). Не ищите среднее значение парных квартальных средних.

Вот каковы были бы средние значения для временной точки Q4'15 & "Q1'16".

df %>% filter(dates %in% c("Q4'15", "Q1'16")) %>%  select(-dates) %>% summarise_all(mean)

person Michael Bellhouse    schedule 07.06.2018    source источник
comment
Было бы здорово, если бы пример был немного меньше и имел ожидаемый результат.   -  person akrun    schedule 07.06.2018
comment
попытался уточнить желаемый результат в вопросе   -  person Michael Bellhouse    schedule 07.06.2018
comment
Похоже, вы можете легко сделать это через цикл, но вы не хотите использовать цикл?   -  person Feng Jiang    schedule 07.06.2018
comment
Было бы лучше, чтобы данные вашего примера были короткими. Кроме того, поскольку вы рассматриваете dates для нахождения среднего квартального (скользящего, 2-го квартала), поэтому имеет смысл сохранить столбец dates, содержащий дату, вместо factor. Пожалуйста, обновите пример правильно.   -  person MKR    schedule 07.06.2018
comment
сделано. @Jfly спасибо, это жизнеспособная стратегия, хотя да, я бы предпочел добавить в цепочку dplyr, если это возможно. Всем спасибо за поиск   -  person Michael Bellhouse    schedule 08.06.2018


Ответы (1)


Мне нравится data.table за это, и у меня есть решение для вас, но может быть и более элегантное. Вот что у меня есть:

Данные

Теперь как data.table:

R> suppressMessages(library(data.table))
R> set.seed(123)
R> datesvec <- c("Q4'15", "Q1'16", "Q2'16","Q3'16", "Q4'16",
+               "Q1'17", "Q2'17" ,"Q3'17", "Q4'17","Q1'18")
R> df <- data.table(dates = sample(dates, 100,  replace = TRUE,
+                                 prob=rep(c(.03,.07,.03,.08, .05),2)),
+                  alpha = rnorm(100, 5),
+                  bravo = rnorm(100, 10),
+                  charlie = rnorm(100, 15))
R> df[ , ind := which(datesvec==dates), by=dates]
R> setkey(df, ind)  # optional but may as well
R> head(df)
   dates   alpha    bravo charlie ind
1: Q4'15 5.37964 11.05271 14.4789   1
2: Q4'15 7.05008 10.36896 15.0892   1
3: Q4'15 4.29080 12.12845 13.6047   1
4: Q4'15 5.00576  8.93667 13.3325   1
5: Q4'15 3.53936  9.81707 13.6360   1
6: Q1'16 3.45125 10.56299 16.0808   2
R> 

Ключевым моментом здесь является то, что нам нужно восстановить/поддерживать временной порядок ваших кварталов, которого нет в вашем представлении данных.

Среднее по кварталу

Это легко с data.table:

R> ndf <- df[ ,
+           .(qtr=head(dates,1),          # label of quarter
+             sa=sum(alpha),              # sum of a in quarter
+             sb=sum(bravo),              # sum of b in quarter
+             sc=sum(charlie),            # sum of c in quarter
+             n=.N),                      # number of observations
+           by=ind]
R> ndf
    ind   qtr      sa       sb       sc  n
 1:   1 Q4'15 25.2656  52.3039  70.1413  5
 2:   2 Q1'16 65.8562 132.6650 192.7921 13
 3:   3 Q2'16 10.3422  17.8061  31.3404  2
 4:   4 Q3'16 84.6664 168.1914 256.9010 17
 5:   5 Q4'16 41.3268  87.8253 139.5873  9
 6:   6 Q1'17 42.6196  85.4059 134.8205  9
 7:   7 Q2'17 76.5190 162.0784 241.2597 16
 8:   8 Q3'17 42.8254  83.2483 127.2600  8
 9:   9 Q4'17 68.1357 133.5794 198.1920 13
10:  10 Q1'18 37.0685  78.4107 120.2808  8
R> 

Отстаньте от этих средних значений один раз

R> ndf[, `:=`(psa=shift(sa),               # previous sum of a
+            psb=shift(sb),               # previous sum of b
+            psc=shift(sc),                # previous sum of c
+            pn=shift(n))]                # previous nb of obs
R> ndf
    ind   qtr      sa       sb       sc  n     psa      psb      psc pn
 1:   1 Q4'15 25.2656  52.3039  70.1413  5      NA       NA       NA NA
 2:   2 Q1'16 65.8562 132.6650 192.7921 13 25.2656  52.3039  70.1413  5
 3:   3 Q2'16 10.3422  17.8061  31.3404  2 65.8562 132.6650 192.7921 13
 4:   4 Q3'16 84.6664 168.1914 256.9010 17 10.3422  17.8061  31.3404  2
 5:   5 Q4'16 41.3268  87.8253 139.5873  9 84.6664 168.1914 256.9010 17
 6:   6 Q1'17 42.6196  85.4059 134.8205  9 41.3268  87.8253 139.5873  9
 7:   7 Q2'17 76.5190 162.0784 241.2597 16 42.6196  85.4059 134.8205  9
 8:   8 Q3'17 42.8254  83.2483 127.2600  8 76.5190 162.0784 241.2597 16
 9:   9 Q4'17 68.1357 133.5794 198.1920 13 42.8254  83.2483 127.2600  8
10:  10 Q1'18 37.0685  78.4107 120.2808  8 68.1357 133.5794 198.1920 13
R> 

Среднее значение за текущий и предыдущий квартал

R> ndf[is.finite(psa),                     # where we have valid data
+     `:=`(ra=(sa+psa)/(n+pn),            # total sum / total n == avg
+          rb=(sb+psb)/(n+pn),
+          rc=(sc+psc)/(n+pn))]
R> ndf[,c(1:2, 11:13)]
    ind   qtr      ra       rb      rc
 1:   1 Q4'15      NA       NA      NA
 2:   2 Q1'16 5.06233 10.27605 14.6074
 3:   3 Q2'16 5.07989 10.03141 14.9422
 4:   4 Q3'16 5.00045  9.78935 15.1706
 5:   5 Q4'16 4.84589  9.84680 15.2496
 6:   6 Q1'17 4.66369  9.62395 15.2449
 7:   7 Q2'17 4.76554  9.89937 15.0432
 8:   8 Q3'17 4.97268 10.22195 15.3550
 9:   9 Q4'17 5.28386 10.32513 15.4977
10:  10 Q1'18 5.00972 10.09476 15.1654
R> 

используя тот факт, что общая сумма за два квартала, деленная на общее количество наблюдений, равна среднему значению этих двух кварталов. (И это отражает редактирование, следующее за моим более ранним размышлением).

Выборочная проверка

Мы можем использовать функцию выбора data.table для вычисления двух из этих строк вручную, я выбрал их для индексов <1,2> и <4,5> здесь:

R> df[ ind <= 2, .(a=mean(alpha), b=mean(bravo), c=mean(charlie))]
         a      b       c
1: 5.06233 10.276 14.6074
R> df[ ind == 4 | ind == 5, .(a=mean(alpha), b=mean(bravo), c=mean(charlie))]
         a      b       c
1: 4.84589 9.8468 15.2496
R> 

Это хорошо работает, и подход должен легко масштабироваться до миллионов строк благодаря data.table.

ПС: все в одном

Как вы упомянули каналы и т. д., вы можете написать все это с помощью связанных data.table операций. Не мой предпочтительный стиль, но возможно. Следующее создает точно такой же вывод без создания временного ndf, как указано выше:

## All in one
df[ , ind := which(datesvec==dates), by=dates][
   ,
    .(qtr=head(dates,1),          # label of quarter
      sa=sum(alpha),              # sum of a in quarter
      sb=sum(bravo),              # sum of b in quarter
      sc=sum(charlie),            # sum of c in quarter
      n=.N),                      # number of observations
    by=ind][
   ,
    `:=`(psa=shift(sa),               # previous sum of a
         psb=shift(sb),               # previous sum of b
         psc=shift(sc),                # previous sum of c
         pn=shift(n))][
    is.finite(psa),                     # where we have valid data
    `:=`(ra=(sa+psa)/(n+pn),            # total sum / total n == avg
         rb=(sb+psb)/(n+pn),
         rc=(sc+psc)/(n+pn))][
    ,c(1:2, 11:13)][]
person Dirk Eddelbuettel    schedule 09.06.2018
comment
Спасибо. Кстати, может быть и не так, что среднее за два квартала = среднее из двух квартальных средних. Я попытался выразить это в своем первоначальном вопросе, не ища среднего значения парных квартальных средних значений. - person Michael Bellhouse; 10.06.2018
comment
Фихтре. Вы правы, когда у них разные значения. Данг. Что еще лучше, вы можете фактически построить sum и nobs для каждой из четвертей, и тогда это будет (sum_i + sum_j) / (nobs_i + nobs_j) для всех пар <i,j>. Это должно держаться. - person Dirk Eddelbuettel; 10.06.2018
comment
Извините, если это было не совсем ясно. Я не знаю таблицы данных (например, я не знаю, что такое nob), но стремлюсь узнать больше, так как я продолжаю видеть области, где это было бы более просто, чем dplyr. Имея все это в виду, хотели бы вы отредактировать свой ответ на основы создания скользящих средних по всем записям? Я иду ужинать и приму ваш ответ сегодня вечером. Кстати, было бы здорово, если бы код можно было легко изменить, скажем, на 4 квартала, если это необходимо. - person Michael Bellhouse; 10.06.2018
comment
nobs = количество наблюдений - person Dirk Eddelbuettel; 10.06.2018
comment
Выложена исправленная версия. - person Dirk Eddelbuettel; 10.06.2018
comment
Спасибо за полезный код для дальнейшего изучения с data.table, я принимаю ваш ответ с признательностью за вашу помощь. - person Michael Bellhouse; 10.06.2018
comment
Не за что. Я также добавил решение «все в одном». Не обязательно рекомендуется, но если нужно, то можно... - person Dirk Eddelbuettel; 10.06.2018