大阪府独自の自粛基準となる「大阪モデル」にある「7日間移動平均」という考え方を「お絵かきアプリ」の作成で活用した話

こんにちは。ひらやま（[@rhirayamaaan](https://twitter.com/rhirayamaaan)）です。

現在、新型コロナウイルス感染拡大防止のため、緊急事態宣言が発令されています。（2020/05/09 現在）

自粛が続いている中ではありますが、自粛が終わるのを心待ちにしながら、STAY HOME を実施している方が多いと思います…

自分もそのうちの一人です…
早く外に出て今まで通りの日常に戻るといいですね…

そんな中、大阪府が**「[府独自の基準に基づく自粛要請・解除の基本的な考え方（案）](http://www.pref.osaka.lg.jp/attach/38215/00362708/031_shiryo3-1.pdf)」**というのを提示しました。

そこの2ページ目に、我々の大希望である「自粛解除」についてこんなことが書いてあります。

> 以下の②〜④の警戒信号全てが原則７日間連続消灯すれば、自粛等を段階的に解除。

（引用元：http://www.pref.osaka.lg.jp/attach/38215/00362708/031_shiryo3-1.pdf）

②〜④ というのは、以下の表のことです。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213205/2d56d7e2-d053-cd33-862c-cd252a4b229d.png)

（参照元：http://www.pref.osaka.lg.jp/attach/38215/00362708/031_shiryo3-1.pdf）

大阪府においては、表の一番右の列の「警戒信号消灯基準」が**7日間連続**で条件を満たせば、「自粛解除」を段階的に実施していきますよ。ということです。

ただ、「7日間連続ということは、②の項目で11人とか出ちゃったら、はいだめ〜〜ってことなの！？そんなの気が参っちゃうよ〜〜〜」と思われる方もいらっしゃると思います。

しかし、表の左から2番目の列の見出しに、以下のように書かれています。

> ※病床使用率以外の指標は７日間移動平均

（引用元：http://www.pref.osaka.lg.jp/attach/38215/00362708/031_shiryo3-1.pdf）

この「移動平均」というのが、上記に書いた「気が参っちゃうよ〜〜」とまではいかないものにしてくれるものです。

**今回はこの「移動平均」を掘り下げてみたいと思います。**

## 移動平均とは
まずは、府が補足してくれている、「7日間移動平均とは」のスライドをご覧ください。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213205/820df32f-87e7-e071-fca2-ba3e7ea6130b.png)

（参照元：http://www.pref.osaka.lg.jp/attach/38215/00362708/031_shiryo3-1.pdf）

我々が「気が参っちゃうよ〜〜」と思っていたのは、黄色い折れ線グラフで考えていたからです。
傾向としては4月末になると右下がりではありますが、日によっては右上がりのときもあります。
この基準で見ていては、「7日間連続」を満たすにはかなり時間がかかってしまいます。
正確に「7日間連続」を満たしたいわけではなく、右下がりの傾向が出ていることをキャッチする方が重要なはずです。

そこで、今回は「移動平均」という方法を採用しているのだと思います。
黒い線とオレンジの点の折れ線グラフが移動平均の値で作られたものです。

図にもありますが、例えば、3/28に移動平均の値を出すときは、3/21からの新規陽性者数を足し上げ、その後に7で割り算して平均値を出します。
3/29に移動平均値を出すときは、3/21のデータは使わず、3/22からの新規陽性者を足し上げて、7で割り、平均値を出します。

このように、区間を徐々に移動させて平均値を取っていくことを移動平均というわけです。

![movingAverage1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213205/69a4d43d-4576-e06b-eae0-4b0212db1c52.png)
※このデータはダミーです。何かを示しているわけではありませんのでご承知おきください。

凹凸のある連続したデータを**「ならして、なめらかにしたい」**際に有効そうだということがわかりました。

## 移動平均を活用してみたい
前置きが長くなりましたが、ここからが本題です！

ひょんなことから「移動平均」というものがあることを知れたので、「ならしてなめらかにしたい」値って何かないかなー？と考えていました。

そんなときにふと、JavaScript を触り始めたときのことを思い出したのです。
触り始めたときって「あ！こんなことできるんだ！すごーい！」と思いながらいろいろなんとなく触ってみると思うんですが、そのときってとりあえず「イベント」で遊ぶと思うんです。

クリック時 `alert` を叩いてみたりして、お〜とか言ったりしてみて。笑

そして、そのイベントの中に、`mousemove` というのもあるじゃないですか？

その `mousemove` イベントを `body` とかに付与して、マウスポインタの x と y の座標を、ただ `console.log` で吐き出すみたいなことをして遊んだことある人、結構多いのではないかなと！

その時の `mousemove` のイベントの発行量がめちゃくちゃ多い上に、ちょっとマウスを動かすだけすぐに値が変わるし、値が不安定だよな〜なんて思ったわけです。

そこで、ちょっとネットサーフィンをしまして、こんな記事に出会いました。

[【初心者】HTMLのcanvasとJavaScriptでお絵かきアプリ作る【ベース作り編】](https://tsuyopon.xyz/2018/09/14/how-to-create-drawing-app-part1/)

**これだ！！と思いました！笑**

上記の記事にある JS のコードを読んでいただければわかると思いますが、`mousemove` の際に値をそのまま `canvas` の `lineTo` 関数に代入しています。
（直接代入しているのは [こちら（コードの48行目）](https://github.com/tsuyopon-xyz/drawing_app_part1/blob/master/main.js#L48) です。`x` と `y` は、[こちら（コードの97行目）](https://github.com/tsuyopon-xyz/drawing_app_part1/blob/master/main.js#L97) で `layerX` と `layerY` を代入しているみたいです。）

もちろんこのままでも良いのですが、これに移動平均を採用したら、もうちょっとなめらかに書き心地の良いツールが作れるのではないか！？ これは大発見なんじゃないか！？ なんて一人で興奮していました。笑

## 実際に作ってみました
良さそうだったらぱっと作っちゃうのがいいですよね。
今はぱっとものづくりしてぱっと公開できるから本当にいい時代だなと心底思います。

gh-pages にデプロイしてあるので、以下から確認できます。
https://hiraryo0213.github.io/drawing-tool/
（※すみません、スマホは非対応です。。）

コードはこちらからどうぞ。
https://github.com/hiraryo0213/drawing-tool
（最初 Vanilla で書いていたのですが、DOM 生成のところでコードが多くなったしまったので React にしました。）

gh-pages にアクセスするとこんな画面が出てくると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213205/ad1c495e-0231-a304-ad09-0f4872501828.png)
この白い矩形のキャンバスに絵を描くことができます。

そして、今回重要なのは**「移動平均」を適用するとどうなるのか？**という点です。
なので、キャンバスの上に、数字を入力できる箇所を用意しました。

ここには、移動平均の区間を値を設定します。
ナンノコッチャだと思いますが、「7日間移動平均」の **`7`** にあたります。
この値が `0` だと移動平均はされない状態になります。逆に大きい数字をいれると、ならしとなめらかさが強化されていきます。
また、どんな文字でも入力できてしまいますが、**`0` と自然数**が入力される想定です。
（エラーハンドリングはちゃんとしていないのであまりいじめないであげてください…笑）

実際にやってみると、こんな感じになります。

### `0` のとき
![movingAverage1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213205/5c8bd803-a9a9-19ea-77d9-45874e0d8fa4.gif)

### `12`のとき
![movingAverage2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213205/0cb5dbc2-2860-72fd-9a4d-42645b0f77ee.gif)

`12` のときのほうがなめらかに描けていますね！！
（これを友人に試してもらったときに `12` が一番描きやすいと言われたので、`12` にしました。笑）

値を大きくすると、今度はならし過ぎてしまってマウスと描画が追いつかなくなるので、そのあたりも楽しんでいただければと思います！
（あまり値を大きくしすぎると処理が重くなってPCが耐えきれなくなるかもしれません。ご注意ください。）

## 移動平均のソースコード
お絵かきアプリの作り方は、上記で挙げた記事の方がわかりやすく説明してくださっているので説明は控えますが、移動平均のプログラムだけは説明します。

```typescript:MovingAverage.ts
export default class MovingAverage {
  private _sliceIndex = 1;

  private _movedArray: Array<number>;

  private _averagedValue: number;

  public get movedArray() {
    return this._movedArray;
  }

  public get averagedValue() {
    return this._averagedValue;
  }

  constructor(array: Array<number>, newValue: number, limit: number = 7) {
    // array が設定している区間よりも多い場合は、先頭側の値を削除して区間の値に揃える
    // 逆に区間より少ない場合には、特に値は削除しない。
    if (array.length > limit) {
      this._sliceIndex = array.length - limit + 1;
    } else if (array.length < limit) {
      this._sliceIndex = 0;
    }

    this._movedArray = [...array.slice(this._sliceIndex), newValue];

    this._averagedValue = this._movedArray.reduce((prev, current) => prev + current) / this._movedArray.length;

    return this;
  }
}
```

作った class はこんな感じです。

上記で出した例の画像をもとに、利用方法を説明します。
![movingAverage1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213205/69a4d43d-4576-e06b-eae0-4b0212db1c52.png)
※9/1〜9/7の配列をもとに、9/2〜9/8の移動平均を求めるものとします。

```typescript
const movingAverage = new MovingAverage([5, 9, 13, 2, 15, 11, 6], 8, 7);

console.log(movingAverage.movedArray); // output: [9, 13, 2, 15, 11, 6, 8]
console.log(movingAverage.averagedValue); // output: 9.142857143
```

第一引数には、元となる配列を入れます。
第二引数には、新しく追加したい値を入れます。今回は9/8の `8` が入ります。
第三引数には、移動平均の区間値を入れます。こちらは任意です。例だと7日間なので `7` を入れます。

そうすると、`movedArray` には新しく生成された配列が入っています。これが、9/2〜9/8の配列となります。
実際の平均値ももちろんほしいので、それは `ageragedValue` に値が入っています。

もし仮に、8個以上の要素を持った配列が渡された場合は、先頭側の要素を削除して、最後に `newValue` を結合し、新しい配列を返します。
逆に、7個未満の要素を持った配列の場合は、そのまま `newValue` を結合して配列を返します。

平均値を出すときに、配列の値を足し上げない必要がありますが、その際は `reduce()` を使うと大変楽です。
`averagedValue` を出す際の式が一行で書けて大変シンプルになりました。

お絵かきアプリではなくても、この関数は使えますので、ぜひ移動平均したい場合はご活用ください＾＾

## さいごに
自粛ムードの中、ニュースも色々と殺伐としたものが多いです。
しかし、そのニュースの中にはいろいろなデータがあり、そのデータをどう活用するかを考えると、それを解決するためのロジックが存在し、そこにはアイデアが詰まっているのだと思います。

今回は「移動平均」というのを取り上げて自分なりに作ってみましたが、他にもおそらくいろんなものづくりのヒントがあって、そこから新しい何かを生み出せたり、逆に理解が深まったりと、学びの機会がたくさんあるのだろうな〜と思いました。

案外視点を変えてみると、自分の持っている知識の領域に、今のこの状況を巻き込めるかもしれません！
「なにか楽しいことないかな〜」と主体的に探していくことは、ものづくりの鍵につながるかもなと、今回ちょっとものを作ってみて思ったので、ぜひみなさんもなにかものづくりをしてみましょ〜〜