Skip to content

Add geometric mean function #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
delphidabbler opened this issue Jan 5, 2025 · 4 comments
Closed

Add geometric mean function #31

delphidabbler opened this issue Jan 5, 2025 · 4 comments
Assignees
Labels
completed Issue completed and committed to develop. To be closed on next release enhancement New feature or request

Comments

@delphidabbler
Copy link
Owner

The geometric mean, sometimes also called geometric average, is an average calculated by multiplying a set of positive values and taking the nth root, where n is the number of values.

The geometric mean is used to minimize the effects of extreme values; for instance, when averaging growth rates.

--- Source: Eurostat

Geometric mean requires that all numbers are > 0.

There are two formula:

  1. For N +ve numbers a1, a2, ... aN, GeoMean = Nth root of (a1 × a2 × ... aN).
  2. For N +ve numbers a1, a2, ... aN, GeoMean = exp(sum(ln(a1)), ln(a2), ... ĺn(aN)) / N).

--- Source: Wikipedia

Note that formula 1 could overflow easily, so formula 2 seems the best algorithm.

E.g.:

function GeoMean(const A: array of Double): Double;
begin
  Assert(Length(A) > 0);  // replace with exception
  var SumOfLogs: Double := 0.0;
  for var D in A do
  begin
    if Sign(D) <> PositiveValue then
      raise EArgumentOutOfRangeException.Create('Non positive value passed to GeoMean');
    SumOfLogs := SumOfLogs + Ln(A);
  end;
  Result := Exp(SumOfLogs / Length(A));
end;

This function was extracted from issue #16

@delphidabbler delphidabbler self-assigned this Jan 5, 2025
@delphidabbler delphidabbler added enhancement New feature or request considering Issue is currently under consideration labels Jan 5, 2025
@github-project-automation github-project-automation bot moved this to Considering in Code Snippets Jan 5, 2025
@delphidabbler
Copy link
Owner Author

Also, there is the weighted geometric mean:

Screenshot_20230719-205701_Chrome

Source: Wikipedia

@delphidabbler
Copy link
Owner Author

delphidabbler commented Jan 11, 2025

UPDATE: This comment has been extracted into it own issue: #44


There is a discussion on the Wikipedia page about the weighted arithmetic mean that discusses that fact that weights can be "normalised" to fall in the range [0..1].

There are three possible approaches:

  1. To always normalise the weights before performing the calculation.
  2. To provide two functions, one of which does no normalisation & the other that performs normalisation but uses a simpler algorithm.
  3. To never alter the weights provided by the user, so that the user can normalise them if necessary before calling the function.

The are problems with all these approaches:

  1. There may be circumstances where rounding errors cause problems if weights are always normalised.
  2. Two functions increase complexity and maintenance issues.
  3. Passing in normalised weights means that the simpler algorithm can't be used.

Regardless of any those issues, some method that can transform an array of values into a normalised array will be of some use, so the following routine is proposed:

function NormaliseWeights(const Weights: array of Double):
  Types.TDoubleDynArray;
var
  Weight: Double;
  WeightSum: Double;
  Idx: Integer;
begin
  if (System.Length(Weights) = 0) then
    raise SysUtils.EArgumentException.Create('Array of weights is empty');
  WeightSum := 0.0;
  for Weight in Weights do
  begin
    if Math.Sign(Weight) = Math.NegativeValue then
      raise SysUtils.EArgumentException.Create('Weights must all be >= 0');
    WeightSum := WeightSum + Weight;
  end;
  if Math.IsZero(WeightSum) then
    raise SysUtils.EArgumentException.Create('Sum of weights can''t be zero');
  System.SetLength(Result, System.Length(Weights));
  for Idx := 0 to Pred(System.Length(Weights)) do
    Result[Idx] := Weights[Idx] / WeightSum;
end;

@delphidabbler delphidabbler added accepted Issue will be actioned in progress Work has started on this issue and removed considering Issue is currently under consideration labels Jan 11, 2025
@delphidabbler delphidabbler moved this from Considering to In progress in Code Snippets Jan 11, 2025
@delphidabbler
Copy link
Owner Author

Added Integer, Cardinal and Double overloads of GeoMean and WeightedGeoMean functions by merge commit 5c7623f

@delphidabbler delphidabbler added completed Issue completed and committed to develop. To be closed on next release and removed accepted Issue will be actioned in progress Work has started on this issue labels Jan 13, 2025
@delphidabbler delphidabbler moved this from In progress to Completed in Code Snippets Jan 13, 2025
@delphidabbler delphidabbler added this to the Next Release milestone Jan 13, 2025
@delphidabbler
Copy link
Owner Author

⚠️ The geometric mean functions were originally named GeoMean and WeightedGeoMean. These were changed to GeometricMean and WeightedGeometricMean as at commit b50ec8a.

@delphidabbler delphidabbler removed this from the Next Release milestone Jan 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
completed Issue completed and committed to develop. To be closed on next release enhancement New feature or request
Projects
Status: Completed
Development

No branches or pull requests

1 participant