Skip to content

Implement quartilemethod options in violin trace #6187

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 3 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions draftlogs/6187_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Add "exclusive" and "inclusive" quartile-computing algorithm to `violin` traces
via `quartilemethod` attribute [[#6187](https://github.com/plotly/plotly.js/pull/6187)]
2 changes: 1 addition & 1 deletion src/lib/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ exports.median = function(data) {
/**
* interp() computes a percentile (quantile) for a given distribution.
* We interpolate the distribution (to compute quantiles, we follow method #10 here:
* http://www.amstat.org/publications/jse/v14n3/langford.html).
* http://jse.amstat.org/v14n3/langford.html).
* Typically the index or rank (n * arr.length) may be non-integer.
* For reference: ends are clipped to the extreme values in the array;
* For box plots: index you get is half a point too high (see
Expand Down
2 changes: 1 addition & 1 deletion src/traces/box/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ module.exports = {
'Sets the method used to compute the sample\'s Q1 and Q3 quartiles.',

'The *linear* method uses the 25th percentile for Q1 and 75th percentile for Q3',
'as computed using method #10 (listed on http://www.amstat.org/publications/jse/v14n3/langford.html).',
'as computed using method #10 (listed on http://jse.amstat.org/v14n3/langford.html).',

'The *exclusive* method uses the median to divide the ordered dataset into two halves',
'if the sample is odd, it does not include the median in either half -',
Expand Down
2 changes: 2 additions & 0 deletions src/traces/violin/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ module.exports = {
hovertext: boxAttrs.hovertext,
hovertemplate: boxAttrs.hovertemplate,

quartilemethod: boxAttrs.quartilemethod,

box: {
visible: {
valType: 'boolean',
Expand Down
2 changes: 2 additions & 0 deletions src/traces/violin/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
var meanLineWidth = coerce2('meanline.width', lineWidth);
var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth));
if(!meanLineVisible) traceOut.meanline = {visible: false};

coerce('quartilemethod');
};
Binary file modified test/image/baselines/box_quartile-methods.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 33 additions & 1 deletion test/image/mocks/box_quartile-methods.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,37 @@
"y": [1, 2, 3, 4, 5],
"name": "inclusive",
"quartilemethod": "inclusive"
}]
},

{
"type": "violin",
"yaxis": "y2",
"y": [1, 2, 3, 4, 5],
"name": "linear"
}, {
"type": "violin",
"yaxis": "y2",
"y": [1, 2, 3, 4, 5],
"name": "exclusive",
"quartilemethod": "exclusive"
}, {
"type": "violin",
"yaxis": "y2",
"y": [1, 2, 3, 4, 5],
"name": "inclusive",
"quartilemethod": "inclusive"
}],
"layout": {
"yaxis": {
"domain": [0, 0.45]
},
"yaxis2": {
"domain": [0.55, 1]
},
"width": 500,
"height": 500,
"title": {
"text": "box and violin quartile methods"
}
}
}
77 changes: 77 additions & 0 deletions test/jasmine/tests/violin_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,85 @@ describe('Test violin calc:', function() {
Plots.doCalcdata(gd);
cd = gd.calcdata[0];
fullLayout = gd._fullLayout;
return cd;
}

it('should compute q1/q3 depending on *quartilemethod*', function() {
// samples from https://en.wikipedia.org/wiki/Quartile
var specs = {
// N is odd and is spanned by (4n+3)
odd: {
sample: [6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49],
methods: {
linear: {q1: 20.25, q3: 42.75},
exclusive: {q1: 15, q3: 43},
inclusive: {q1: 25.5, q3: 42.5}
}
},
// N is odd and is spanned by (4n+1)
odd2: {
sample: [6, 15, 36, 39, 40, 42, 43, 47, 49],
methods: {
linear: {q1: 30.75, q3: 44},
exclusive: {q1: 25.5, q3: 45},
inclusive: {q1: 36, q3: 43}
}
},
// N is even
even: {
sample: [7, 15, 36, 39, 40, 41],
methods: {
linear: {q1: 15, q3: 40},
exclusive: {q1: 15, q3: 40},
inclusive: {q1: 15, q3: 40}
}
},
// samples from http://jse.amstat.org/v14n3/langford.html
s4: {
sample: [1, 2, 3, 4],
methods: {
linear: {q1: 1.5, q3: 3.5},
exclusive: {q1: 1.5, q3: 3.5},
inclusive: {q1: 1.5, q3: 3.5}
}
},
s5: {
sample: [1, 2, 3, 4, 5],
methods: {
linear: {q1: 1.75, q3: 4.25},
exclusive: {q1: 1.5, q3: 4.5},
inclusive: {q1: 2, q3: 4}
}
},
s6: {
sample: [1, 2, 3, 4, 5, 6],
methods: {
linear: {q1: 2, q3: 5},
exclusive: {q1: 2, q3: 5},
inclusive: {q1: 2, q3: 5}
}
},
s7: {
sample: [1, 2, 3, 4, 5, 6, 7],
methods: {
linear: {q1: 2.25, q3: 5.75},
exclusive: {q1: 2, q3: 6},
inclusive: {q1: 2.5, q3: 5.5}
}
}
};

for(var name in specs) {
var spec = specs[name];

for(var m in spec.methods) {
var cd = _calc({y: spec.sample, quartilemethod: m});
expect(cd[0].q1).toBe(spec.methods[m].q1, ['q1', m, name].join(' | '));
expect(cd[0].q3).toBe(spec.methods[m].q3, ['q3', m, name].join(' | '));
}
}
});

it('should compute bandwidth and span based on the sample and *spanmode*', function() {
var y = [1, 1, 2, 2, 3];

Expand Down
13 changes: 12 additions & 1 deletion test/plot-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15905,7 +15905,7 @@
"valType": "string"
},
"quartilemethod": {
"description": "Sets the method used to compute the sample's Q1 and Q3 quartiles. The *linear* method uses the 25th percentile for Q1 and 75th percentile for Q3 as computed using method #10 (listed on http://www.amstat.org/publications/jse/v14n3/langford.html). The *exclusive* method uses the median to divide the ordered dataset into two halves if the sample is odd, it does not include the median in either half - Q1 is then the median of the lower half and Q3 the median of the upper half. The *inclusive* method also uses the median to divide the ordered dataset into two halves but if the sample is odd, it includes the median in both halves - Q1 is then the median of the lower half and Q3 the median of the upper half.",
"description": "Sets the method used to compute the sample's Q1 and Q3 quartiles. The *linear* method uses the 25th percentile for Q1 and 75th percentile for Q3 as computed using method #10 (listed on http://jse.amstat.org/v14n3/langford.html). The *exclusive* method uses the median to divide the ordered dataset into two halves if the sample is odd, it does not include the median in either half - Q1 is then the median of the lower half and Q3 the median of the upper half. The *inclusive* method also uses the median to divide the ordered dataset into two halves but if the sample is odd, it includes the median in both halves - Q1 is then the median of the lower half and Q3 the median of the upper half.",
"dflt": "linear",
"editType": "calc",
"valType": "enumerated",
Expand Down Expand Up @@ -69495,6 +69495,17 @@
false
]
},
"quartilemethod": {
"description": "Sets the method used to compute the sample's Q1 and Q3 quartiles. The *linear* method uses the 25th percentile for Q1 and 75th percentile for Q3 as computed using method #10 (listed on http://jse.amstat.org/v14n3/langford.html). The *exclusive* method uses the median to divide the ordered dataset into two halves if the sample is odd, it does not include the median in either half - Q1 is then the median of the lower half and Q3 the median of the upper half. The *inclusive* method also uses the median to divide the ordered dataset into two halves but if the sample is odd, it includes the median in both halves - Q1 is then the median of the lower half and Q3 the median of the upper half.",
"dflt": "linear",
"editType": "calc",
"valType": "enumerated",
"values": [
"linear",
"exclusive",
"inclusive"
]
},
"scalegroup": {
"description": "If there are multiple violins that should be sized according to to some metric (see `scalemode`), link them by providing a non-empty group id here shared by every trace in the same group. If a violin's `width` is undefined, `scalegroup` will default to the trace's name. In this case, violins with the same names will be linked together",
"dflt": "",
Expand Down