Skip to content
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

Implement quartilemethod options in violin trace #6187

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions draftlogs/6187_add.md
@@ -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
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
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
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
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
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
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
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
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