Skip to content

Commit 31e7086

Browse files
authored
Merge pull request #6187 from plotly/violin-quartile-method
Implement `quartilemethod` options in `violin` trace
2 parents 182cbca + 24ef4fd commit 31e7086

File tree

9 files changed

+130
-4
lines changed

9 files changed

+130
-4
lines changed

draftlogs/6187_add.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Add "exclusive" and "inclusive" quartile-computing algorithm to `violin` traces
2+
via `quartilemethod` attribute [[#6187](https://github.com/plotly/plotly.js/pull/6187)]

src/lib/stats.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ exports.median = function(data) {
7777
/**
7878
* interp() computes a percentile (quantile) for a given distribution.
7979
* We interpolate the distribution (to compute quantiles, we follow method #10 here:
80-
* http://www.amstat.org/publications/jse/v14n3/langford.html).
80+
* http://jse.amstat.org/v14n3/langford.html).
8181
* Typically the index or rank (n * arr.length) may be non-integer.
8282
* For reference: ends are clipped to the extreme values in the array;
8383
* For box plots: index you get is half a point too high (see

src/traces/box/attributes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ module.exports = {
275275
'Sets the method used to compute the sample\'s Q1 and Q3 quartiles.',
276276

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

280280
'The *exclusive* method uses the median to divide the ordered dataset into two halves',
281281
'if the sample is odd, it does not include the median in either half -',

src/traces/violin/attributes.js

+2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ module.exports = {
154154
hovertext: boxAttrs.hovertext,
155155
hovertemplate: boxAttrs.hovertemplate,
156156

157+
quartilemethod: boxAttrs.quartilemethod,
158+
157159
box: {
158160
visible: {
159161
valType: 'boolean',

src/traces/violin/defaults.js

+2
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
4848
var meanLineWidth = coerce2('meanline.width', lineWidth);
4949
var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth));
5050
if(!meanLineVisible) traceOut.meanline = {visible: false};
51+
52+
coerce('quartilemethod');
5153
};
9.39 KB
Loading

test/image/mocks/box_quartile-methods.json

+33-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,37 @@
1313
"y": [1, 2, 3, 4, 5],
1414
"name": "inclusive",
1515
"quartilemethod": "inclusive"
16-
}]
16+
},
17+
18+
{
19+
"type": "violin",
20+
"yaxis": "y2",
21+
"y": [1, 2, 3, 4, 5],
22+
"name": "linear"
23+
}, {
24+
"type": "violin",
25+
"yaxis": "y2",
26+
"y": [1, 2, 3, 4, 5],
27+
"name": "exclusive",
28+
"quartilemethod": "exclusive"
29+
}, {
30+
"type": "violin",
31+
"yaxis": "y2",
32+
"y": [1, 2, 3, 4, 5],
33+
"name": "inclusive",
34+
"quartilemethod": "inclusive"
35+
}],
36+
"layout": {
37+
"yaxis": {
38+
"domain": [0, 0.45]
39+
},
40+
"yaxis2": {
41+
"domain": [0.55, 1]
42+
},
43+
"width": 500,
44+
"height": 500,
45+
"title": {
46+
"text": "box and violin quartile methods"
47+
}
48+
}
1749
}

test/jasmine/tests/violin_test.js

+77
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,85 @@ describe('Test violin calc:', function() {
223223
Plots.doCalcdata(gd);
224224
cd = gd.calcdata[0];
225225
fullLayout = gd._fullLayout;
226+
return cd;
226227
}
227228

229+
it('should compute q1/q3 depending on *quartilemethod*', function() {
230+
// samples from https://en.wikipedia.org/wiki/Quartile
231+
var specs = {
232+
// N is odd and is spanned by (4n+3)
233+
odd: {
234+
sample: [6, 7, 15, 36, 39, 40, 41, 42, 43, 47, 49],
235+
methods: {
236+
linear: {q1: 20.25, q3: 42.75},
237+
exclusive: {q1: 15, q3: 43},
238+
inclusive: {q1: 25.5, q3: 42.5}
239+
}
240+
},
241+
// N is odd and is spanned by (4n+1)
242+
odd2: {
243+
sample: [6, 15, 36, 39, 40, 42, 43, 47, 49],
244+
methods: {
245+
linear: {q1: 30.75, q3: 44},
246+
exclusive: {q1: 25.5, q3: 45},
247+
inclusive: {q1: 36, q3: 43}
248+
}
249+
},
250+
// N is even
251+
even: {
252+
sample: [7, 15, 36, 39, 40, 41],
253+
methods: {
254+
linear: {q1: 15, q3: 40},
255+
exclusive: {q1: 15, q3: 40},
256+
inclusive: {q1: 15, q3: 40}
257+
}
258+
},
259+
// samples from http://jse.amstat.org/v14n3/langford.html
260+
s4: {
261+
sample: [1, 2, 3, 4],
262+
methods: {
263+
linear: {q1: 1.5, q3: 3.5},
264+
exclusive: {q1: 1.5, q3: 3.5},
265+
inclusive: {q1: 1.5, q3: 3.5}
266+
}
267+
},
268+
s5: {
269+
sample: [1, 2, 3, 4, 5],
270+
methods: {
271+
linear: {q1: 1.75, q3: 4.25},
272+
exclusive: {q1: 1.5, q3: 4.5},
273+
inclusive: {q1: 2, q3: 4}
274+
}
275+
},
276+
s6: {
277+
sample: [1, 2, 3, 4, 5, 6],
278+
methods: {
279+
linear: {q1: 2, q3: 5},
280+
exclusive: {q1: 2, q3: 5},
281+
inclusive: {q1: 2, q3: 5}
282+
}
283+
},
284+
s7: {
285+
sample: [1, 2, 3, 4, 5, 6, 7],
286+
methods: {
287+
linear: {q1: 2.25, q3: 5.75},
288+
exclusive: {q1: 2, q3: 6},
289+
inclusive: {q1: 2.5, q3: 5.5}
290+
}
291+
}
292+
};
293+
294+
for(var name in specs) {
295+
var spec = specs[name];
296+
297+
for(var m in spec.methods) {
298+
var cd = _calc({y: spec.sample, quartilemethod: m});
299+
expect(cd[0].q1).toBe(spec.methods[m].q1, ['q1', m, name].join(' | '));
300+
expect(cd[0].q3).toBe(spec.methods[m].q3, ['q3', m, name].join(' | '));
301+
}
302+
}
303+
});
304+
228305
it('should compute bandwidth and span based on the sample and *spanmode*', function() {
229306
var y = [1, 1, 2, 2, 3];
230307

test/plot-schema.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -15905,7 +15905,7 @@
1590515905
"valType": "string"
1590615906
},
1590715907
"quartilemethod": {
15908-
"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.",
15908+
"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.",
1590915909
"dflt": "linear",
1591015910
"editType": "calc",
1591115911
"valType": "enumerated",
@@ -69495,6 +69495,17 @@
6949569495
false
6949669496
]
6949769497
},
69498+
"quartilemethod": {
69499+
"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.",
69500+
"dflt": "linear",
69501+
"editType": "calc",
69502+
"valType": "enumerated",
69503+
"values": [
69504+
"linear",
69505+
"exclusive",
69506+
"inclusive"
69507+
]
69508+
},
6949869509
"scalegroup": {
6949969510
"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",
6950069511
"dflt": "",

0 commit comments

Comments
 (0)