はじめに
『前処理大全』の4章で移動平均の計算が出てきたのでメモ。
移動平均の基本的な算出方法は「dplyrを使いこなす!Window関数編」などが詳しい。
移動平均の基本
xにこんな感じでデータ入っているとき,移動平均は以下のように計算される。
(以下ではすべて,3つずつ計算,右詰め,の前提で書く)
x | rollsum | rollmean |
---|---|---|
1 | NA | NA |
2 | NA | NA |
3 | 1+2+3 | (1+2+3)/3 |
4 | 2+3+4 | (2+3+4)/3 |
5 | 3+4+5 | (3+4+5)/3 |
6 | 4+5+6 | (4+5+6)/3 |
Rで実装するとこんな感じになる
> tibble(x = 1:6) %>%
+ mutate(rollsum = RcppRoll::roll_sum(x, n = 3L, align = 'right', fill = NA),
+ rollmean = RcppRoll::roll_mean(x, n = 3L, align = 'right', fill = NA))
# A tibble: 6 x 3
x rollsum rollmean
<int> <dbl> <dbl>
1 1 NA NA
2 2 NA NA
3 3 6 2
4 4 9 3
5 5 12 4
6 6 15 5
移動平均のかゆいところ
ここまでの移動平均は,値が3つ揃っているところだけ計算し,3つに満たない端っこのケースに関しては欠損で埋める,というルールで計算している。
ただし,場合によっては3件に満たない場合でも欠損値扱いにせずに平均値を計算したい,という場合もあるだろう。
(イメージ)
x | rollsum | rollmean |
---|---|---|
1 | 1 | 1/1 |
2 | 1+2 | (1+2)/2 |
3 | 1+2+3 | (1+2+3)/3 |
4 | 2+3+4 | (2+3+4)/3 |
5 | 3+4+5 | (3+4+5)/3 |
6 | 4+5+6 | (4+5+6)/3 |
『前処理大全』でもこのようなケースが扱われており,Rのサンプルコードではlag
と条件式の組み合わせで突破していたが,もちろんNot Awesomeである。
zoo::rollapply
を用いた実装
以上の問題を解決するにはzoo::rollapply()
が有効である。
この関数の引数で,partial = TRUE
とすると,以上の問題に対処できる。
詳しくはvignetteを参照。
> tibble(x = 1:6) %>%
+ mutate(rollsum = zoo::rollapply(x, width = 3L, FUN = sum, align = 'right', partial = TRUE),
+ rollmean = zoo::rollapply(x, width = 3L, FUN = mean, align = 'right', partial = TRUE))
# A tibble: 6 x 3
x rollsum rollmean
<int> <int> <dbl>
1 1 1 1
2 2 3 1.5
3 3 6 2
4 4 9 3
5 5 12 4
6 6 15 5
この場合1行目の値は和でも平均でも,元のデータがダイレクトに反映されることになる。
最低でも2つ以上の値の場合だけ計算したい!という時は,partial = 2L
のように,引数に整数を入れればよい。
tibble(x = 1:6) %>%
+ mutate(rollsum = zoo::rollapply(x, width = 3L, FUN = sum, align = 'right', fill = NA, partial = 2L),
+ rollmean = zoo::rollapply(x, width = 3L, FUN = mean, align = 'right', fill = NA, partial = 2L))
# A tibble: 6 x 3
x rollsum rollmean
<int> <int> <dbl>
1 1 NA NA
2 2 3 1.5
3 3 6 2
4 4 9 3
5 5 12 4
6 6 15 5
- また,なぜか
zoo::rollsum
やzoo::rollmean
でpartial = TRUE
としても上手くいかない。なぜ…?
> zoo::rollapply(1:6, width = 3L, FUN = mean, align = 'right', partial = TRUE)
[1] 1.0 1.5 2.0 3.0 4.0 5.0
> zoo::rollmean(1:6, k = 3L, align = 'right', partial = TRUE)
[1] 2 3 4 5
RcppRoll
にもpartial
があるが…
移動平均といえばRcppRoll
を使う人が多いはず。速度もzooより断然速いので基本的にはこちらを使うべきと筆者も思っている。
実はRcppRoll
の関数にもpartial
引数が入っている。
これを見た時に「これで行けるやん」と思ったのだが…
partial Partial application? Currently unimplemented.
https://cran.r-project.org/web/packages/RcppRoll/RcppRoll.pdf
実装されてませんでした…
まとめ
というわけで,移動平均の計算は通常時はRcppRoll
を使いつつ,ああいった特殊な場合にはzoo
を使えばよいということがわかった。RcppRoll
は早くpartial
を実装してほしいところ。