Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

feature(geomean): add geomean function #6223

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

Merged
merged 10 commits into from
Jul 15, 2024
2 changes: 2 additions & 0 deletions 2 draftlogs/6223_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Add geometric mean functionality and 'geometric mean ascending' + 'geometric mean descending' to `category_order` on cartesian axes [[#6223](https://github.com/plotly/plotly.js/pull/6223)]
with thanks to @acxz and @prabhathc for the contribution!
1 change: 1 addition & 0 deletions 1 src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ var statsModule = require('./stats');
lib.aggNums = statsModule.aggNums;
lib.len = statsModule.len;
lib.mean = statsModule.mean;
lib.geometricMean = statsModule.geometricMean;
lib.median = statsModule.median;
lib.midRange = statsModule.midRange;
lib.variance = statsModule.variance;
Expand Down
5 changes: 5 additions & 0 deletions 5 src/lib/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ exports.mean = function(data, len) {
return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
};

exports.geometricMean = function(data, len) {
if(!len) len = exports.len(data);
return Math.pow(exports.aggNums(function(a, b) { return a * b; }, 1, data), 1 / len);
};

exports.midRange = function(numArr) {
if(numArr === undefined || numArr.length === 0) return undefined;
return (exports.aggNums(Math.max, null, numArr) + exports.aggNums(Math.min, null, numArr)) / 2;
Expand Down
3 changes: 2 additions & 1 deletion 3 src/plots/cartesian/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,7 @@ module.exports = {
'max ascending', 'max descending',
'sum ascending', 'sum descending',
'mean ascending', 'mean descending',
'geometric mean ascending', 'geometric mean descending',
'median ascending', 'median descending'
],
dflt: 'trace',
Expand All @@ -1216,7 +1217,7 @@ module.exports = {
'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.',
'Set `categoryorder` to *total ascending* or *total descending* if order should be determined by the',
'numerical order of the values.',
'Similarly, the order can be determined by the min, max, sum, mean or median of all the values.'
'Similarly, the order can be determined by the min, max, sum, mean, geometric mean or median of all the values.'
].join(' ')
},
categoryarray: {
Expand Down
3 changes: 2 additions & 1 deletion 3 src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -3188,7 +3188,7 @@ plots.doCalcdata = function(gd, traces) {
Registry.getComponentMethod('errorbars', 'calc')(gd);
};

var sortAxisCategoriesByValueRegex = /(total|sum|min|max|mean|median) (ascending|descending)/;
var sortAxisCategoriesByValueRegex = /(total|sum|min|max|mean|geometric mean|median) (ascending|descending)/;

function sortAxisCategoriesByValue(axList, gd) {
var affectedTraces = [];
Expand Down Expand Up @@ -3223,6 +3223,7 @@ function sortAxisCategoriesByValue(axList, gd) {
sum: function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
total: function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
mean: function(values) {return Lib.mean(values);},
'geometric mean': function(values) {return Lib.geometricMean(values);},
median: function(values) {return Lib.median(values);}
};

Expand Down
19 changes: 19 additions & 0 deletions 19 test/jasmine/tests/calcdata_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,25 @@ describe('calculated data and points', function() {
checkAggregatedValue(baseMock, expectedAgg, false, done);
});

it('takes the geometric mean of all values per category across traces of type ' + trace.type, function(done) {
acxz marked this conversation as resolved.
Show resolved Hide resolved
if(trace.type === 'ohlc' || trace.type === 'candlestick') return done();

var type = trace.type;
var data = [7, 2, 3];
var data2 = [5, 4, 2];
var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}};
baseMock.layout[axName] = { type: 'category', categoryorder: 'geometric mean ascending'};

var expectedAgg = [['a', Math.sqrt(data[0] * data2[0])], ['b', Math.sqrt(data[1] * data2[1])], ['c', Math.sqrt(data[2] * data2[2])]];
// TODO: how to actually calc these? what do these even mean?
if(type === 'histogram') expectedAgg = [['a', 2], ['b', 1], ['c', 1]];
if(type === 'histogram2d') expectedAgg = [['a', 0], ['b', 0], ['c', 0]];
if(type === 'contour' || type === 'heatmap') expectedAgg = [['a', 0], ['b', 0], ['c', 0]];
if(type === 'histogram2dcontour') expectedAgg = [['a', 0], ['b', 0], ['c', 0]];

checkAggregatedValue(baseMock, expectedAgg, false, done);
});

it('takes the median of all values per category across traces of type ' + trace.type, function(done) {
var type = trace.type;
var data = [7, 2, 3];
Expand Down
18 changes: 18 additions & 0 deletions 18 test/jasmine/tests/lib_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,24 @@ describe('Test lib.js:', function() {
});
});

describe('geometricMean() should', function() {
it('toss out non-numerics (strings)', function() {
var input = [1, 2, 'apple', 'orange'];
var res = Lib.geometricMean(input);
expect(res).toBeCloseTo(1.414, 3);
});
it('toss out non-numerics (NaN)', function() {
var input = [1, 2, NaN];
var res = Lib.geometricMean(input);
expect(res).toBeCloseTo(1.414, 3);
});
it('evaluate numbers which are passed around as text strings:', function() {
var input = ['1', '2'];
var res = Lib.geometricMean(input);
expect(res).toBeCloseTo(1.414, 3);
});
});

describe('midRange() should', function() {
it('should calculate the arithmetic mean of the maximum and minimum value of a given array', function() {
var input = [1, 5.5, 6, 15, 10, 13];
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.