数学 に関する投稿を表示しています

算術平均を逐次計算したい。

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

数式をこねくりまわす知識や技術はないので自分で求められないのですが、平均値って逐次計算できるのかな?を調べたメモ。

x = [...] // 元の数列
z = ...   // 追加したい数

// 平均値
Average(x) = Sum(x) / Count(x)

// 数を追加した平均値
Average(x + z) = Average(x) + ((z - Average(x)) / Count(x))

調べていたらこういう式も。平均に個数をかけて数を足し、そこから新しい個数で割れば、新しい平均だよね、というもの。そりゃそうだわ。

Average(x + z) = (Average(x) * Count(x) + z) / Count(x + z)

どちらにしても個数と平均値だけ持っておけば逐次計算できますね。後者は掛け算するので、個数や平均値が大きすぎると扱えなさそうなので、前者のほうがよさそう。

試しに実装してみるとこういう形。

class MeanStream
{
    private $mean = 0;
    private $dataCount = 0;

    public function __construct() {
    }

    public function add($num) {
        $this->dataCount++;
        $this->mean = $this->mean + (($num - $this->mean) / $this->dataCount);
        return $this;
    }

    public function get() {
        return $this->mean;
    }
}

echo new MeanStream()
    ->add(1)
    ->add(7)
    ->add(8)
    ->add(6)
    ->add(2)
    ->add(5)
    ->add(1)
    ->add(8)
    ->add(9)
    ->add(4)
    ->get();

echo (1+7+8+6+2+5+1+8+9+4) / 10;
// 5.1

あってますねー

 
ちなみに、当然のことながら上記の例だと浮動小数点数を使うので、使う値によっては表現の都合で微妙に数値が変わるのでその点だけ注意ですね。

$values = [];
for($i = 0; $i < 10; $i++) {
    $values[] = mt_rand();
}

$mean = new MeanStream();
foreach ($values as $value) {
    $mean->add($value);
}

echo sprintf('%.30f', array_sum($values) / count($values));
// 995988838.500000000000000000000000000000

echo sprintf('%.30f', $mean->get());
// 995988838.499999880790710449218750000000

PHP じゃない言語で実装するとして、もし Decimal のような、よろしく精度をもってくれる機能があればそれを使ってあげるほうがよさそうです。PHP は言語として標準に Decimal がないのでどうするんだろう、 bcmath とか gmp というのがありそうだけど extension に左右されるなー。まあ composer.json で extension について依存あるよーと記載すればいいのか。
PHP: 数学 - Manual

Packagist みると幾つかありそうなので、このあたりを使ったほうがいいのかなー。

 
と、話がそれてきたあたりでおしまい。ライブラリの話はまた今度。

前後の記事

Next:
Prev: