Skip to content

Commit c701efe

Browse files
authored
Merge pull request #6223 from prabhathc/feature/geomean
feature(geomean): add geomean function
2 parents dcdfd82 + 5597352 commit c701efe

File tree

8 files changed

+70
-9
lines changed

8 files changed

+70
-9
lines changed

Diff for: draftlogs/6223_add.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- 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)]
2+
with thanks to @acxz and @prabhathc for the contribution!

Diff for: src/lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ var statsModule = require('./stats');
115115
lib.aggNums = statsModule.aggNums;
116116
lib.len = statsModule.len;
117117
lib.mean = statsModule.mean;
118+
lib.geometricMean = statsModule.geometricMean;
118119
lib.median = statsModule.median;
119120
lib.midRange = statsModule.midRange;
120121
lib.variance = statsModule.variance;

Diff for: src/lib/stats.js

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ exports.mean = function(data, len) {
4747
return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
4848
};
4949

50+
exports.geometricMean = function(data, len) {
51+
if(!len) len = exports.len(data);
52+
return Math.pow(exports.aggNums(function(a, b) { return a * b; }, 1, data), 1 / len);
53+
};
54+
5055
exports.midRange = function(numArr) {
5156
if(numArr === undefined || numArr.length === 0) return undefined;
5257
return (exports.aggNums(Math.max, null, numArr) + exports.aggNums(Math.min, null, numArr)) / 2;

Diff for: src/plots/cartesian/layout_attributes.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,7 @@ module.exports = {
12021202
'max ascending', 'max descending',
12031203
'sum ascending', 'sum descending',
12041204
'mean ascending', 'mean descending',
1205+
'geometric mean ascending', 'geometric mean descending',
12051206
'median ascending', 'median descending'
12061207
],
12071208
dflt: 'trace',
@@ -1216,7 +1217,7 @@ module.exports = {
12161217
'the *trace* mode. The unspecified categories will follow the categories in `categoryarray`.',
12171218
'Set `categoryorder` to *total ascending* or *total descending* if order should be determined by the',
12181219
'numerical order of the values.',
1219-
'Similarly, the order can be determined by the min, max, sum, mean or median of all the values.'
1220+
'Similarly, the order can be determined by the min, max, sum, mean, geometric mean or median of all the values.'
12201221
].join(' ')
12211222
},
12221223
categoryarray: {

Diff for: src/plots/plots.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3188,7 +3188,7 @@ plots.doCalcdata = function(gd, traces) {
31883188
Registry.getComponentMethod('errorbars', 'calc')(gd);
31893189
};
31903190

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

31933193
function sortAxisCategoriesByValue(axList, gd) {
31943194
var affectedTraces = [];
@@ -3223,6 +3223,7 @@ function sortAxisCategoriesByValue(axList, gd) {
32233223
sum: function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
32243224
total: function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
32253225
mean: function(values) {return Lib.mean(values);},
3226+
'geometric mean': function(values) {return Lib.geometricMean(values);},
32263227
median: function(values) {return Lib.median(values);}
32273228
};
32283229

Diff for: test/jasmine/tests/calcdata_test.js

+19
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,25 @@ describe('calculated data and points', function() {
11601160
checkAggregatedValue(baseMock, expectedAgg, false, done);
11611161
});
11621162

1163+
it('takes the geometric mean of all values per category across traces of type ' + trace.type, function(done) {
1164+
if(trace.type === 'ohlc' || trace.type === 'candlestick') return done();
1165+
1166+
var type = trace.type;
1167+
var data = [7, 2, 3];
1168+
var data2 = [5, 4, 2];
1169+
var baseMock = { data: [makeData(type, axName, cat, data), makeData(type, axName, cat, data2)], layout: {}};
1170+
baseMock.layout[axName] = { type: 'category', categoryorder: 'geometric mean ascending'};
1171+
1172+
var expectedAgg = [['a', Math.sqrt(data[0] * data2[0])], ['b', Math.sqrt(data[1] * data2[1])], ['c', Math.sqrt(data[2] * data2[2])]];
1173+
// TODO: how to actually calc these? what do these even mean?
1174+
if(type === 'histogram') expectedAgg = [['a', 2], ['b', 1], ['c', 1]];
1175+
if(type === 'histogram2d') expectedAgg = [['a', 0], ['b', 0], ['c', 0]];
1176+
if(type === 'contour' || type === 'heatmap') expectedAgg = [['a', 0], ['b', 0], ['c', 0]];
1177+
if(type === 'histogram2dcontour') expectedAgg = [['a', 0], ['b', 0], ['c', 0]];
1178+
1179+
checkAggregatedValue(baseMock, expectedAgg, false, done);
1180+
});
1181+
11631182
it('takes the median of all values per category across traces of type ' + trace.type, function(done) {
11641183
var type = trace.type;
11651184
var data = [7, 2, 3];

Diff for: test/jasmine/tests/lib_test.js

+18
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,24 @@ describe('Test lib.js:', function() {
126126
});
127127
});
128128

129+
describe('geometricMean() should', function() {
130+
it('toss out non-numerics (strings)', function() {
131+
var input = [1, 2, 'apple', 'orange'];
132+
var res = Lib.geometricMean(input);
133+
expect(res).toBeCloseTo(1.414, 3);
134+
});
135+
it('toss out non-numerics (NaN)', function() {
136+
var input = [1, 2, NaN];
137+
var res = Lib.geometricMean(input);
138+
expect(res).toBeCloseTo(1.414, 3);
139+
});
140+
it('evaluate numbers which are passed around as text strings:', function() {
141+
var input = ['1', '2'];
142+
var res = Lib.geometricMean(input);
143+
expect(res).toBeCloseTo(1.414, 3);
144+
});
145+
});
146+
129147
describe('midRange() should', function() {
130148
it('should calculate the arithmetic mean of the maximum and minimum value of a given array', function() {
131149
var input = [1, 5.5, 6, 15, 10, 13];

0 commit comments

Comments
 (0)