diff --git a/src/Helper/RunningVariance.php b/src/Helper/RunningVariance.php index fde558e..6d0d86f 100644 --- a/src/Helper/RunningVariance.php +++ b/src/Helper/RunningVariance.php @@ -39,6 +39,12 @@ class RunningVariance */ private int $count = 0; + /** The smallest observed value */ + private float $min = NAN; + + /** The largest observed value */ + private float $max = NAN; + /** First moment: the mean value. */ private float $mean = 0.0; @@ -61,6 +67,19 @@ public function observe(float $value): float $this->mean += $delta / $this->count; $this->m2 += $delta * ($value - $this->mean); + if (1 === $this->count) { + $this->min = $value; + $this->max = $value; + } else { + if ($value < $this->min) { + $this->min = $value; + } + + if ($value > $this->max) { + $this->max = $value; + } + } + return $value; } @@ -72,6 +91,18 @@ public function getCount(): int return $this->count; } + /** The smallest observed value */ + public function getMin(): float + { + return $this->min; + } + + /** The largest observed value */ + public function getMax(): float + { + return $this->max; + } + /** * Get the mean value. */ @@ -129,6 +160,8 @@ private function merge(self $other): void $this->count = $other->count; $this->mean = $other->mean; $this->m2 = $other->m2; + $this->min = $other->min; + $this->max = $other->max; return; } @@ -139,5 +172,13 @@ private function merge(self $other): void $this->mean = ($this->count * $this->mean) / $count + ($other->count * $other->mean) / $count; $this->m2 = $this->m2 + $other->m2 + ($delta ** 2 * $this->count * $other->count / $count); $this->count = $count; + + if ($other->min < $this->min) { + $this->min = $other->min; + } + + if ($other->max > $this->max) { + $this->max = $other->max; + } } } diff --git a/tests/Helper/RunningVarianceTest.php b/tests/Helper/RunningVarianceTest.php index dd6d5e2..5d802a9 100644 --- a/tests/Helper/RunningVarianceTest.php +++ b/tests/Helper/RunningVarianceTest.php @@ -51,6 +51,8 @@ public function testEmpty(): void $this->assertSame(0, $variance->getCount()); $this->assertNan($variance->getMean()); + $this->assertNan($variance->getMin()); + $this->assertNan($variance->getMax()); $this->assertNan($variance->getVariance()); $this->assertNan($variance->getStandardDeviation()); } @@ -72,10 +74,26 @@ public function testOne(): void $this->assertSame(1, $variance->getCount()); $this->assertSame(M_PI, $variance->getMean()); + $this->assertSame(M_PI, $variance->getMin()); + $this->assertSame(M_PI, $variance->getMax()); $this->assertSame(0.0, $variance->getVariance()); $this->assertSame(0.0, $variance->getStandardDeviation()); } + public function testOneNegative(): void + { + $variance = new RunningVariance(); + $variance->observe(-1.01); + + $this->assertSame(1, $variance->getCount()); + $this->assertSame(-1.01, $variance->getMean()); + $this->assertSame(-1.01, $variance->getMin()); + $this->assertSame(-1.01, $variance->getMax()); + $this->assertSame(0.0, $variance->getVariance()); + $this->assertSame(0.0, $variance->getStandardDeviation()); + } + + public function testTwo(): void { $variance = new RunningVariance(); @@ -84,6 +102,8 @@ public function testTwo(): void $this->assertSame(2, $variance->getCount()); $this->assertSame(M_PI, $variance->getMean()); + $this->assertSame(M_PI, $variance->getMin()); + $this->assertSame(M_PI, $variance->getMax()); $this->assertSame(0.0, $variance->getVariance()); $this->assertSame(0.0, $variance->getStandardDeviation()); } @@ -96,6 +116,8 @@ public function testNAN(): void $this->assertSame(2, $variance->getCount()); $this->assertNan($variance->getMean()); + $this->assertSame(M_PI, $variance->getMin()); + $this->assertSame(M_PI, $variance->getMax()); $this->assertNan($variance->getVariance()); $this->assertNan($variance->getStandardDeviation()); } @@ -111,6 +133,8 @@ public function testFive(): void $this->assertSame(5, $variance->getCount()); $this->assertSame(5.0, $variance->getMean()); + $this->assertSame(2.0, $variance->getMin()); + $this->assertSame(8.0, $variance->getMax()); $this->assertEqualsWithDelta(5.0, $variance->getVariance(), 0.0001); $this->assertEqualsWithDelta(sqrt(5.0), $variance->getStandardDeviation(), 0.0001); } @@ -128,6 +152,8 @@ public function testCopy(): void $this->assertSame(5, $variance->getCount()); $this->assertSame(5.0, $variance->getMean()); + $this->assertSame(2.0, $variance->getMin()); + $this->assertSame(8.0, $variance->getMax()); $this->assertEqualsWithDelta(5.0, $variance->getVariance(), 0.0001); $this->assertEqualsWithDelta(sqrt(5.0), $variance->getStandardDeviation(), 0.0001); } @@ -145,6 +171,8 @@ public function testFiveMerged(): void $this->assertSame(5, $variance->getCount()); $this->assertSame(5.0, $variance->getMean()); + $this->assertSame(2.0, $variance->getMin()); + $this->assertSame(8.0, $variance->getMax()); $this->assertEqualsWithDelta(5.0, $variance->getVariance(), 0.0001); $this->assertEqualsWithDelta(sqrt(5.0), $variance->getStandardDeviation(), 0.0001); } @@ -164,6 +192,8 @@ public function testFiveMergedTwice(): void $this->assertSame(5, $variance->getCount()); $this->assertSame(5.0, $variance->getMean()); + $this->assertSame(2.0, $variance->getMin()); + $this->assertSame(8.0, $variance->getMax()); $this->assertEqualsWithDelta(5.0, $variance->getVariance(), 0.0001); $this->assertEqualsWithDelta(sqrt(5.0), $variance->getStandardDeviation(), 0.0001); } @@ -185,6 +215,8 @@ public function testFiveMergedThrice(): void $this->assertSame(5, $variance->getCount()); $this->assertSame(5.0, $variance->getMean()); + $this->assertSame(2.0, $variance->getMin()); + $this->assertSame(8.0, $variance->getMax()); $this->assertEqualsWithDelta(5.0, $variance->getVariance(), 0.0001); $this->assertEqualsWithDelta(sqrt(5.0), $variance->getStandardDeviation(), 0.0001); }