@@ -38,11 +38,8 @@ 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 bandwidth = cdi . bandwidth = trace . bandwidth || ruleOfThumbBandwidth ( vals , ssd , cdi . q3 - cdi . q1 ) ;
42+ var bandwidth = cdi . bandwidth = calcBandwidth ( trace , cdi , vals ) ;
4643 var span = cdi . span = calcSpan ( trace , cdi , valAxis , bandwidth ) ;
4744
4845 // step that well covers the bandwidth and is multiple of span distance
@@ -74,13 +71,30 @@ module.exports = function calc(gd, trace) {
7471 return cd ;
7572} ;
7673
77- // Default to Silveman's rule of thumb:
74+ // Default to Silveman's rule of thumb, but if 'too small' use Scott's rule
7875// - https://stats.stackexchange.com/a/6671
7976// - https://en.wikipedia.org/wiki/Kernel_density_estimation#A_rule-of-thumb_bandwidth_estimator
8077// - https://github.com/statsmodels/statsmodels/blob/master/statsmodels/nonparametric/bandwidths.py
81- function ruleOfThumbBandwidth ( vals , ssd , iqr ) {
78+ function silvermanRule ( len , ssd , iqr ) {
8279 var a = Math . min ( ssd , iqr / 1.349 ) ;
83- return 1.059 * a * Math . pow ( vals . length , - 0.2 ) ;
80+ return 1.059 * a * Math . pow ( len , - 0.2 ) ;
81+ }
82+
83+ function scottRule ( len , ssd ) {
84+ return ssd * Math . pow ( len , - 0.2 ) ;
85+ }
86+
87+ function calcBandwidth ( trace , cdi , vals ) {
88+ if ( trace . bandwidth ) return trace . bandwidth ;
89+
90+ var len = vals . length ;
91+ var ssd = Lib . stdev ( vals , len - 1 , cdi . mean ) ;
92+ var bw = silvermanRule ( len , ssd , cdi . q3 - cdi . q1 ) ;
93+
94+ if ( ( cdi . max - cdi . min ) / bw > 1e5 ) {
95+ bw = scottRule ( len , ssd ) ;
96+ }
97+ return bw ;
8498}
8599
86100function calcSpan ( trace , cdi , valAxis , bandwidth ) {
0 commit comments