Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
103 / 103
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
Average
100.00% covered (success)
100.00%
103 / 103
100.00% covered (success)
100.00%
11 / 11
42
100.00% covered (success)
100.00%
1 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 averageDatasetChange
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 totalMovingAverage
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 movingAverage
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
8
 weightedAverage
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 arithmeticMean
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 mode
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 median
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 geometricMean
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 harmonicMean
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 angleMean
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
4
 angleMean2
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Math\Statistic
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Math\Statistic;
16
17use phpOMS\Math\Exception\ZeroDivisionException;
18use phpOMS\Math\Matrix\Exception\InvalidDimensionException;
19
20/**
21 * Average class.
22 *
23 * @package phpOMS\Math\Statistic
24 * @license OMS License 2.0
25 * @link    https://jingga.app
26 * @since   1.0.0
27 */
28final class Average
29{
30    public const MA3 = [1 / 3, 1 / 3];
31
32    public const MA5 = [0.2, 0.2, 0.2];
33
34    public const MA2X12 = [5 / 6, 5 / 6, 5 / 6, 5 / 6, 5 / 6, 5 / 6, 0.42];
35
36    public const MA3X3 = [1 / 3, 2 / 9, 1 / 9];
37
38    public const MA3X5 = [0.2, 0.2, 2 / 15, 4 / 6];
39
40    public const MAS15 = [0.231, 0.209, 0.144, 2 / 3, 0.009, -0.016, -0.019, -0.009];
41
42    public const MAS21 = [0.171, 0.163, 0.134, 0.37, 0.51, 0.017, -0.006, -0.014, -0.014, -0.009, -0.003];
43
44    public const MAH5 = [0.558, 0.294, -0.73];
45
46    public const MAH9 = [0.330, 0.267, 0.119, -0.010, -0.041];
47
48    public const MAH13 = [0.240, 0.214, 0.147, 0.66, 0, -0.028, -0.019];
49
50    public const MAH23 = [0.148, 0.138, 0.122, 0.097, 0.068, 0.039, 0.013, -0.005, -0.015, -0.016, -0.011, -0.004];
51
52    /**
53     * Constructor.
54     *
55     * @since 1.0.0
56     * @codeCoverageIgnore
57     */
58    private function __construct()
59    {
60    }
61
62    /**
63     * Average change.
64     *
65     * @param array<int, int|float> $x Dataset
66     * @param int                   $h Future steps
67     *
68     * @return float
69     *
70     * @since 1.0.0
71     */
72    public static function averageDatasetChange(array $x, int $h = 1) : float
73    {
74        $count = \count($x);
75
76        return $h * ($x[$count - 1] - $x[0]) / ($count - 1);
77    }
78
79    /**
80     * Moving average of dataset (SMA)
81     *
82     * @param array<int, int|float> $x         Dataset
83     * @param int                   $order     Periods to use for average
84     * @param array<int, int|float> $weight    Weight for moving average
85     * @param bool                  $symmetric Cyclic moving average
86     *
87     * @return float[] Moving average of data
88     *
89     * @throws \Exception
90     *
91     * @since 1.0.0
92     */
93    public static function totalMovingAverage(array $x, int $order, array $weight = null, bool $symmetric = false) : array
94    {
95        $periods = (int) ($order / ($symmetric ? 2 : 1));
96        $count   = \count($x) - ($symmetric ? $periods : 0);
97        $avg     = [];
98
99        for ($i = $periods - 1; $i < $count; ++$i) {
100            $avg[] = self::movingAverage($x, $i, $order, $weight, $symmetric);
101        }
102
103        return $avg;
104    }
105
106    /**
107     * Moving average of element in dataset (SMA)
108     *
109     * @param array<int, int|float> $x         Dataset
110     * @param int                   $t         Current period
111     * @param int                   $order     Periods to use for average
112     * @param array<int, int|float> $weight    Weight for moving average
113     * @param bool                  $symmetric Cyclic moving average
114     *
115     * @return float Moving average
116     *
117     * @throws \Exception
118     *
119     * @since 1.0.0
120     */
121    public static function movingAverage(array $x, int $t, int $order, array $weight = null, bool $symmetric = false) : float
122    {
123        $periods = (int) ($order / ($symmetric ? 2 : 1));
124        $count   = \count($x);
125
126        if ($count < $t || $count < $periods || ($symmetric && $t + $periods >= $count)) {
127            throw new \Exception('Periods');
128        }
129
130        $t    += 2;
131        $end   = $symmetric ? $t + $periods - 1 : $t - 1;
132        $start = $t - 1 - $periods;
133
134        if (!empty($weight)) {
135            return self::weightedAverage(\array_slice($x, $start, $end - $start), \array_slice($weight, $start, $end - $start));
136        } else {
137            return self::arithmeticMean(\array_slice($x, $start, $end - $start));
138        }
139    }
140
141    /**
142     * Calculate weighted average.
143     *
144     * Example: ([1, 2, 3, 4], [0.25, 0.5, 0.125, 0.125])
145     *
146     * @param array<int, int|float> $values Values
147     * @param array<int, int|float> $weight Weight for values
148     *
149     * @return float
150     *
151     * @throws InvalidDimensionException This exception is thrown in case both parameters have different array length
152     *
153     * @since 1.0.0
154     */
155    public static function weightedAverage(array $values, array $weight) : float
156    {
157        if (($count = \count($values)) !== \count($weight)) {
158            throw new InvalidDimensionException(\count($values) . 'x' . \count($weight));
159        }
160
161        $avg = 0.0;
162
163        for ($i = 0; $i < $count; ++$i) {
164            $avg += $values[$i] * $weight[$i];
165        }
166
167        return $avg;
168    }
169
170    /**
171     * Calculate the arithmetic mean.
172     *
173     * Example: ([1, 2, 2, 3, 4, 4, 2])
174     *
175     * @latex \mu = mean = \frac{1}{n}\sum_{i=1}^{n}a_i
176     *
177     * @param array<int, int|float> $values Values
178     * @param int                   $offset Offset for outlier
179     *
180     * @return float
181     *
182     * @throws ZeroDivisionException This exception is thrown if the values array is empty
183     *
184     * @since 1.0.0
185     */
186    public static function arithmeticMean(array $values, int $offset = 0) : float
187    {
188        $count = \count($values);
189        if ($count <= $offset * 2) {
190            throw new ZeroDivisionException();
191        }
192
193        if ($offset > 0) {
194            \sort($values);
195            $values = \array_slice($values, $offset, -$offset);
196            $count -= $offset * 2;
197        }
198
199        return \array_sum($values) / $count;
200    }
201
202    /**
203     * Calculate the mode.
204     *
205     * Example: ([1, 2, 2, 3, 4, 4, 2])
206     *
207     * @param array<int, int|float> $values Values
208     * @param int                   $offset Offset for outlier
209     *
210     * @return float
211     *
212     * @since 1.0.0
213     */
214    public static function mode(array $values, int $offset = 0) : float
215    {
216        if ($offset > 0) {
217            \sort($values);
218            $values = \array_slice($values, $offset, -$offset);
219        }
220
221        $count = \array_count_values($values);
222        $best  = \max($count);
223
224        return (float) (\array_keys($count, $best)[0] ?? 0.0);
225    }
226
227    /**
228     * Calculate the median.
229     *
230     * Example: ([1, 2, 2, 3, 4, 4, 2])
231     *
232     * @param array<int, int|float> $values Values
233     * @param int                   $offset Offset for outlier
234     *
235     * @return float
236     *
237     * @since 1.0.0
238     */
239    public static function median(array $values, int $offset = 0) : float
240    {
241        \sort($values);
242
243        if ($offset > 0) {
244            $values = \array_slice($values, $offset, -$offset);
245        }
246
247        $count     = \count($values);
248        $middleval = (int) \floor(($count - 1) / 2);
249
250        if ($count % 2 !== 0) {
251            $median = $values[$middleval];
252        } else {
253            $low    = $values[$middleval];
254            $high   = $values[$middleval + 1];
255            $median = ($low + $high) / 2;
256        }
257
258        return $median;
259    }
260
261    /**
262     * Calculate the geometric mean.
263     *
264     * Example: ([1, 2, 2, 3, 4, 4, 2])
265     *
266     * @param array<int, int|float> $values Values
267     * @param int                   $offset Offset for outlier
268     *
269     * @return float
270     *
271     * @throws ZeroDivisionException This exception is thrown if the values array is empty
272     *
273     * @since 1.0.0
274     */
275    public static function geometricMean(array $values, int $offset = 0) : float
276    {
277        $count = \count($values);
278        if ($count <= $offset * 2) {
279            throw new ZeroDivisionException();
280        }
281
282        if ($offset > 0) {
283            \sort($values);
284            $values = \array_slice($values, $offset, -$offset);
285            $count -= $offset * 2;
286        }
287
288        return \pow(\array_product($values), 1 / $count);
289    }
290
291    /**
292     * Calculate the harmonic mean.
293     *
294     * Example: ([1, 2, 2, 3, 4, 4, 2])
295     *
296     * @param array<int, int|float> $values Values
297     * @param int                   $offset Offset for outlier
298     *
299     * @return float
300     *
301     * @throws ZeroDivisionException This exception is thrown if a value in the values array is 0 or if the values array is empty
302     *
303     * @since 1.0.0
304     */
305    public static function harmonicMean(array $values, int $offset = 0) : float
306    {
307        $count = \count($values);
308        if ($count <= $offset * 2) {
309            throw new ZeroDivisionException();
310        }
311
312        if ($offset > 0) {
313            \sort($values);
314            $values = \array_slice($values, $offset, -$offset);
315            $count -= $offset * 2;
316        }
317
318        $sum = 0.0;
319        foreach ($values as $value) {
320            if ($value === 0) {
321                throw new ZeroDivisionException();
322            }
323
324            $sum += 1 / $value;
325        }
326
327        return 1 / ($sum / $count);
328    }
329
330    /**
331     * Calculate the angle mean.
332     *
333     * Example: ([1, 2, 2, 3, 4, 4, 2])
334     *
335     * @param array<int, int|float> $angles Angles
336     * @param int                   $offset Offset for outlier
337     *
338     * @return float
339     *
340     * @throws ZeroDivisionException
341     *
342     * @since 1.0.0
343     */
344    public static function angleMean(array $angles, int $offset = 0) : float
345    {
346        $count = \count($angles);
347        if ($count <= $offset * 2) {
348            throw new ZeroDivisionException();
349        }
350
351        if ($offset > 0) {
352            \sort($angles);
353            $angles = \array_slice($angles, $offset, -$offset);
354            $count -= $offset * 2;
355        }
356
357        $y = 0;
358        $x = 0;
359
360        for ($i = 0; $i < $count; ++$i) {
361            $x += \cos(\deg2rad($angles[$i]));
362            $y += \sin(\deg2rad($angles[$i]));
363        }
364
365        $x /= $count;
366        $y /= $count;
367
368        return \rad2deg(\atan2($y, $x));
369    }
370
371    /**
372     * Calculate the angle mean.
373     *
374     * Example: ([1, 2, 2, 3, 4, 4, 2])
375     *
376     * @param array<int, int|float> $angles Angles
377     * @param int                   $offset Offset for outlier
378     *
379     * @return float
380     *
381     * @throws ZeroDivisionException
382     *
383     * @since 1.0.0
384     */
385    public static function angleMean2(array $angles, int $offset = 0) : float
386    {
387        $count = \count($angles);
388        if ($count <= $offset * 2) {
389            throw new ZeroDivisionException();
390        }
391
392        if ($offset > 0) {
393            \sort($angles);
394            $angles = \array_slice($angles, $offset, -$offset);
395            $count -= $offset * 2;
396        }
397
398        $sins = 0.0;
399        $coss = 0.0;
400
401        foreach ($angles as $a) {
402            $sins += \sin(\deg2rad($a));
403            $coss += \cos(\deg2rad($a));
404        }
405
406        $avgsin = $sins / (0.0 + $count);
407        $avgcos = $coss / (0.0 + $count);
408        $avgang = \rad2deg(\atan2($avgsin, $avgcos));
409
410        while ($avgang < 0.0) {
411            $avgang += 360.0;
412        }
413
414        return $avgang;
415    }
416}