$x Dataset * @param int $h Future steps * * @return float * * @since 1.0.0 */ public static function averageDatasetChange(array $x, int $h = 1) : float { $count = \count($x); return $h * ($x[$count - 1] - $x[0]) / ($count - 1); } /** * Moving average of dataset (SMA) * * @param array $x Dataset * @param int $order Periods to use for average * @param array $weight Weight for moving average * @param bool $symmetric Cyclic moving average * * @return array Moving average of data * * @throws \Exception * * @since 1.0.0 */ public static function totalMovingAverage(array $x, int $order, array $weight = null, bool $symmetric = false) : array { $periods = (int) ($order / ($symmetric ? 2 : 1)); $count = \count($x) - ($symmetric ? $periods : 0); $avg = []; for ($i = $periods - 1; $i < $count; ++$i) { $avg[] = self::movingAverage($x, $i, $order, $weight, $symmetric); } return $avg; } /** * Moving average of element in dataset (SMA) * * @param array $x Dataset * @param int $t Current period * @param int $order Periods to use for average * @param array $weight Weight for moving average * @param bool $symmetric Cyclic moving average * * @return float Moving average * * @throws \Exception * * @since 1.0.0 */ public static function movingAverage(array $x, int $t, int $order, array $weight = null, bool $symmetric = false) : float { $periods = (int) ($order / ($symmetric ? 2 : 1)); $count = \count($x); if ($count < $t || $count < $periods || ($symmetric && $t + $periods >= $count)) { throw new \Exception('Periods'); } $t += 2; $end = $symmetric ? $t + $periods - 1 : $t - 1; $start = $t - 1 - $periods; if (!empty($weight)) { return self::weightedAverage(\array_slice($x, $start, $end - $start), \array_slice($weight, $start, $end - $start)); } else { return self::arithmeticMean(\array_slice($x, $start, $end - $start)); } } /** * Calculate weighted average. * * Example: ([1, 2, 3, 4], [0.25, 0.5, 0.125, 0.125]) * * @param array $values Values * @param array $weight Weight for values * * @return float * * @throws InvalidDimensionException This exception is thrown in case both parameters have different array length * * @since 1.0.0 */ public static function weightedAverage(array $values, array $weight) : float { if (($count = \count($values)) !== \count($weight)) { throw new InvalidDimensionException(\count($values) . 'x' . \count($weight)); } $avg = 0.0; for ($i = 0; $i < $count; ++$i) { $avg += $values[$i] * $weight[$i]; } return $avg; } /** * Calculate the arithmetic mean. * * Example: ([1, 2, 2, 3, 4, 4, 2]) * * @param array $values Values * * @return float * * @throws ZeroDevisionException This exception is thrown if the values array is empty * * @since 1.0.0 */ public static function arithmeticMean(array $values) : float { $count = \count($values); if ($count === 0) { throw new ZeroDevisionException(); } return \array_sum($values) / $count; } /** * Calculate the mode. * * Example: ([1, 2, 2, 3, 4, 4, 2]) * * @param array $values Values * * @return float * * @since 1.0.0 */ public static function mode(array $values) : float { $count = \array_count_values($values); $best = \max($count); return (float) (\array_keys($count, $best)[0] ?? 0.0); } /** * Calculate the median. * * Example: ([1, 2, 2, 3, 4, 4, 2]) * * @param array $values Values * * @return float * * @since 1.0.0 */ public static function median(array $values) : float { \sort($values); $count = \count($values); $middleval = (int) \floor(($count - 1) / 2); if ($count % 2) { $median = $values[$middleval]; } else { $low = $values[$middleval]; $high = $values[$middleval + 1]; $median = ($low + $high) / 2; } return $median; } /** * Calculate the geometric mean. * * Example: ([1, 2, 2, 3, 4, 4, 2]) * * @param array $values Values * @param int $offset Offset for outlier * * @return float * * @throws ZeroDevisionException This exception is thrown if the values array is empty * * @since 1.0.0 */ public static function geometricMean(array $values, int $offset = 0) : float { $count = \count($values); if ($count === 0) { throw new ZeroDevisionException(); } return \pow(\array_product($values), 1 / $count); } /** * Calculate the harmonic mean. * * Example: ([1, 2, 2, 3, 4, 4, 2]) * * @param array $values Values * @param int $offset Offset for outlier * * @return float * * @throws ZeroDevisionException This exception is thrown if a value in the values array is 0 or if the values array is empty * * @since 1.0.0 */ public static function harmonicMean(array $values, int $offset = 0) : float { \sort($values); if ($offset > 0) { $values = \array_slice($values, $offset, -$offset); } $count = \count($values); $sum = 0.0; if ($count === 0) { throw new ZeroDevisionException(); } foreach ($values as $value) { if ($value === 0) { throw new ZeroDevisionException(); } $sum += 1 / $value; } return 1 / ($sum / $count); } /** * Calculate the angle mean. * * Example: ([1, 2, 2, 3, 4, 4, 2]) * * @param array $angles Angles * @param int $offset Offset for outlier * * @return float * * @since 1.0.0 */ public static function angleMean($angles, int $offset = 0) : float { $y = 0; $x = 0; $size = \count($angles); for ($i = 0; $i < $size; ++$i) { $x += \cos(\deg2rad($angles[$i])); $y += \sin(\deg2rad($angles[$i])); } $x /= $size; $y /= $size; return \rad2deg(\atan2($y, $x)); } /** * Calculate the angle mean. * * Example: ([1, 2, 2, 3, 4, 4, 2]) * * @param array $angles Angles * @param int $offset Offset for outlier * * @return float * * @since 1.0.0 */ public static function angleMean2(array $angles, int $offset = 0) : float { \sort($angles); if ($offset > 0) { $angles = \array_slice($angles, $offset, -$offset); } $sins = 0.0; $coss = 0.0; foreach ($angles as $a) { $sins += \sin(\deg2rad($a)); $coss += \cos(\deg2rad($a)); } $avgsin = $sins / (0.0 + \count($angles)); $avgcos = $coss / (0.0 + \count($angles)); $avgang = \rad2deg(\atan2($avgsin, $avgcos)); while ($avgang < 0.0) { $avgang += 360.0; } return $avgang; } }