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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 1.1.4 - 2025-04-25
- Adding `fmean()` method for computing the arithmetic mean with float numbers.

## 1.1.3 - 2024-12-14
- Adding `multiply()` method to scale NormalDist by a constant

Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,36 @@ $mean = Stat::mean([-1.0, 2.5, 3.25, 5.75]);
// 2.625
```

#### Stat::fmean( array $data, array|null $weights = null, int|null $precision = null )
Return the arithmetic mean of the array `$data`, as a float, with optional weights and precision control.
This function behaves like `mean()` but ensures a floating-point result and supports weighted datasets.
If `$weights` is provided, it computes the weighted average. The result is rounded to a given decimal $precision.
The result is rounded to `$precision` decimal places.
If `$precision` is null, no rounding is applied — this may lead to results with long or unexpected decimal expansions due to the nature of floating-point arithmetic in PHP. Using rounding helps ensure cleaner, more predictable output.

```php
use HiFolks\Statistics\Stat;

// Unweighted mean (same as mean but always float)
$fmean = Stat::fmean([3.5, 4.0, 5.25]);
// 4.25

// Weighted mean
$fmean = Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1]);
// 4.1875

// Custom precision
$fmean = Stat::fmean([3.5, 4.0, 5.25], null, 2);
// 4.25

$fmean = Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1], 3);
// 4.188

```

If the input is empty, or weights are invalid (e.g., length mismatch or sum is zero), an exception is thrown.
Use this function when you need floating-point accuracy or to apply custom weighting and rounding to your average.

#### Stat::geometricMean( array $data )
The geometric mean indicates the central tendency or typical value of the data using the product of the values (as opposed to the arithmetic mean which uses their sum).

Expand Down
54 changes: 54 additions & 0 deletions src/Stat.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,60 @@ public static function mean(array $data): int|float|null
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.
*
* @param array<float> $data Array of floating numbers
* @param null|array<float> $weights Optional array of weights (same length as $data).
* @param null|int $precision Optional number of decimal places to round the result (default is null, no round() is applied).
* @return float|null arithmetic mean
*
* @throws InvalidDataInputException if the data is empty
*/
public static function fmean(array $data, array|null $weights = null, int|null $precision = null): float|null
{
$sum = 0;
$count = self::count($data);
if ($count === 0) {
throw new InvalidDataInputException('The data must not be empty.');
}

// Unweighted mean
if ($weights === null) {
$sum = array_sum(array_map('floatval', $data));
$count = count($data);
if ($precision) {
return round($sum / $count, $precision);
}
return $sum / $count;
}

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

$weightedSum = 0.0;
$weightTotal = 0.0;
foreach ($data as $i => $value) {
$w = floatval($weights[$i]);
$weightedSum += floatval($value) * $w;
$weightTotal += $w;
}

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

if ($precision) {
return round($weightedSum / $weightTotal, $precision);
}
return $weightedSum / $weightTotal;
}

/**
* Return the median (middle value) of data,
* using the common “mean of middle two” method.
Expand Down
28 changes: 28 additions & 0 deletions tests/StatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,34 @@
)->toThrow(InvalidDataInputException::class);
});

it('calculates fmean (static)', function () {
expect(
Stat::mean([1, 2, 3, 4, 4]),
)->toEqual(2.8);
expect(
Stat::mean([-1.0, 2.5, 3.25, 5.75]),
)->toEqual(2.625);
expect(
fn() => Stat::mean([]),
)->toThrow(InvalidDataInputException::class);
expect(
Stat::fmean([3.5, 4.0, 5.25]),
)->toBeFloat()->toEqual(4.25);
expect(
Stat::fmean([85, 92, 83, 91], [0.20, 0.20, 0.30, 0.30], 2),
)->toBeFloat()->toEqual(87.6);
expect(
Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1]),
)->toBeFloat()->toEqual(4.1875);
expect(
Stat::fmean([3.5, 4.0, 5.25], precision: 2),
)->toBeFloat()->toEqual(4.25);
expect(
Stat::fmean([3.5, 4.0, 5.25], [1, 2, 1], precision: 3),
)->toBeFloat()->toEqual(4.188);

});

it('calculates median (static)', function () {
expect(
Stat::median([1, 3, 5]),
Expand Down