diff --git a/draftlogs/6223_add.md b/draftlogs/6223_add.md new file mode 100644 index 00000000000..d90cd7d103a --- /dev/null +++ b/draftlogs/6223_add.md @@ -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! diff --git a/src/lib/index.js b/src/lib/index.js index a414f8e592d..3f989fb550c 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -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; diff --git a/src/lib/stats.js b/src/lib/stats.js index 2f2350dfcf9..0ffbf4eb0e9 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -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; diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 6fe04d31406..4d0c64c34a2 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -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', @@ -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: { diff --git a/src/plots/plots.js b/src/plots/plots.js index e75d2066d83..ab48ea34915 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -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 = []; @@ -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);} }; diff --git a/test/jasmine/tests/calcdata_test.js b/test/jasmine/tests/calcdata_test.js index 2111018242a..86ef265c7b4 100644 --- a/test/jasmine/tests/calcdata_test.js +++ b/test/jasmine/tests/calcdata_test.js @@ -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) { + 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]; diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 0e69e7c6084..5d61e23f737 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -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]; diff --git a/test/plot-schema.json b/test/plot-schema.json index 929904f8d9a..6821a47fabc 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -4869,7 +4869,7 @@ "valType": "string" }, "categoryorder": { - "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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.", + "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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, geometric mean or median of all the values.", "dflt": "trace", "editType": "calc", "valType": "enumerated", @@ -4888,6 +4888,8 @@ "sum descending", "mean ascending", "mean descending", + "geometric mean ascending", + "geometric mean descending", "median ascending", "median descending" ] @@ -5653,7 +5655,7 @@ "valType": "string" }, "categoryorder": { - "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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.", + "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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, geometric mean or median of all the values.", "dflt": "trace", "editType": "calc", "valType": "enumerated", @@ -5672,6 +5674,8 @@ "sum descending", "mean ascending", "mean descending", + "geometric mean ascending", + "geometric mean descending", "median ascending", "median descending" ] @@ -7170,7 +7174,7 @@ "valType": "string" }, "categoryorder": { - "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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.", + "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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, geometric mean or median of all the values.", "dflt": "trace", "editType": "plot", "valType": "enumerated", @@ -7189,6 +7193,8 @@ "sum descending", "mean ascending", "mean descending", + "geometric mean ascending", + "geometric mean descending", "median ascending", "median descending" ] @@ -8002,7 +8008,7 @@ "valType": "string" }, "categoryorder": { - "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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.", + "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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, geometric mean or median of all the values.", "dflt": "trace", "editType": "plot", "valType": "enumerated", @@ -8021,6 +8027,8 @@ "sum descending", "mean ascending", "mean descending", + "geometric mean ascending", + "geometric mean descending", "median ascending", "median descending" ] @@ -8834,7 +8842,7 @@ "valType": "string" }, "categoryorder": { - "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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.", + "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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, geometric mean or median of all the values.", "dflt": "trace", "editType": "plot", "valType": "enumerated", @@ -8853,6 +8861,8 @@ "sum descending", "mean ascending", "mean descending", + "geometric mean ascending", + "geometric mean descending", "median ascending", "median descending" ] @@ -14011,7 +14021,7 @@ "valType": "string" }, "categoryorder": { - "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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.", + "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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, geometric mean or median of all the values.", "dflt": "trace", "editType": "calc", "valType": "enumerated", @@ -14030,6 +14040,8 @@ "sum descending", "mean ascending", "mean descending", + "geometric mean ascending", + "geometric mean descending", "median ascending", "median descending" ] @@ -15660,7 +15672,7 @@ "valType": "string" }, "categoryorder": { - "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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.", + "description": "Specifies the ordering logic for the case of categorical variables. By default, plotly uses *trace*, which specifies the order that is present in the data supplied. Set `categoryorder` to *category ascending* or *category descending* if order should be determined by the alphanumerical order of the category names. Set `categoryorder` to *array* to derive the ordering from the attribute `categoryarray`. If a category is not found in the `categoryarray` array, the sorting behavior for that attribute will be identical to 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, geometric mean or median of all the values.", "dflt": "trace", "editType": "calc", "valType": "enumerated", @@ -15679,6 +15691,8 @@ "sum descending", "mean ascending", "mean descending", + "geometric mean ascending", + "geometric mean descending", "median ascending", "median descending" ]