Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/Freq.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ public static function frequencyTableBySize(array $data, int $chunkSize = 1): ar
foreach ($data as $number) {
if (
($number >= $rangeLow)
&&
($number < $rangeHigh)
&& ($number < $rangeHigh)
) {
$count++;
//unset($data[$key]);
Expand Down Expand Up @@ -167,8 +166,7 @@ public static function frequencyTable(array $data, ?int $category = null): array
foreach ($data as $number) {
if (
($number >= $rangeLow)
&&
($number < $rangeHigh)
&& ($number < $rangeHigh)
) {
$count++;
//unset($data[$key]);
Expand Down
130 changes: 77 additions & 53 deletions src/Stat.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

class Stat
{
final public const MEDIAN_TYPE_LOW = 'LOW';
final public const MEDIAN_TYPE_LOW = "LOW";

final public const MEDIAN_TYPE_HIGH = 'HIGH';
final public const MEDIAN_TYPE_HIGH = "HIGH";

final public const MEDIAN_TYPE_MIDDLE = 'MIDDLE';
final public const MEDIAN_TYPE_MIDDLE = "MIDDLE";

/**
* Count the element in the array
Expand Down Expand Up @@ -39,21 +39,23 @@ public static function mean(array $data): int|float|null
{
$sum = 0;
if (self::count($data) === 0) {
throw new InvalidDataInputException('The data must not be empty.');
throw new InvalidDataInputException("The data must not be empty.");
}
if (array_filter($data, 'is_string') !== []) {
throw new InvalidDataInputException('The data array contains a string.');
if (array_filter($data, "is_string") !== []) {
throw new InvalidDataInputException(
"The data array contains a string.",
);
}
$sum = array_sum($data);

return $sum / self::count($data);
}

/**
* Calculate the float number arithmetic mean of a float numbers dataset with optional weights and precision.
*
* Supports both unweighted and weighted means. Automatically casts values to float.
* Returns `null` if the input data is empty.
* Calculate the float number arithmetic mean of a float numbers dataset with optional weights and precision.
*
* Supports both unweighted and weighted means. Automatically casts values to float.
* Returns `null` if the input data is empty.
*
* @param array<float> $data Array of floating numbers
* @param null|array<float> $weights Optional array of weights (same length as $data).
Expand All @@ -62,17 +64,20 @@ public static function mean(array $data): int|float|null
*
* @throws InvalidDataInputException if the data is empty
*/
public static function fmean(array $data, array|null $weights = null, int|null $precision = null): float|null
{
public static function fmean(
array $data,
?array $weights = null,
?int $precision = null,
): ?float {
$sum = 0;
$count = self::count($data);
if ($count === 0) {
throw new InvalidDataInputException('The data must not be empty.');
throw new InvalidDataInputException("The data must not be empty.");
}

// Unweighted mean
if ($weights === null) {
$sum = array_sum(array_map('floatval', $data));
$sum = array_sum(array_map("floatval", $data));
$count = count($data);
if ($precision) {
return round($sum / $count, $precision);
Expand All @@ -82,7 +87,9 @@ public static function fmean(array $data, array|null $weights = null, int|null $

// Check lengths
if ($count !== count($weights)) {
throw new InvalidDataInputException('The data and weights must be the same length');
throw new InvalidDataInputException(
"The data and weights must be the same length",
);
}

$weightedSum = 0.0;
Expand All @@ -94,7 +101,9 @@ public static function fmean(array $data, array|null $weights = null, int|null $
}

if ($weightTotal == 0) {
throw new InvalidDataInputException('The sum of weights must be non-zero');
throw new InvalidDataInputException(
"The sum of weights must be non-zero",
);
}

if ($precision) {
Expand All @@ -111,21 +120,24 @@ public static function fmean(array $data, array|null $weights = null, int|null $
* @return mixed median of the data
* @throws InvalidDataInputException if the data is empty
*/
public static function median(array $data, string $medianType = self::MEDIAN_TYPE_MIDDLE): mixed
{
public static function median(
array $data,
string $medianType = self::MEDIAN_TYPE_MIDDLE,
): mixed {
sort($data);
$count = self::count($data);
if ($count === 0) {
throw new InvalidDataInputException('The data must not be empty.');
throw new InvalidDataInputException("The data must not be empty.");
}
$index = (int) floor($count / 2); // cache the index
if (($count & 1) !== 0) { // count is odd
$index = (int) floor($count / 2); // cache the index
if (($count & 1) !== 0) {
// count is odd
return $data[$index];
}

// count is even
return match ($medianType) {
self::MEDIAN_TYPE_LOW => ($data[$index - 1]),
self::MEDIAN_TYPE_LOW => $data[$index - 1],
self::MEDIAN_TYPE_HIGH => $data[$index],
default => ($data[$index - 1] + $data[$index]) / 2,
};
Expand Down Expand Up @@ -180,7 +192,7 @@ public static function mode(array $data, bool $multimode = false): mixed
{
$frequencies = Freq::frequencies($data);
if ($frequencies === []) {
throw new InvalidDataInputException('The data must not be empty.');
throw new InvalidDataInputException("The data must not be empty.");
}
$sameMode = true;
foreach ($frequencies as $value) {
Expand Down Expand Up @@ -212,7 +224,7 @@ public static function mode(array $data, bool $multimode = false): mixed
*
* @return mixed[]|null array of the most common data points or null, if all elements occurs once
*/
public static function multimode(array $data): array|null
public static function multimode(array $data): ?array
{
return self::mode($data, true);
}
Expand All @@ -227,27 +239,31 @@ public static function multimode(array $data): array|null
*
* @throws InvalidDataInputException if number of quantiles is less than 1, or the data size is less than 2
*/
public static function quantiles(array $data, int $n = 4, ?int $round = null): array
{
public static function quantiles(
array $data,
int $n = 4,
?int $round = null,
): array {
$count = self::count($data);
if ($count < 2 || $n < 1) {
throw new InvalidDataInputException(
'The size of the data must be greater than 2 and the number of quantiles must be greater than 1.',
"The size of the data must be greater than 2 and the number of quantiles must be greater than 1.",
);
}

sort($data);
$m = $count + 1;
$result = [];
foreach (range(1, $n - 1) as $i) {
$j = floor($i * $m / $n);
$j = (int) floor(($i * $m) / $n);
if ($j < 1) {
$j = 1;
} elseif ($j > $count - 1) {
$j = $count - 1;
}
$delta = $i * $m - $j * $n;
$interpolated = ($data[$j - 1] * ($n - $delta) + $data[$j] * $delta) / $n;
$interpolated
= ($data[$j - 1] * ($n - $delta) + $data[$j] * $delta) / $n;
$result[] = Math::round($interpolated, $round);
}

Expand Down Expand Up @@ -321,7 +337,7 @@ public static function pvariance(array $data, ?int $round = null): float
{
$num_of_elements = self::count($data);
if ($num_of_elements == 0) {
throw new InvalidDataInputException('The data must not be empty.');
throw new InvalidDataInputException("The data must not be empty.");
}
$sumSquareDifferences = 0.0;
$average = self::mean($data);
Expand All @@ -332,7 +348,7 @@ public static function pvariance(array $data, ?int $round = null): float
$sumSquareDifferences += ($i - $average) ** 2;
}

return Math::round($sumSquareDifferences / ($num_of_elements), $round);
return Math::round($sumSquareDifferences / $num_of_elements, $round);
}

/**
Expand Down Expand Up @@ -365,7 +381,9 @@ public static function variance(array $data, ?int $round = null): float
{
$num_of_elements = self::count($data);
if ($num_of_elements <= 1) {
throw new InvalidDataInputException('The data size must be greater than 1.');
throw new InvalidDataInputException(
"The data size must be greater than 1.",
);
}
$sumSquareDifferences = 0.0;
$average = self::mean($data);
Expand All @@ -376,7 +394,10 @@ public static function variance(array $data, ?int $round = null): float
$sumSquareDifferences += ($i - $average) ** 2;
}

return Math::round($sumSquareDifferences / ($num_of_elements - 1), $round);
return Math::round(
$sumSquareDifferences / ($num_of_elements - 1),
$round,
);
}

/**
Expand All @@ -394,7 +415,7 @@ public static function geometricMean(array $data, ?int $round = null): float
{
$count = self::count($data);
if ($count === 0) {
throw new InvalidDataInputException('The data must not be empty.');
throw new InvalidDataInputException("The data must not be empty.");
}
$product = 1;
foreach ($data as $value) {
Expand All @@ -415,12 +436,15 @@ public static function geometricMean(array $data, ?int $round = null): float
*
* @throws InvalidDataInputException if the data is empty
*/
public static function harmonicMean(array $data, ?array $weights = null, ?int $round = null): float
{
public static function harmonicMean(
array $data,
?array $weights = null,
?int $round = null,
): float {
$sum = 0;
$count = self::count($data);
if ($count === 0) {
throw new InvalidDataInputException('The data must not be empty.');
throw new InvalidDataInputException("The data must not be empty.");
}
$sumWeigth = 0;
foreach ($data as $key => $value) {
Expand Down Expand Up @@ -451,12 +475,12 @@ public static function covariance(array $x, array $y): false|float
$countY = count($y);
if ($countX !== $countY) {
throw new InvalidDataInputException(
'Covariance requires that both inputs have same number of data points.',
"Covariance requires that both inputs have same number of data points.",
);
}
if ($countX < 2) {
throw new InvalidDataInputException(
'Covariance requires at least two data points.',
"Covariance requires at least two data points.",
);
}
$meanX = self::mean($x);
Expand All @@ -467,18 +491,18 @@ public static function covariance(array $x, array $y): false|float
$valueX = $x[$pos];
if (!is_numeric($valueX)) {
throw new InvalidDataInputException(
'Covariance requires numeric data points.',
"Covariance requires numeric data points.",
);
}
$valueY = $y[$pos];
if (!is_numeric($valueY)) {
throw new InvalidDataInputException(
'Covariance requires numeric data points.',
"Covariance requires numeric data points.",
);
}
$diffX = $valueX - $meanX;
$diffY = $valueY - $meanY;
$add += ($diffX * $diffY);
$add += $diffX * $diffY;
}

// covariance for sample: N - 1
Expand Down Expand Up @@ -506,12 +530,12 @@ public static function correlation(array $x, array $y): false|float
$countY = count($y);
if ($countX !== $countY) {
throw new InvalidDataInputException(
'Correlation requires that both inputs have same number of data points.',
"Correlation requires that both inputs have same number of data points.",
);
}
if ($countX < 2) {
throw new InvalidDataInputException(
'Correlation requires at least two data points.',
"Correlation requires at least two data points.",
);
}
$meanX = self::mean($x);
Expand All @@ -530,7 +554,7 @@ public static function correlation(array $x, array $y): false|float
$b = sqrt($bx * $by);
if ($b == 0) {
throw new InvalidDataInputException(
'Correlation, at least one of the inputs is constant.',
"Correlation, at least one of the inputs is constant.",
);
}

Expand All @@ -552,12 +576,12 @@ public static function linearRegression(array $x, array $y): array
$countY = count($y);
if ($countX !== $countY) {
throw new InvalidDataInputException(
'Linear regression requires that both inputs have same number of data points.',
"Linear regression requires that both inputs have same number of data points.",
);
}
if ($countX < 2) {
throw new InvalidDataInputException(
'Linear regression requires at least two data points.',
"Linear regression requires at least two data points.",
);
}
$sumX = array_sum($x);
Expand All @@ -566,17 +590,17 @@ public static function linearRegression(array $x, array $y): array
$sumXY = 0;

foreach ($x as $key => $value) {
$sumXY += ($value * $y[$key]);
$sumXX += ($value * $value);
$sumXY += $value * $y[$key];
$sumXX += $value * $value;
}
$denominator = (($countX * $sumXX) - ($sumX * $sumX));
$denominator = $countX * $sumXX - $sumX * $sumX;
if ($denominator === 0) {
throw new InvalidDataInputException(
'Linear regression, the inputs is constant.',
"Linear regression, the inputs is constant.",
);
}
$slope = (($countX * $sumXY) - ($sumX * $sumY)) / $denominator;
$intercept = ($sumY - ($slope * $sumX)) / $countX;
$slope = ($countX * $sumXY - $sumX * $sumY) / $denominator;
$intercept = ($sumY - $slope * $sumX) / $countX;

return [$slope, $intercept];
}
Expand Down