Skip to content

Commit

Permalink
feat: Implement log average (#556)
Browse files Browse the repository at this point in the history
* Implement log average

* Fix message
  • Loading branch information
dsaxton authored Mar 2, 2021
1 parent 074413e commit aeda3c0
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 29 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export { default as addToMean } from "./src/add_to_mean";
export { default as combineMeans } from "./src/combine_means";
export { default as combineVariances } from "./src/combine_variances";
export { default as geometricMean } from "./src/geometric_mean";
export { default as logAverage } from "./src/log_average";
export { default as harmonicMean } from "./src/harmonic_mean";
export { default as average, default as mean } from "./src/mean";

Expand Down
4 changes: 2 additions & 2 deletions src/geometric_mean.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ function geometricMean(x) {

for (let i = 0; i < x.length; i++) {
// the geometric mean is only valid for positive numbers
if (x[i] <= 0) {
if (x[i] < 0) {
throw new Error(
"geometricMean requires only positive numbers as input"
"geometricMean requires only non-negative numbers as input"
);
}

Expand Down
30 changes: 30 additions & 0 deletions src/log_average.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* The [log average](https://en.wikipedia.org/wiki/https://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_logarithms)
* is an equivalent way of computing the geometric mean of an array suitable for large or small products.
*
* It's found by calculating the average logarithm of the elements and exponentiating.
*
* @param {Array<number>} x sample of one or more data points
* @returns {number} geometric mean
* @throws {Error} if x is empty
* @throws {Error} if x contains a negative number
*/
function logAverage(x) {
if (x.length === 0) {
throw new Error("logAverage requires at least one data point");
}

let value = 0;
for (let i = 0; i < x.length; i++) {
if (x[i] < 0) {
throw new Error(
"logAverage requires only non-negative numbers as input"
);
}
value += Math.log(x[i]);
}

return Math.exp(value / x.length);
}

export default logAverage;
7 changes: 7 additions & 0 deletions test/geometric_mean.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,12 @@ test("geometric mean", function (t) {
});
t.end();
});

t.test("equals zero if array contains zero", function (t) {
if (ss.geometricMean([0, 1, 2]) !== 0) {
t.fail("geometric mean of array containing zero is not zero");
}
t.end();
});
t.end();
});
63 changes: 63 additions & 0 deletions test/log_average.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* eslint no-shadow: 0 */

const test = require("tap").test;
const ss = require("../");

test("log average", function (t) {
t.test("cannot calculate for empty lists", function (t) {
t.throws(function () {
ss.logAverage([]);
});
t.end();
});

t.test("cannot calculate for lists with negative numbers", function (t) {
t.throws(function () {
ss.logAverage([-1]);
});
t.end();
});

t.test("does not overflow for large products", function (t) {
const value = 1000;
const array = [];
for (let i = 0; i < 100; i++) {
array.push(value);
}
if (!isFinite(ss.logAverage(array))) {
t.fail("log average failed for large product");
}
t.end();
});

t.test("does not underflow for small products", function (t) {
const value = 0.001;
const array = [];
for (let i = 0; i < 100; i++) {
array.push(value);
}
if (ss.logAverage(array) === 0) {
t.fail("log average failed for small product");
}
t.end();
});

t.test("agrees with geometricMean", function (t) {
const arr = [];
for (let i = 0; i < 10; i++) {
arr.push(Math.exp(Math.random()));
}
if (Math.abs(ss.logAverage(arr) - ss.geometricMean(arr)) > ss.epsilon) {
t.fail("log average and geometric mean are not equal");
}
t.end();
});

t.test("equals zero if array contains zero", function (t) {
if (ss.logAverage([0, 1, 2]) !== 0) {
t.fail("log average of array containing zero is not zero");
}
t.end();
});
t.end();
});
27 changes: 0 additions & 27 deletions test/logit.test.js

This file was deleted.

0 comments on commit aeda3c0

Please sign in to comment.