Skip to content

Commit c2457ff

Browse files
authoredJun 29, 2018
Merge pull request #2764 from bhogan-mitre/aggregate-functions
Aggregate functions
2 parents 0673823 + 165b5bf commit c2457ff

File tree

2 files changed

+35
-3
lines changed

2 files changed

+35
-3
lines changed
 

‎src/transforms/aggregate.js

+25-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ var attrs = exports.attributes = {
6868
},
6969
func: {
7070
valType: 'enumerated',
71-
values: ['count', 'sum', 'avg', 'median', 'mode', 'rms', 'stddev', 'min', 'max', 'first', 'last'],
71+
values: ['count', 'sum', 'avg', 'median', 'mode', 'rms', 'stddev', 'min', 'max', 'first', 'last', 'change', 'range'],
7272
dflt: 'first',
7373
role: 'info',
7474
editType: 'calc',
@@ -86,7 +86,9 @@ var attrs = exports.attributes = {
8686
'for example a sum of dates or average of categories.',
8787
'*median* will return the average of the two central values if there is',
8888
'an even count. *mode* will return the first value to reach the maximum',
89-
'count, in case of a tie.'
89+
'count, in case of a tie.',
90+
'*change* will return the difference between the first and last linked values.',
91+
'*range* will return the difference between the min and max linked values.'
9092
].join(' ')
9193
},
9294
funcmode: {
@@ -345,6 +347,27 @@ function getAggregateFunction(opts, conversions) {
345347
return (out === -Infinity) ? BADNUM : c2d(out);
346348
};
347349

350+
case 'range':
351+
return function(array, indices) {
352+
var min = Infinity;
353+
var max = -Infinity;
354+
for(var i = 0; i < indices.length; i++) {
355+
var vi = d2c(array[indices[i]]);
356+
if(vi !== BADNUM) {
357+
min = Math.min(min, vi);
358+
max = Math.max(max, vi);
359+
}
360+
}
361+
return (max === -Infinity || min === Infinity) ? BADNUM : c2d(max - min);
362+
};
363+
364+
case 'change':
365+
return function(array, indices) {
366+
var first = d2c(array[indices[0]]);
367+
var last = d2c(array[indices[indices.length - 1]]);
368+
return (first === BADNUM || last === BADNUM) ? BADNUM : c2d(last - first);
369+
};
370+
348371
case 'median':
349372
return function(array, indices) {
350373
var sortCalc = [];

‎test/jasmine/tests/transform_aggregate_test.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe('aggregate', function() {
1515
Plotly.newPlot(gd, [{
1616
x: [1, 2, 3, 4, 'fail'],
1717
y: [1.1, 2.2, 3.3, 'nope', 5.5],
18+
customdata: [4, 'nope', 3, 2, 1],
1819
marker: {
1920
size: ['2001-01-01', 0.2, 0.1, 0.4, 0.5],
2021
color: [2, 4, '', 10, 8],
@@ -34,6 +35,7 @@ describe('aggregate', function() {
3435
{target: 'x', func: 'sum'},
3536
// non-numerics will not count toward numerator or denominator for avg
3637
{target: 'y', func: 'avg'},
38+
{target: 'customdata', func: 'change'},
3739
{target: 'marker.size', func: 'min'},
3840
{target: 'marker.color', func: 'max'},
3941
// marker.opacity doesn't have an entry, but it will default to first
@@ -54,6 +56,7 @@ describe('aggregate', function() {
5456

5557
expect(traceOut.x).toEqual([8, 2]);
5658
expect(traceOut.y).toBeCloseToArray([3.3, 2.2], 5);
59+
expect(traceOut.customdata).toEqual([-3, undefined]);
5760
expect(traceOut.marker.size).toEqual([0.1, 0.2]);
5861
expect(traceOut.marker.color).toEqual([10, 4]);
5962
expect(traceOut.marker.opacity).toEqual([0.6, 'boo']);
@@ -221,15 +224,17 @@ describe('aggregate', function() {
221224
expect(inverseMapping).toEqual({0: [0, 1, 4], 1: [2, 3]});
222225
});
223226

224-
it('handles median, mode, rms, & stddev for numeric data', function() {
227+
it('handles median, mode, rms, stddev, change & range for numeric data', function() {
225228
// again, nothing is going to barf with non-numeric data, but sometimes it
226229
// won't make much sense.
227230

228231
Plotly.newPlot(gd, [{
229232
x: [1, 1, 2, 2, 1],
230233
y: [1, 2, 3, 4, 5],
234+
customdata: [5, 4, 3, 2, 1],
231235
marker: {
232236
size: [1, 2, 3, 4, 5],
237+
opacity: [0.6, 0.5, 0.2, 0.8, 1.0],
233238
line: {width: [1, 1, 2, 2, 1]},
234239
color: [1, 1, 2, 2, 1]
235240
},
@@ -239,7 +244,9 @@ describe('aggregate', function() {
239244
aggregations: [
240245
{target: 'x', func: 'mode'},
241246
{target: 'y', func: 'median'},
247+
{target: 'customdata', func: 'change'},
242248
{target: 'marker.size', func: 'rms'},
249+
{target: 'marker.opacity', func: 'range'},
243250
{target: 'marker.line.width', func: 'stddev', funcmode: 'population'},
244251
{target: 'marker.color', func: 'stddev'}
245252
]
@@ -252,7 +259,9 @@ describe('aggregate', function() {
252259
// but 2 gets to that count first
253260
expect(traceOut.x).toEqual([2, 1]);
254261
expect(traceOut.y).toBeCloseToArray([3.5, 2], 5);
262+
expect(traceOut.customdata).toEqual([-4, 0]);
255263
expect(traceOut.marker.size).toBeCloseToArray([Math.sqrt(51 / 4), 2], 5);
264+
expect(traceOut.marker.opacity).toEqual([0.8, 0]);
256265
expect(traceOut.marker.line.width).toBeCloseToArray([0.5, 0], 5);
257266
expect(traceOut.marker.color).toBeCloseToArray([Math.sqrt(1 / 3), 0], 5);
258267
});

0 commit comments

Comments
 (0)
Please sign in to comment.