Skip to content

Commit a132094

Browse files
committed
Added statistics utility class
1 parent ea2b8d2 commit a132094

File tree

4 files changed

+293
-0
lines changed

4 files changed

+293
-0
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
### 11.1.0 <small>(2025-??-??)</small>
2+
3+
#### New
4+
5+
* Added a statistics utility class with the following methods:
6+
- `Statistics::mean()`
7+
- `Statistics::median()`
8+
- `Statistics::mode()`
9+
- `Statistics::midrange()`
10+
- `Statistics::sampleVariance()`
11+
- `Statistics::populationVariance()`
12+
13+
#### Changes
14+
15+
* Command argument aliases are now shown in a separate column.
16+
117
### 11.0.0 <small>(2025-01-03)</small>
218

319
The major version bump is due to upping the required PHP version from `8.1` to `8.4` and a several breaking changes. Most applications built using Mako `10` should run on Mako `11` with just a few simple adjustments.

src/mako/utility/Statistics.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
/**
4+
* @copyright Frederic G. Østby
5+
* @license http://www.makoframework.com/license
6+
*/
7+
8+
namespace mako\utility;
9+
10+
use mako\utility\exceptions\StatisticsException;
11+
12+
use function array_count_values;
13+
use function array_keys;
14+
use function array_sum;
15+
use function count;
16+
use function max;
17+
use function min;
18+
use function sort;
19+
20+
/**
21+
* Class containing statistic helper methods.
22+
*/
23+
class Statistics
24+
{
25+
/**
26+
* Returns the mean of the numbers in the array.
27+
*/
28+
public static function mean(array $numbers): float|int
29+
{
30+
$count = count($numbers);
31+
32+
if ($count === 0) {
33+
throw new StatisticsException('The array can not be empty.');
34+
}
35+
36+
return array_sum($numbers) / $count;
37+
}
38+
39+
/**
40+
* Returns the median of the numbers in the array.
41+
*/
42+
public static function median(array $numbers): float|int
43+
{
44+
$count = count($numbers);
45+
46+
if ($count === 0) {
47+
throw new StatisticsException('The array can not be empty.');
48+
}
49+
50+
sort($numbers);
51+
52+
$middle = (int) ($count / 2);
53+
54+
if ($count % 2 === 0) {
55+
return ($numbers[$middle - 1] + $numbers[$middle]) / 2;
56+
}
57+
58+
return $numbers[$middle];
59+
}
60+
61+
/**
62+
* Returns the mode of the values in the array.
63+
*/
64+
public static function mode(array $values): mixed
65+
{
66+
if (empty($values)) {
67+
throw new StatisticsException('The array can not be empty.');
68+
}
69+
70+
$frequency = array_count_values($values);
71+
72+
return array_keys($frequency, max($frequency))[0];
73+
}
74+
75+
/**
76+
* Returns the midrange of the numbers in the array.
77+
*/
78+
public static function midrange(array $numbers): float|int
79+
{
80+
if (empty($numbers)) {
81+
throw new StatisticsException('The array can not be empty.');
82+
}
83+
84+
return (min($numbers) + max($numbers)) / 2;
85+
}
86+
87+
/**
88+
* Returns the variance of the numbers in the array.
89+
*/
90+
public static function sampleVariance(array $numbers): float|int
91+
{
92+
$count = count($numbers);
93+
94+
if ($count === 0) {
95+
throw new StatisticsException('The array can not be empty.');
96+
}
97+
98+
$mean = self::mean($numbers);
99+
100+
$variance = 0;
101+
102+
foreach ($numbers as $number) {
103+
$variance += ($number - $mean) ** 2;
104+
}
105+
106+
return $variance / ($count - 1);
107+
}
108+
109+
/**
110+
* Returns the variance of the numbers in the array.
111+
*/
112+
public static function populationVariance(array $numbers): float|int
113+
{
114+
$count = count($numbers);
115+
116+
if ($count === 0) {
117+
throw new StatisticsException('The array can not be empty.');
118+
}
119+
120+
$mean = self::mean($numbers);
121+
122+
$variance = 0;
123+
124+
foreach ($numbers as $number) {
125+
$variance += ($number - $mean) ** 2;
126+
}
127+
128+
return $variance / $count;
129+
}
130+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/**
4+
* @copyright Frederic G. Østby
5+
* @license http://www.makoframework.com/license
6+
*/
7+
8+
namespace mako\utility\exceptions;
9+
10+
use RuntimeException;
11+
12+
/**
13+
* Statistics exception.
14+
*/
15+
class StatisticsException extends RuntimeException
16+
{
17+
18+
}

tests/unit/utility/StatisticsTest.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
/**
4+
* @copyright Frederic G. Østby
5+
* @license http://www.makoframework.com/license
6+
*/
7+
8+
namespace mako\tests\unit\utility;
9+
10+
use mako\tests\TestCase;
11+
use mako\utility\exceptions\StatisticsException;
12+
use mako\utility\Statistics;
13+
use PHPUnit\Framework\Attributes\Group;
14+
15+
#[Group('unit')]
16+
class StatisticsTest extends TestCase
17+
{
18+
/**
19+
*
20+
*/
21+
public function testMean(): void
22+
{
23+
$this->assertSame(7, Statistics::mean([1, 3, 5, 7, 9, 11, 13]));
24+
$this->assertSame(6, Statistics::mean([1, 3, 5, 7, 9, 11]));
25+
$this->assertEqualsWithDelta(1.8666666666666665, Statistics::mean([-11, 5.5, -3.4, 7.1, -9, 22]), 0.000000000000001);
26+
}
27+
28+
/**
29+
*
30+
*/
31+
public function testMeanWithEmptyArray(): void
32+
{
33+
$this->expectException(StatisticsException::class);
34+
35+
Statistics::mean([]);
36+
}
37+
38+
/**
39+
*
40+
*/
41+
public function testMedian(): void
42+
{
43+
$this->assertSame(7, Statistics::median([1, 3, 5, 7, 9, 11, 13]));
44+
$this->assertSame(6, Statistics::median([1, 3, 5, 7, 9, 11]));
45+
$this->assertSame(1.05, Statistics::median([-11, 5.5, -3.4, 7.1, -9, 22]));
46+
}
47+
48+
/**
49+
*
50+
*/
51+
public function testMedianWithEmptyArray(): void
52+
{
53+
$this->expectException(StatisticsException::class);
54+
55+
Statistics::median([]);
56+
}
57+
58+
/**
59+
*
60+
*/
61+
public function testMode(): void
62+
{
63+
$this->assertSame(3, Statistics::mode([1, 3, 3, 3, 5, 7, 7, 9]));
64+
$this->assertSame(1, Statistics::mode([1, 1, -3, 3, 7, -9]));
65+
$this->assertSame('red', Statistics::mode(['red', 'green', 'blue', 'red']));
66+
}
67+
68+
/**
69+
*
70+
*/
71+
public function testModeWithEmptyArray(): void
72+
{
73+
$this->expectException(StatisticsException::class);
74+
75+
Statistics::mode([]);
76+
}
77+
78+
/**
79+
*
80+
*/
81+
public function testMidrange(): void
82+
{
83+
$this->assertSame(7, Statistics::midrange([1, 3, 5, 7, 9, 11, 13]));
84+
$this->assertSame(6, Statistics::midrange([1, 3, 5, 7, 9, 11]));
85+
$this->assertSame(5.5, Statistics::midrange([-11, 5.5, -3.4, 7.1, -9, 22]));
86+
}
87+
88+
/**
89+
*
90+
*/
91+
public function testMidrangeWithEmptyArray(): void
92+
{
93+
$this->expectException(StatisticsException::class);
94+
95+
Statistics::midrange([]);
96+
}
97+
98+
/**
99+
*
100+
*/
101+
public function testSampleVariance(): void
102+
{
103+
$this->assertSame(14, Statistics::sampleVariance([1, 3, 5, 7, 9, 11]));
104+
$this->assertEqualsWithDelta(0.4796666666666667, Statistics::sampleVariance([2, 2.5, 1.25, 3.1, 1.75, 2.8]), 0.000000000000001);
105+
$this->assertEqualsWithDelta(70.80333333333334, Statistics::sampleVariance([-11, 5.5, -3.4, 7.1]), 0.0000000000001);
106+
$this->assertEqualsWithDelta(1736.9166666666667, Statistics::sampleVariance([1, 30, 50, 100]), 0.000000000001);
107+
}
108+
109+
/**
110+
*
111+
*/
112+
public function testSampleVarianceWithEmptyArray(): void
113+
{
114+
$this->expectException(StatisticsException::class);
115+
116+
Statistics::sampleVariance([]);
117+
}
118+
119+
/**
120+
*
121+
*/
122+
public function testPopulationVariance(): void
123+
{
124+
$this->assertEqualsWithDelta(11.666666666666666, Statistics::populationVariance([1, 3, 5, 7, 9, 11]), 0.000000000000001);
125+
$this->assertEqualsWithDelta(0.3997222222222222, Statistics::populationVariance([2, 2.5, 1.25, 3.1, 1.75, 2.8]), 0.000000000000001);
126+
$this->assertEqualsWithDelta(53.1025, Statistics::populationVariance([-11, 5.5, -3.4, 7.1]), 0.0001);
127+
$this->assertEqualsWithDelta(1302.6875, Statistics::populationVariance([1, 30, 50, 100]), 0.0001);
128+
}
129+
}

0 commit comments

Comments
 (0)