一つ前の平均値と新しい値と要素数で、新しい平均値を算出する
やりたいこと
平均値を全ての要素を覚えずに累積させて計算したい。
例えば、以下の値の平均を知りたい。
$ 80, 45, 7
この平均値は合計と要素数から以下のように$ 44。
$ \frac{80 + 45 + 7}{3}= 44
つぎ、新しく$ 12がやってきて、それを加えた状態で再度平均値を求めたくなった。
通常は、以下のように同様に計算して平均値$ 36と求める。
$ \frac{80 + 45 + 7 + 12}{4}= 36
通常、プログラム上で実装するときは[80, 45, 7]のような配列を用意して、あたらしい要素12が来たら配列に加えて、再度平均を計算すると思う。
ただ[80, 45, 7]が増えていけばメモリも食うし、要素数がすごく多いと足し算のコストもあると思う。
そこで、前回の平均値だけで次の平均値が求められると嬉しい。
やりかた
最初の平均値は$ 44だった。
そして新しい値は$ 12だった。
そしてこの時の要素数は$ 4つだった。
これらを使うと$ \frac{80 + 45 + 7 + 12}{4}= 36と同じ値$ 36を求めることができる。
以下のように求める。
$ \frac{ (今の要素数 - 1) \times 前の平均 + 新しい値 }{今の要素数}
$ = \frac{ (4 - 1) \times 44 + 12 }{4}
$ = 36
仕組み
一般化するために$ a_1, a_2, \cdots , a_nの平均値を$ Avg_nする。
$ \frac{a_1 + a_2 + \cdots + a_n}{n} = Avg_n
この両辺に$ nを掛けて左辺を$ a_1, a_2, ... , a_nの和の形にする。
$ a_1 + a_2 + \cdots + a_n = nAvg_n
つぎに新しい要素$ a_{n+1}を加えた新しい平均値$ Avg_{n+1}は以下のように表せる。
$ Avg_{n+1} = \frac{a_1 + a_2 + \cdots + a_n + a_{n+1}}{n+1}
ここで$ a_1 + a_2 + ... + a_n が$ nAvg_nで表せるため置き換えると以下のように変形できる。
$ Avg_{n+1} = \frac{nAvg_n + a_{n+1}}{n+1}
この状態になると古い要素を全て記憶する必要がなくなり、新しい平均$ Avg_{n+1}を計算するのに必要な値は、
前の要素数$ nと
前の平均値$ Avg_nと
新しい要素$ a_{n+1}
だということが分かる。
そして全ての要素$ a_1, a_2, \cdots , a_nを記憶しておく必要がなくなる。
おまけ
code:js
const avg = a.reduce((a, b) => a + b) / a.length;
console.log(avg);
const newValue = 12;
const n = 4;
const newAvg = ((n-1) * avg + newValue) / n;
console.log(newAvg);
自明だと思って今までも使ってきたけど、解説みたことはない気がするのでまとめた。
浮動小数点が絡むと全体を足して1回割る普通の方法と比べて誤差は生まれるとは思う。