-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathcalc.js
138 lines (118 loc) · 4.59 KB
/
calc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
'use strict';
var isNumeric = require('fast-isnumeric');
var Plotly = require('../../plotly');
var Lib = require('../../lib');
module.exports = function calc(gd, trace) {
// outlier definition based on http://www.physics.csbsju.edu/stats/box2.html
var xa = Plotly.Axes.getFromId(gd, trace.xaxis||'x'),
ya = Plotly.Axes.getFromId(gd, trace.yaxis||'y'),
orientation = trace.orientation,
cd = [],
valAxis, valLetter, val, valBinned,
posAxis, posLetter, pos, posDistinct, dPos;
// Set value (val) and position (pos) keys via orientation
if (orientation==='h') {
valAxis = xa;
valLetter = 'x';
posAxis = ya;
posLetter = 'y';
} else {
valAxis = ya;
valLetter = 'y';
posAxis = xa;
posLetter = 'x';
}
val = valAxis.makeCalcdata(trace, valLetter); // get val
// size autorange based on all source points
// position happens afterward when we know all the pos
Plotly.Axes.expand(valAxis, val, {padded: true});
// In vertical (horizontal) box plots:
// if no x (y) data, use x0 (y0), or name
// so if you want one box
// per trace, set x0 (y0) to the x (y) value or category for this trace
// (or set x (y) to a constant array matching y (x))
function getPos (gd, trace, posLetter, posAxis, val) {
var pos0;
if (posLetter in trace) pos = posAxis.makeCalcdata(trace, posLetter);
else {
if (posLetter+'0' in trace) pos0 = trace[posLetter+'0'];
else if ('name' in trace && (
posAxis.type==='category' ||
(isNumeric(trace.name) &&
['linear','log'].indexOf(posAxis.type)!==-1) ||
(Lib.isDateTime(trace.name) &&
posAxis.type==='date')
)) {
pos0 = trace.name;
}
else pos0 = gd.numboxes;
pos0 = posAxis.d2c(pos0);
pos = val.map(function(){ return pos0; });
}
return pos;
}
pos = getPos(gd, trace, posLetter, posAxis, val);
// get distinct positions and min difference
var dv = Lib.distinctVals(pos);
posDistinct = dv.vals;
dPos = dv.minDiff/2;
function binVal (cd, val, pos, posDistinct, dPos) {
var posDistinctLength = posDistinct.length,
valLength = val.length,
valBinned = [],
bins = [],
i, p, n, v;
// store distinct pos in cd, find bins, init. valBinned
for (i = 0; i < posDistinctLength; ++i) {
p = posDistinct[i];
cd[i] = {pos: p};
bins[i] = p - dPos;
valBinned[i] = [];
}
bins.push(posDistinct[posDistinctLength-1] + dPos);
// bin the values
for (i = 0; i < valLength; ++i) {
v = val[i];
if(!isNumeric(v)) continue;
n = Lib.findBin(pos[i], bins);
if(n>=0 && n<valLength) valBinned[n].push(v);
}
return valBinned;
}
valBinned = binVal(cd, val, pos, posDistinct, dPos);
// sort the bins and calculate the stats
function calculateStats (cd, valBinned) {
var v, l, cdi, i;
for (i = 0; i < valBinned.length; ++i) {
v = valBinned[i].sort(Lib.sorterAsc);
l = v.length;
cdi = cd[i];
cdi.val = v; // put all values into calcdata
cdi.min = v[0];
cdi.max = v[l-1];
cdi.mean = Lib.mean(v,l);
cdi.sd = Lib.stdev(v,l,cdi.mean);
cdi.q1 = Lib.interp(v, 0.25); // first quartile
cdi.med = Lib.interp(v, 0.5); // median
cdi.q3 = Lib.interp(v, 0.75); // third quartile
// lower and upper fences - last point inside
// 1.5 interquartile ranges from quartiles
cdi.lf = Math.min(cdi.q1, v[
Math.min(Lib.findBin(2.5*cdi.q1-1.5*cdi.q3,v,true)+1, l-1)]);
cdi.uf = Math.max(cdi.q3,v[
Math.max(Lib.findBin(2.5*cdi.q3-1.5*cdi.q1,v), 0)]);
// lower and upper outliers - 3 IQR out (don't clip to max/min,
// this is only for discriminating suspected & far outliers)
cdi.lo = 4*cdi.q1-3*cdi.q3;
cdi.uo = 4*cdi.q3-3*cdi.q1;
}
}
calculateStats(cd, valBinned);
// remove empty bins
cd = cd.filter(function(cdi){ return cdi.val && cdi.val.length; });
if(!cd.length) return [{t: {emptybox: true}}];
// add numboxes and dPos to cd
cd[0].t = {boxnum: gd.numboxes, dPos: dPos};
gd.numboxes++;
return cd;
};