Skip to content

Commit 857cd05

Browse files
authored
Merge pull request #2650 from plotly/violin-span-fix
Violin span fix
2 parents e004a09 + 74b5770 commit 857cd05

File tree

3 files changed

+712
-9
lines changed

3 files changed

+712
-9
lines changed

src/traces/violin/calc.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,13 @@ module.exports = function calc(gd, trace) {
3838
for(var i = 0; i < cd.length; i++) {
3939
var cdi = cd[i];
4040
var vals = cdi.pts.map(helpers.extractVal);
41-
var len = vals.length;
4241

43-
// sample standard deviation
44-
var ssd = Lib.stdev(vals, len - 1, cdi.mean);
45-
var bandwidthDflt = ruleOfThumbBandwidth(vals, ssd, cdi.q3 - cdi.q1);
46-
var bandwidth = cdi.bandwidth = trace.bandwidth || bandwidthDflt;
42+
var bandwidth = cdi.bandwidth = calcBandwidth(trace, cdi, vals);
4743
var span = cdi.span = calcSpan(trace, cdi, valAxis, bandwidth);
4844

4945
// step that well covers the bandwidth and is multiple of span distance
5046
var dist = span[1] - span[0];
51-
var n = Math.ceil(dist / (Math.min(bandwidthDflt, bandwidth) / 3));
47+
var n = Math.ceil(dist / (bandwidth / 3));
5248
var step = dist / n;
5349

5450
if(!isFinite(step) || !isFinite(n)) {
@@ -75,13 +71,36 @@ module.exports = function calc(gd, trace) {
7571
return cd;
7672
};
7773

78-
// Default to Silveman's rule of thumb:
74+
// Default to Silveman's rule of thumb
7975
// - https://stats.stackexchange.com/a/6671
8076
// - https://en.wikipedia.org/wiki/Kernel_density_estimation#A_rule-of-thumb_bandwidth_estimator
8177
// - https://github.com/statsmodels/statsmodels/blob/master/statsmodels/nonparametric/bandwidths.py
82-
function ruleOfThumbBandwidth(vals, ssd, iqr) {
78+
function silvermanRule(len, ssd, iqr) {
8379
var a = Math.min(ssd, iqr / 1.349);
84-
return 1.059 * a * Math.pow(vals.length, -0.2);
80+
return 1.059 * a * Math.pow(len, -0.2);
81+
}
82+
83+
function calcBandwidth(trace, cdi, vals) {
84+
var span = cdi.max - cdi.min;
85+
86+
// Limit how small the bandwidth can be.
87+
//
88+
// Silverman's rule of thumb can be "very" small
89+
// when IQR does a poor job at describing the spread
90+
// of the distribution.
91+
// We also want to limit custom bandwidths
92+
// to not blow up kde computations.
93+
94+
if(trace.bandwidth) {
95+
return Math.max(trace.bandwidth, span / 1e4);
96+
} else {
97+
var len = vals.length;
98+
var ssd = Lib.stdev(vals, len - 1, cdi.mean);
99+
return Math.max(
100+
silvermanRule(len, ssd, cdi.q3 - cdi.q1),
101+
span / 100
102+
);
103+
}
85104
}
86105

87106
function calcSpan(trace, cdi, valAxis, bandwidth) {
Loading

0 commit comments

Comments
 (0)