移動平均の基本
xにこんな感じでデータ入っているとき,移動平均は以下のように計算される。
(以下ではすべて,3つずつ計算,右詰め,の前提で書く)
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で実装するとこんな感じになる
library (tidyverse)
tibble (x = 1 : 6 ) |>
mutate (
rollsum = RcppRoll:: roll_sum (x, n = 3 L, align = 'right' , fill = NA ),
rollmean = RcppRoll:: roll_mean (x, n = 3 L, align = 'right' , fill = NA )
)
# A tibble: 6 × 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件に満たない場合でも欠損値扱いにせずに平均値を計算したい ,という場合もあるだろう。
(イメージ)
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
と条件式の組み合わせで突破していたが,あんまりきれいじゃない。
zoo::rollapply
を用いた実装
以上の問題を解決するにはzoo::rollapply()
が有効である。
この関数の引数で,partial = TRUE
とすると,以上の問題に対処できる。
詳しくはvignette を参照。
tibble (x = 1 : 6 ) |>
mutate (
rollsum = zoo:: rollapply (x, width = 3 L, FUN = sum, align = 'right' , partial = TRUE ),
rollmean = zoo:: rollapply (x, width = 3 L, FUN = mean, align = 'right' , partial = TRUE )
)
# A tibble: 6 × 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 = 3 L, FUN = sum, align = 'right' , fill = NA , partial = 2 L),
rollmean = zoo:: rollapply (x, width = 3 L, FUN = mean, align = 'right' , fill = NA , partial = 2 L)
)
# A tibble: 6 × 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 = 3 L, FUN = mean, align = 'right' , partial = TRUE )
[1] 1.0 1.5 2.0 3.0 4.0 5.0
zoo:: rollmean (1 : 6 , k = 3 L, align = 'right' , partial = TRUE )
まとめ
というわけで,移動平均の計算は通常時はRcppRoll
を使いつつ,ああいった特殊な場合にはzoo
を使えばよいということがわかった。RcppRoll
は早くpartial
を実装してほしいところ。