移動平均のかゆいところに,partial = TRUE

R
Author

Kentaro Kamada

Published

October 25, 2020

はじめに

『前処理大全』の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で実装するとこんな感じになる

library(tidyverse)

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 × 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と条件式の組み合わせで突破していたが,あんまりきれいじゃない。

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 × 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 × 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::rollsumzoo::rollmeanpartial = 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を実装してほしいところ。