1 /*
  2     Copyright 2008-2024
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true*/
 33 /*jslint nomen: true, plusplus: true*/
 34 
 35 import JXG from "../jxg.js";
 36 import Mat from "./math.js";
 37 import Type from "../utils/type.js";
 38 
 39 /**
 40  * Functions for mathematical statistics. Most functions are like in the statistics package R.
 41  * @name JXG.Math.Statistics
 42  * @exports Mat.Statistics as JXG.Math.Statistics
 43  * @namespace
 44  */
 45 Mat.Statistics = {
 46     /**
 47      * Sums up all elements of the given array.
 48      * @param {Array} arr An array of numbers.
 49      * @returns {Number}
 50      * @memberof JXG.Math.Statistics
 51      */
 52     sum: function (arr) {
 53         var i,
 54             len = arr.length,
 55             res = 0;
 56 
 57         for (i = 0; i < len; i++) {
 58             res += arr[i];
 59         }
 60         return res;
 61     },
 62 
 63     /**
 64      * Multiplies all elements of the given array.
 65      * @param {Array} arr An array of numbers.
 66      * @returns {Number}
 67      * @memberof JXG.Math.Statistics
 68      */
 69     prod: function (arr) {
 70         var i,
 71             len = arr.length,
 72             res = 1;
 73 
 74         for (i = 0; i < len; i++) {
 75             res *= arr[i];
 76         }
 77         return res;
 78     },
 79 
 80     /**
 81      * Determines the mean value of the values given in an array.
 82      * @param {Array} arr
 83      * @returns {Number}
 84      * @memberof JXG.Math.Statistics
 85      */
 86     mean: function (arr) {
 87         if (arr.length > 0) {
 88             return this.sum(arr) / arr.length;
 89         }
 90 
 91         return 0.0;
 92     },
 93 
 94     /**
 95      * The median of a finite set of values is the value that divides the set
 96      * into two equal sized subsets.
 97      * @param {Array} arr The set of values.
 98      * @returns {Number}
 99      * @memberof JXG.Math.Statistics
100      */
101     median: function (arr) {
102         var tmp, len;
103 
104         if (arr.length > 0) {
105             if (ArrayBuffer.isView(arr)) {
106                 tmp = new Float64Array(arr);
107                 tmp.sort();
108             } else {
109                 tmp = arr.slice(0);
110                 tmp.sort(function (a, b) {
111                     return a - b;
112                 });
113             }
114             len = tmp.length;
115 
116             if (len & 1) {
117                 // odd
118                 return tmp[parseInt(len * 0.5, 10)];
119             }
120 
121             return (tmp[len * 0.5 - 1] + tmp[len * 0.5]) * 0.5;
122         }
123 
124         return 0.0;
125     },
126 
127     /**
128      * The P-th percentile ( <i>0 < P ≤ 100</i> ) of a list of <i>N</i> ordered values (sorted from least to greatest)
129      * is the smallest value in the list such that no more than <i>P</i> percent of the data is strictly less
130      * than the value and at least <i>P</i> percent of the data is less than or equal to that value.
131      * See <a href="https://en.wikipedia.org/wiki/Percentile">https://en.wikipedia.org/wiki/Percentile</a>.
132      *
133      * Here, the <i>linear interpolation between closest ranks</i> method is used.
134      * @param {Array} arr The set of values, need not be ordered.
135      * @param {Number|Array} percentile One or several percentiles
136      * @returns {Number|Array} Depending if a number or an array is the input for percentile, a number or an array containing the percentiles
137      * is returned.
138      */
139     percentile: function (arr, percentile) {
140         var tmp,
141             len,
142             i,
143             p,
144             res = [],
145             per;
146 
147         if (arr.length > 0) {
148             if (ArrayBuffer.isView(arr)) {
149                 tmp = new Float64Array(arr);
150                 tmp.sort();
151             } else {
152                 tmp = arr.slice(0);
153                 tmp.sort(function (a, b) {
154                     return a - b;
155                 });
156             }
157             len = tmp.length;
158 
159             if (Type.isArray(percentile)) {
160                 p = percentile;
161             } else {
162                 p = [percentile];
163             }
164 
165             for (i = 0; i < p.length; i++) {
166                 per = len * p[i] * 0.01;
167                 if (parseInt(per, 10) === per) {
168                     res.push((tmp[per - 1] + tmp[per]) * 0.5);
169                 } else {
170                     res.push(tmp[parseInt(per, 10)]);
171                 }
172             }
173 
174             if (Type.isArray(percentile)) {
175                 return res;
176             } else {
177                 return res[0];
178             }
179         }
180 
181         return 0.0;
182     },
183 
184     /**
185      * Bias-corrected sample variance. A variance is a measure of how far a
186      * set of numbers are spread out from each other.
187      * @param {Array} arr
188      * @returns {Number}
189      * @memberof JXG.Math.Statistics
190      */
191     variance: function (arr) {
192         var m,
193             res,
194             i,
195             len = arr.length;
196 
197         if (len > 1) {
198             m = this.mean(arr);
199             res = 0;
200             for (i = 0; i < len; i++) {
201                 res += (arr[i] - m) * (arr[i] - m);
202             }
203             return res / (arr.length - 1);
204         }
205 
206         return 0.0;
207     },
208 
209     /**
210      * Determines the <strong>s</strong>tandard <strong>d</strong>eviation which shows how much
211      * variation there is from the average value of a set of numbers.
212      * @param {Array} arr
213      * @returns {Number}
214      * @memberof JXG.Math.Statistics
215      */
216     sd: function (arr) {
217         return Math.sqrt(this.variance(arr));
218     },
219 
220     /**
221      * Weighted mean value is basically the same as {@link JXG.Math.Statistics.mean} but here the values
222      * are weighted, i.e. multiplied with another value called <em>weight</em>. The weight values are given
223      * as a second array with the same length as the value array..
224      * @throws {Error} If the dimensions of the arrays don't match.
225      * @param {Array} arr Set of alues.
226      * @param {Array} w Weight values.
227      * @returns {Number}
228      * @memberof JXG.Math.Statistics
229      */
230     weightedMean: function (arr, w) {
231         if (arr.length !== w.length) {
232             throw new Error(
233                 "JSXGraph error (Math.Statistics.weightedMean): Array dimension mismatch."
234             );
235         }
236 
237         if (arr.length > 0) {
238             return this.mean(this.multiply(arr, w));
239         }
240 
241         return 0.0;
242     },
243 
244     /**
245      * Extracts the maximum value from the array.
246      * @param {Array} arr
247      * @returns {Number} The highest number from the array. It returns <tt>NaN</tt> if not every element could be
248      * interpreted as a number and <tt>-Infinity</tt> if an empty array is given or no element could be interpreted
249      * as a number.
250      * @memberof JXG.Math.Statistics
251      */
252     max: function (arr) {
253         return Math.max.apply(this, arr);
254     },
255 
256     /**
257      * Extracts the minimum value from the array.
258      * @param {Array} arr
259      * @returns {Number} The lowest number from the array. It returns <tt>NaN</tt> if not every element could be
260      * interpreted as a number and <tt>Infinity</tt> if an empty array is given or no element could be interpreted
261      * as a number.
262      * @memberof JXG.Math.Statistics
263      */
264     min: function (arr) {
265         return Math.min.apply(this, arr);
266     },
267 
268     /**
269      * Determines the lowest and the highest value from the given array.
270      * @param {Array} arr
271      * @returns {Array} The minimum value as the first and the maximum value as the second value.
272      * @memberof JXG.Math.Statistics
273      */
274     range: function (arr) {
275         return [this.min(arr), this.max(arr)];
276     },
277 
278     /**
279      * Determines the absolute value of every given value.
280      * @param {Array|Number} arr
281      * @returns {Array|Number}
282      * @memberof JXG.Math.Statistics
283      */
284     abs: function (arr) {
285         var i, len, res;
286 
287         if (Type.isArray(arr)) {
288             if (arr.map) {
289                 res = arr.map(Math.abs);
290             } else {
291                 len = arr.length;
292                 res = [];
293 
294                 for (i = 0; i < len; i++) {
295                     res[i] = Math.abs(arr[i]);
296                 }
297             }
298         } else if (ArrayBuffer.isView(arr)) {
299             res = arr.map(Math.abs);
300         } else {
301             res = Math.abs(arr);
302         }
303         return res;
304     },
305 
306     /**
307      * Adds up two (sequences of) values. If one value is an array and the other one is a number the number
308      * is added to every element of the array. If two arrays are given and the lengths don't match the shortest
309      * length is taken.
310      * @param {Array|Number} arr1
311      * @param {Array|Number} arr2
312      * @returns {Array|Number}
313      * @memberof JXG.Math.Statistics
314      */
315     add: function (arr1, arr2) {
316         var i,
317             len,
318             res = [];
319 
320         arr1 = Type.evalSlider(arr1);
321         arr2 = Type.evalSlider(arr2);
322 
323         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
324             len = arr1.length;
325 
326             for (i = 0; i < len; i++) {
327                 res[i] = arr1[i] + arr2;
328             }
329         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
330             len = arr2.length;
331 
332             for (i = 0; i < len; i++) {
333                 res[i] = arr1 + arr2[i];
334             }
335         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
336             len = Math.min(arr1.length, arr2.length);
337 
338             for (i = 0; i < len; i++) {
339                 res[i] = arr1[i] + arr2[i];
340             }
341         } else {
342             res = arr1 + arr2;
343         }
344 
345         return res;
346     },
347 
348     /**
349      * Divides two (sequences of) values. If two arrays are given and the lengths don't match the shortest length
350      * is taken.
351      * @param {Array|Number} arr1 Dividend
352      * @param {Array|Number} arr2 Divisor
353      * @returns {Array|Number}
354      * @memberof JXG.Math.Statistics
355      */
356     div: function (arr1, arr2) {
357         var i,
358             len,
359             res = [];
360 
361         arr1 = Type.evalSlider(arr1);
362         arr2 = Type.evalSlider(arr2);
363 
364         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
365             len = arr1.length;
366 
367             for (i = 0; i < len; i++) {
368                 res[i] = arr1[i] / arr2;
369             }
370         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
371             len = arr2.length;
372 
373             for (i = 0; i < len; i++) {
374                 res[i] = arr1 / arr2[i];
375             }
376         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
377             len = Math.min(arr1.length, arr2.length);
378 
379             for (i = 0; i < len; i++) {
380                 res[i] = arr1[i] / arr2[i];
381             }
382         } else {
383             res = arr1 / arr2;
384         }
385 
386         return res;
387     },
388 
389     /**
390      * @function
391      * @deprecated Use {@link JXG.Math.Statistics.div} instead.
392      * @memberof JXG.Math.Statistics
393      */
394     divide: function () {
395         JXG.deprecated("Statistics.divide()", "Statistics.div()");
396         Mat.Statistics.div.apply(Mat.Statistics, arguments);
397     },
398 
399     /**
400      * Divides two (sequences of) values and returns the remainder. If two arrays are given and the lengths don't
401      * match the shortest length is taken.
402      * @param {Array|Number} arr1 Dividend
403      * @param {Array|Number} arr2 Divisor
404      * @param {Boolean} [math=false] Mathematical mod or symmetric mod? Default is symmetric, the JavaScript <tt>%</tt> operator.
405      * @returns {Array|Number}
406      * @memberof JXG.Math.Statistics
407      */
408     mod: function (arr1, arr2, math) {
409         var i,
410             len,
411             res = [],
412             mod = function (a, m) {
413                 return a % m;
414             };
415 
416         math = Type.def(math, false);
417 
418         if (math) {
419             mod = Mat.mod;
420         }
421 
422         arr1 = Type.evalSlider(arr1);
423         arr2 = Type.evalSlider(arr2);
424 
425         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
426             len = arr1.length;
427 
428             for (i = 0; i < len; i++) {
429                 res[i] = mod(arr1[i], arr2);
430             }
431         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
432             len = arr2.length;
433 
434             for (i = 0; i < len; i++) {
435                 res[i] = mod(arr1, arr2[i]);
436             }
437         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
438             len = Math.min(arr1.length, arr2.length);
439 
440             for (i = 0; i < len; i++) {
441                 res[i] = mod(arr1[i], arr2[i]);
442             }
443         } else {
444             res = mod(arr1, arr2);
445         }
446 
447         return res;
448     },
449 
450     /**
451      * Multiplies two (sequences of) values. If one value is an array and the other one is a number the number
452      * is multiplied to every element of the array. If two arrays are given and the lengths don't match the shortest
453      * length is taken.
454      * @param {Array|Number} arr1
455      * @param {Array|Number} arr2
456      * @returns {Array|Number}
457      * @memberof JXG.Math.Statistics
458      */
459     multiply: function (arr1, arr2) {
460         var i,
461             len,
462             res = [];
463 
464         arr1 = Type.evalSlider(arr1);
465         arr2 = Type.evalSlider(arr2);
466 
467         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
468             len = arr1.length;
469 
470             for (i = 0; i < len; i++) {
471                 res[i] = arr1[i] * arr2;
472             }
473         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
474             len = arr2.length;
475 
476             for (i = 0; i < len; i++) {
477                 res[i] = arr1 * arr2[i];
478             }
479         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
480             len = Math.min(arr1.length, arr2.length);
481 
482             for (i = 0; i < len; i++) {
483                 res[i] = arr1[i] * arr2[i];
484             }
485         } else {
486             res = arr1 * arr2;
487         }
488 
489         return res;
490     },
491 
492     /**
493      * Subtracts two (sequences of) values. If two arrays are given and the lengths don't match the shortest
494      * length is taken.
495      * @param {Array|Number} arr1 Minuend
496      * @param {Array|Number} arr2 Subtrahend
497      * @returns {Array|Number}
498      * @memberof JXG.Math.Statistics
499      */
500     subtract: function (arr1, arr2) {
501         var i,
502             len,
503             res = [];
504 
505         arr1 = Type.evalSlider(arr1);
506         arr2 = Type.evalSlider(arr2);
507 
508         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
509             len = arr1.length;
510 
511             for (i = 0; i < len; i++) {
512                 res[i] = arr1[i] - arr2;
513             }
514         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
515             len = arr2.length;
516 
517             for (i = 0; i < len; i++) {
518                 res[i] = arr1 - arr2[i];
519             }
520         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
521             len = Math.min(arr1.length, arr2.length);
522 
523             for (i = 0; i < len; i++) {
524                 res[i] = arr1[i] - arr2[i];
525             }
526         } else {
527             res = arr1 - arr2;
528         }
529 
530         return res;
531     },
532 
533     /**
534      * The Theil-Sen estimator can be used to determine a more robust linear regression of a set of sample
535      * points than least squares regression in {@link JXG.Math.Numerics.regressionPolynomial}.
536      *
537      * If the function should be applied to an array a of points, a the coords array can be generated with
538      * JavaScript array.map:
539      *
540      * <pre>
541      * JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords));
542      * </pre>
543      *
544      * @param {Array} coords Array of {@link JXG.Coords}.
545      * @returns {Array} A stdform array of the regression line.
546      * @memberof JXG.Math.Statistics
547      *
548      * @example
549      * var board = JXG.JSXGraph.initBoard('jxgbox', { boundingbox: [-6,6,6,-6], axis : true });
550      * var a=[];
551      * a[0]=board.create('point', [0,0]);
552      * a[1]=board.create('point', [3,0]);
553      * a[2]=board.create('point', [0,3]);
554      *
555      * board.create('line', [
556      *     () => JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords))
557      *   ],
558      *   {strokeWidth:1, strokeColor:'black'});
559      *
560      * </pre><div id="JXG0a28be85-91c5-44d3-aae6-114e81217cf0" class="jxgbox" style="width: 300px; height: 300px;"></div>
561      * <script type="text/javascript">
562      *     (function() {
563      *         var board = JXG.JSXGraph.initBoard('JXG0a28be85-91c5-44d3-aae6-114e81217cf0',
564      *             {boundingbox: [-6,6,6,-6], axis: true, showcopyright: false, shownavigation: false});
565      *     var a=[];
566      *     a[0]=board.create('point', [0,0]);
567      *     a[1]=board.create('point', [3,0]);
568      *     a[2]=board.create('point', [0,3]);
569      *
570      *     board.create('line', [
571      *         () => JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords))
572      *       ],
573      *       {strokeWidth:1, strokeColor:'black'});
574      *
575      *     })();
576      *
577      * </script><pre>
578      *
579      */
580     TheilSenRegression: function (coords) {
581         var i,
582             j,
583             slopes = [],
584             tmpslopes = [],
585             yintercepts = [];
586 
587         for (i = 0; i < coords.length; i++) {
588             tmpslopes.length = 0;
589 
590             for (j = 0; j < coords.length; j++) {
591                 if (Math.abs(coords[j].usrCoords[1] - coords[i].usrCoords[1]) > Mat.eps) {
592                     tmpslopes[j] =
593                         (coords[j].usrCoords[2] - coords[i].usrCoords[2]) /
594                         (coords[j].usrCoords[1] - coords[i].usrCoords[1]);
595                 }
596             }
597 
598             slopes[i] = this.median(tmpslopes);
599             yintercepts.push(coords[i].usrCoords[2] - slopes[i] * coords[i].usrCoords[1]);
600         }
601 
602         return [this.median(yintercepts), this.median(slopes), -1];
603     },
604 
605     /**
606      * Generate values of a standard normal random variable with the Marsaglia polar method, see
607      * <a href="https://en.wikipedia.org/wiki/Marsaglia_polar_method">https://en.wikipedia.org/wiki/Marsaglia_polar_method</a>.
608      * See also D. E. Knuth, The art of computer programming, vol 2, p. 117.
609      *
610      * @param {Number} mean mean value of the normal distribution
611      * @param {Number} stdDev standard deviation of the normal distribution
612      * @returns {Number} value of a standard normal random variable
613      * @memberof JXG.Math.Statistics
614      */
615     generateGaussian: function (mean, stdDev) {
616         var u, v, s;
617 
618         if (this.hasSpare) {
619             this.hasSpare = false;
620             return this.spare * stdDev + mean;
621         }
622 
623         do {
624             u = Math.random() * 2 - 1;
625             v = Math.random() * 2 - 1;
626             s = u * u + v * v;
627         } while (s >= 1 || s === 0);
628 
629         s = Math.sqrt((-2.0 * Math.log(s)) / s);
630 
631         this.spare = v * s;
632         this.hasSpare = true;
633         return mean + stdDev * u * s;
634     },
635 
636     /**
637      * Generate value of a standard normal random variable with given mean and standard deviation.
638      * Alias for {@link JXG.Math.Statistics#generateGaussian}
639      *
640      * @param {Number} mean
641      * @param {Number} stdDev
642      * @returns Number
643      * @memberof JXG.Math.Statistics
644      * @see JXG.Math.Statistics#generateGaussian
645      * @example
646      *  let board = JXG.JSXGraph.initBoard('JXGbox',
647      *       { boundingbox: [-5, 1.5, 5, -.03], axis: true});
648      *
649      *   let runs = [
650      *       [0, 0.2, 'blue'],
651      *       [0, 1.0, 'red'],
652      *       [0, 5.0, 'orange'],
653      *       [-2,0.5, 'green'],
654      *   ]
655      *
656      *   let labelY = 1.2
657      *   runs.forEach((run,i) => {
658      *       board.create('segment',[[1.0,labelY-(i/20)],[2.0,labelY-(i/20)]],{strokeColor:run[2]})
659      *       board.create('text',[2.5,labelY-(i/20),`μ=${run[0]}, σ<sup>2</sup>=${run[1]}`])
660      *
661      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomNormal(run[0],Math.sqrt(run[1])))  // sqrt so Std Dev, not Variance
662      *       let res = JXG.Math.Statistics.histogram(x, { bins: 40, density: true, cumulative: false, range: false });
663      *       board.create('curve', [res[1], res[0]], { strokeColor: run[2], strokeWidth:2});
664      *   })
665      *
666      * </pre><div id="JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-4" class="jxgbox" style="width: 300px; height: 300px;"></div>
667      * <script type="text/javascript">
668      * {
669      *  let board = JXG.JSXGraph.initBoard('JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-4',
670      *       { boundingbox: [-5, 1.5, 5, -.03], axis: true});
671      *
672      *   let runs = [
673      *       [0, 0.2, 'blue'],
674      *       [0, 1.0, 'red'],
675      *       [0, 5.0, 'orange'],
676      *       [-2,0.5, 'green'],
677      *   ]
678      *
679      *   let labelY = 1.2
680      *   runs.forEach((run,i) => {
681      *       board.create('segment',[[1.0,labelY-(i/20)],[2.0,labelY-(i/20)]],{strokeColor:run[2]})
682      *       board.create('text',[2.5,labelY-(i/20),`μ=${run[0]}, σ<sup>2</sup>=${run[1]}`])
683      *
684      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomNormal(run[0],Math.sqrt(run[1])))  // sqrt so Std Dev, not Variance
685      *       let res = JXG.Math.Statistics.histogram(x, { bins: 40, density: true, cumulative: false, range: false });
686      *       board.create('curve', [res[1], res[0]], { strokeColor: run[2], strokeWidth:2});
687      *   })
688      * }
689      * </script><pre>
690 
691      */
692     randomNormal: function (mean, stdDev) {
693         return this.generateGaussian(mean, stdDev);
694     },
695 
696     /**
697      * Generate value of a uniform distributed random variable in the interval [a, b].
698      * @param {Number} a
699      * @param {Number} b
700      * @returns Number
701      * @memberof JXG.Math.Statistics
702      */
703     randomUniform: function (a, b) {
704         return Math.random() * (b - a) + a;
705     },
706 
707     /**
708      * Generate value of a random variable with exponential distribution, i.e.
709      * <i>f(x; lambda) = lambda * e^(-lambda x)</i> if <i>x >= 0</i> and <i>f(x; lambda) = 0</i> if <i>x < 0</i>.
710      * See <a href="https://en.wikipedia.org/wiki/Exponential_distribution">https://en.wikipedia.org/wiki/Exponential_distribution</a>.
711      * Algorithm: D.E. Knuth, TAOCP 2, p. 128.
712      *
713      * @param {Number} lambda <i>> 0</i>
714      * @returns Number
715      * @memberof JXG.Math.Statistics
716      * @example
717      *  let board = JXG.JSXGraph.initBoard('JXGbox',
718      *       { boundingbox: [-.5, 1.5, 5, -.1], axis: true});
719      *
720      *   let runs = [
721      *       [0.5, 'red'],
722      *       [1.0, 'green'],
723      *       [1.5, 'blue'],
724      *   ]
725      *
726      *   let labelY = 1
727      *   runs.forEach((run,i) => {
728      *       board.create('segment',[[1.8,labelY-(i/20)],[2.3,labelY-(i/20)]],{strokeColor:run[1]})
729      *       board.create('text',[2.5,labelY-(i/20),`λ=${run[0]}`])
730      *
731      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomExponential(run[0]))
732      *       let res = JXG.Math.Statistics.histogram(x, { bins: 40, density: true, cumulative: false, range: false });
733      *       board.create('curve', [res[1], res[0]], { strokeColor: run[1], strokeWidth:2});
734      *   })
735      *
736      * </pre><div id="JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-5" class="jxgbox" style="width: 300px; height: 300px;"></div>
737      * <script type="text/javascript">
738      * {
739      *  let board = JXG.JSXGraph.initBoard('JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-5',
740      *       { boundingbox: [-.5, 1.5, 5, -.1], axis: true});
741      *
742      *   let runs = [
743      *       [0.5, 'red'],
744      *       [1.0, 'green'],
745      *       [1.5, 'blue'],
746      *   ]
747      *
748      *   let labelY = 1
749      *   runs.forEach((run,i) => {
750      *       board.create('segment',[[1.8,labelY-(i/20)],[2.3,labelY-(i/20)]],{strokeColor:run[1]})
751      *       board.create('text',[2.5,labelY-(i/20),`λ=${run[0]}`])
752      *
753      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomExponential(run[0]))
754      *       let res = JXG.Math.Statistics.histogram(x, { bins: 40, density: true, cumulative: false, range: false });
755      *       board.create('curve', [res[1], res[0]], { strokeColor: run[1], strokeWidth:2});
756      *   })
757      * }
758      * </script><pre>
759 
760     */
761     randomExponential: function (lbda) {
762         var u;
763 
764         // Knuth, TAOCP 2, p 128
765         // See https://en.wikipedia.org/wiki/Exponential_distribution
766         if (lbda <= 0) {
767             return NaN;
768         }
769 
770         do {
771             u = Math.random();
772         } while (u === 0);
773 
774         return -Math.log(u) / lbda;
775     },
776 
777     /**
778      * Generate value of a random variable with gamma distribution of order alpha.
779      * See <a href="https://en.wikipedia.org/wiki/Gamma_distribution">https://en.wikipedia.org/wiki/Gamma_distribution</a>.
780      * Algorithm: D.E. Knuth, TAOCP 2, p. 129.
781 
782      * @param {Number} a shape, <i> > 0</i>
783      * @param {Number} [b=1] scale, <i> > 0</i>
784      * @param {Number} [t=0] threshold
785      * @returns Number
786      * @memberof JXG.Math.Statistics
787      * @example
788      *  let board = JXG.JSXGraph.initBoard('jxgbox',
789      *       { boundingbox: [-1.7, .5, 20, -.03], axis: true});
790      *
791      *   let runs = [
792      *       [0.5, 1.0, 'brown'],
793      *       [1.0, 2.0, 'red'],
794      *       [2.0, 2.0, 'orange'],
795      *       [3.0, 2.0, 'yellow'],
796      *       [5.0, 1.0, 'green'],
797      *       [9.0, 0.5, 'black'],
798      *       [7.5, 1.0, 'purple'],
799      *   ]
800      *
801      *   let labelY = .4
802      *   runs.forEach((run,i) => {
803      *       board.create('segment',[[7,labelY-(i/50)],[9,labelY-(i/50)]],{strokeColor:run[2]})
804      *       board.create('text',[10,labelY-(i/50),`k=${run[0]}, θ=${run[1]}`])
805      *
806      *       // density
807      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomGamma(run[0],run[1]))
808      *       let res = JXG.Math.Statistics.histogram(x, { bins: 50, density: true, cumulative: false, range: [0, 20] });
809      *       board.create('curve', [res[1], res[0]], { strokeColor: run[2]});
810      *
811      *   })
812      *
813      *
814      * </pre>
815      * <div id="JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-6" class="jxgbox" style="width: 300px; height: 300px;"></div>
816      * <script type="text/javascript">
817      * {
818      *  let board = JXG.JSXGraph.initBoard('JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-6',
819      *       { boundingbox: [-1.7, .5, 20, -.03], axis: true});
820      *
821      *   let runs = [
822      *       [0.5, 1.0, 'brown'],
823      *       [1.0, 2.0, 'red'],
824      *       [2.0, 2.0, 'orange'],
825      *       [3.0, 2.0, 'yellow'],
826      *       [5.0, 1.0, 'green'],
827      *       [9.0, 0.5, 'black'],
828      *       [7.5, 1.0, 'purple'],
829      *   ]
830      *
831      *   let labelY = .4
832      *   runs.forEach((run,i) => {
833      *       board.create('segment',[[7,labelY-(i/50)],[9,labelY-(i/50)]],{strokeColor:run[2]})
834      *       board.create('text',[10,labelY-(i/50),`k=${run[0]}, θ=${run[1]}`])
835      *
836      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomGamma(run[0],run[1]))
837      *       let res = JXG.Math.Statistics.histogram(x, { bins: 50, density: true, cumulative: false, range: [0, 20] });
838      *       board.create('curve', [res[1], res[0]], { strokeColor: run[2]});
839      *   })
840      * }
841      * </script><pre>
842      *
843      */
844     randomGamma: function (a, b, t) {
845         var u, v, x, y,
846             p, q;
847 
848         if (a <= 0) {
849             return NaN;
850         }
851 
852         b = b || 1;
853         t = t || 0;
854 
855         if (a === 1) {
856             return b * this.randomExponential(1) + t;
857         }
858 
859         if (a < 1) {
860             // Method by Ahrens
861             // Knuth, TAOCP 2, Ex. 16, p 551
862             p = Math.E / (a + Math.E);
863 
864             do {
865                 u = Math.random();
866                 do {
867                     v = Math.random();
868                 } while (v === 0);
869                 if (u < p) {
870                     x = Math.pow(v, 1 / a);
871                     q = Math.exp(-x);
872                 } else {
873                     x = 1 - Math.log(v);
874                     q = Math.pow(x, a - 1);
875                 }
876                 u = Math.random();
877             } while (u >= q);
878             return b * x + t;
879         }
880 
881         // a > 1
882         // Knuth, TAOCP 2, p 129
883         do {
884             y = Math.tan(Math.PI * Math.random());
885             x = Math.sqrt(2 * a - 1) * y + a - 1;
886             if (x > 0) {
887                 v = Math.random();
888             } else {
889                 continue;
890             }
891         } while (x <= 0.0 || v > (1 + y * y) * Math.exp((a - 1) * Math.log(x / (a - 1)) - Math.sqrt(2 * a - 1) * y));
892 
893         return b * x + t;
894     },
895 
896     /**
897      * Generate value of a random variable with beta distribution with shape parameters alpha and beta.
898      * See <a href="https://en.wikipedia.org/wiki/Beta_distribution">https://en.wikipedia.org/wiki/Beta_distribution</a>.
899      *
900      * @param {Number} alpha <i>> 0</i>
901      * @param {Number} beta <i>> 0</i>
902      * @returns Number
903      * @memberof JXG.Math.Statistics
904      */
905     randomBeta: function (a, b) {
906         // Knuth, TAOCP 2, p 129
907         var x1, x2, x;
908 
909         if (a <= 0 || b <= 0) {
910             return NaN;
911         }
912 
913         x1 = this.randomGamma(a);
914         x2 = this.randomGamma(b);
915         x = x1 / (x1 + x2);
916         return x;
917     },
918 
919     /**
920      * Generate value of a random variable with chi-square distribution with k degrees of freedom.
921      * See <a href="https://en.wikipedia.org/wiki/Chi-squared_distribution">https://en.wikipedia.org/wiki/Chi-squared_distribution</a>.
922      *
923      * @param {Number} k <i>> 0</i>
924      * @returns Number
925      * @memberof JXG.Math.Statistics
926      */
927     randomChisquare: function (nu) {
928         // Knuth, TAOCP 2, p 130
929 
930         if (nu <= 0) {
931             return NaN;
932         }
933 
934         return 2 * this.randomGamma(nu * 0.5);
935     },
936 
937     /**
938      * Generate value of a random variable with F-distribution with d<sub>1</sub> and d<sub>2</sub> degrees of freedom.
939      * See <a href="https://en.wikipedia.org/wiki/F-distribution">https://en.wikipedia.org/wiki/F-distribution</a>.
940      * @param {Number} d1 <i>> 0</i>
941      * @param {Number} d2 <i>> 0</i>
942      * @returns Number
943      * @memberof JXG.Math.Statistics
944      */
945     randomF: function (nu1, nu2) {
946         // Knuth, TAOCP 2, p 130
947         var y1, y2;
948 
949         if (nu1 <= 0 || nu2 <= 0) {
950             return NaN;
951         }
952 
953         y1 = this.randomChisquare(nu1);
954         y2 = this.randomChisquare(nu2);
955 
956         return (y1 * nu2) / (y2 * nu1);
957     },
958 
959     /**
960      * Generate value of a random variable with Students-t-distribution with ν degrees of freedom.
961      * See <a href="https://en.wikipedia.org/wiki/Student%27s_t-distribution">https://en.wikipedia.org/wiki/Student%27s_t-distribution</a>.
962      * @param {Number} nu <i>> 0</i>
963      * @returns Number
964      * @memberof JXG.Math.Statistics
965      */
966     randomT: function (nu) {
967         // Knuth, TAOCP 2, p 130
968         var y1, y2;
969 
970         if (nu <= 0) {
971             return NaN;
972         }
973 
974         y1 = this.randomNormal(0, 1);
975         y2 = this.randomChisquare(nu);
976 
977         return y1 / Math.sqrt(y2 / nu);
978     },
979 
980     /**
981      * Generate values for a random variable in binomial distribution with parameters <i>n</i> and <i>p</i>.
982      * See <a href="https://en.wikipedia.org/wiki/Binomial_distribution">https://en.wikipedia.org/wiki/Binomial_distribution</a>.
983      * It uses algorithm BG from <a href="https://dl.acm.org/doi/pdf/10.1145/42372.42381">https://dl.acm.org/doi/pdf/10.1145/42372.42381</a>.
984      *
985      * @param {Number} n Number of trials (n >= 0)
986      * @param {Number} p Probability (0 <= p <= 1)
987      * @returns Number Integer value of a random variable in binomial distribution
988      * @memberof JXG.Math.Statistics
989      *
990      * @example
991      * let board = JXG.JSXGraph.initBoard('jxgbox',
992      *     { boundingbox: [-1.7, .5, 30, -.03], axis: true });
993      *
994      * let runs = [
995      *     [0.5, 20, 'blue'],
996      *     [0.7, 20, 'green'],
997      *     [0.5, 40, 'red'],
998      * ];
999      *
1000      * let labelY = .4;
1001      * runs.forEach((run, i) => {
1002      *     board.create('segment', [[7, labelY - (i / 50)], [9, labelY - (i / 50)]], { strokeColor: run[2] });
1003      *     board.create('text', [10, labelY - (i / 50), `p=${run[0]}, n=${run[1]}`]);
1004      *
1005      *     let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomBinomial(run[1], run[0]));
1006      *     let res = JXG.Math.Statistics.histogram(x, {
1007      *         bins: 40,
1008      *         density: true,
1009      *         cumulative: false,
1010      *         range: [0, 40]
1011      *     });
1012      *     board.create('curve', [res[1], res[0]], { strokeColor: run[2] });
1013      * });
1014      *
1015      *
1016      * </pre><div id="JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-3" class="jxgbox" style="width: 300px; height: 300px;"></div>
1017      * <script type="text/javascript">
1018      * {
1019      *  let board = JXG.JSXGraph.initBoard('JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-3',
1020      *       { boundingbox: [-1.7, .5, 30, -.03], axis: true});
1021      *
1022      *   let runs = [
1023      *       [0.5, 20, 'blue'],
1024      *       [0.7, 20, 'green'],
1025      *       [0.5, 40, 'red'],
1026      *   ]
1027      *
1028      *   let labelY = .4
1029      *   runs.forEach((run,i) => {
1030      *       board.create('segment',[[7,labelY-(i/50)],[9,labelY-(i/50)]],{strokeColor:run[2]})
1031      *       board.create('text',[10,labelY-(i/50),`p=${run[0]}, n=${run[1]}`])
1032      *
1033      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomBinomial(run[1],run[0]))
1034      *       let res = JXG.Math.Statistics.histogram(x, { bins: 40, density: true, cumulative: false, range: [0, 40] });
1035      *       board.create('curve', [res[1], res[0]], { strokeColor: run[2]});
1036      *   })
1037      * }
1038      * </script><pre>
1039      *
1040      */
1041     randomBinomial: function (n, p) {
1042         var x, y, c,
1043             a, b, N1;
1044 
1045         if (p < 0 || p > 1 || n < 0) {
1046             return NaN;
1047         }
1048 
1049         // Edge cases
1050         if (p === 0) {
1051             return 0;
1052         }
1053         if (p === 1) {
1054             return n;
1055         }
1056 
1057         // Now, we can assume 0 < p < 1.
1058 
1059         // Fast path for common cases
1060         if (n === 0) {
1061             return 0;
1062         }
1063         if (n === 1) {
1064             return ((Math.random() < p) ? 1 : 0);
1065         }
1066 
1067         // Exploit symmetry
1068         if (p > 0.5) {
1069             return n - this.randomBinomial(n, 1 - p);
1070         }
1071 
1072         // General case: n > 1, p <= 0.5
1073         if (n < 100) {
1074             // n small:
1075             // Algorithm BG (Devroye) from:
1076             // https://dl.acm.org/doi/pdf/10.1145/42372.42381
1077             // Time O(np) so suitable for np small only.
1078             x = -1;
1079             y = 0;
1080 
1081             c = Math.log(1 - p);
1082             if (c === 0) {
1083                 return 0;
1084             }
1085 
1086             do {
1087                 x += 1;
1088                 y += Math.floor(Math.log(Math.random()) / c) + 1;
1089             } while (y < n);
1090         } else {
1091             // n large:
1092             // Knuth, TAOCP 2, p 131
1093             a = 1 + Math.floor(n * 0.5);
1094             b = n - a + 1;
1095             x = this.randomBeta(a, b);
1096             if (x >= p) {
1097                 N1 = this.randomBinomial(a - 1, p / x);
1098                 x = N1;
1099             } else {
1100                 N1 = this.randomBinomial(b - 1, (p - x) / (1 - x));
1101                 x = a + N1;
1102             }
1103         }
1104         return x;
1105     },
1106 
1107     /**
1108      * Generate values for a random variable in geometric distribution with probability <i>p</i>.
1109      * See <a href="https://en.wikipedia.org/wiki/Geometric_distribution">https://en.wikipedia.org/wiki/Geometric_distribution</a>.
1110      *
1111      * @param {Number} p (0 <= p <= 1)
1112      * @returns Number
1113      * @memberof JXG.Math.Statistics
1114      */
1115     randomGeometric: function (p) {
1116         var u;
1117 
1118         if (p < 0 || p > 1) {
1119             return NaN;
1120         }
1121         // Knuth, TAOCP 2, p 131
1122         u = Math.random();
1123 
1124         return Math.ceil(Math.log(u) / Math.log(1 - p));
1125     },
1126 
1127     /**
1128      * Generate values for a random variable in Poisson distribution with mean <i>mu</i>.
1129      * See <a href="https://en.wikipedia.org/wiki/Poisson_distribution">https://en.wikipedia.org/wiki/Poisson_distribution</a>.
1130      *
1131      * @param {Number} mu (0 < mu)
1132      * @returns Number
1133      * @memberof JXG.Math.Statistics
1134      */
1135     randomPoisson: function (mu) {
1136         var e = Math.exp(-mu),
1137             N,
1138             m = 0,
1139             u = 1,
1140             x,
1141             alpha = 7 / 8;
1142 
1143         if (mu <= 0) {
1144             return NaN;
1145         }
1146 
1147         // Knuth, TAOCP 2, p 132
1148         if (mu < 10) {
1149             do {
1150                 u *= Math.random();
1151                 m += 1;
1152             } while (u > e);
1153             N = m - 1;
1154         } else {
1155             m = Math.floor(alpha * mu);
1156             x = this.randomGamma(m);
1157             if (x < mu) {
1158                 N = m + this.randomPoisson(mu - x);
1159             } else {
1160                 N = this.randomBinomial(m - 1, mu / x);
1161             }
1162         }
1163         return N;
1164     },
1165 
1166     /**
1167      * Generate values for a random variable in Pareto distribution with
1168      * shape <i>gamma</i> and scale <i>k</i>.
1169      * See <a href="https://en.wikipedia.org/wiki/Pareto_distribution">https://en.wikipedia.org/wiki/Pareto_distribution</a>.
1170      * Method: use inverse transformation sampling.
1171      *
1172      * @param {Number} gamma shape (0 < gamma)
1173      * @param {Number} k scale (0 < k < x)
1174      * @returns Number
1175      * @memberof JXG.Math.Statistics
1176      */
1177     randomPareto: function (gamma, k) {
1178         var u = Math.random();
1179 
1180         if (gamma <= 0 || k <= 0) {
1181             return NaN;
1182         }
1183         return k * Math.pow(1 - u, -1 / gamma);
1184     },
1185 
1186     /**
1187      * Generate values for a random variable in hypergeometric distribution.
1188      * Samples are drawn from a hypergeometric distribution with specified parameters, <i>good</i> (ways to make a good selection),
1189      * <i>bad</i> (ways to make a bad selection), and <i>samples</i> (number of items sampled, which is less than or equal to <i>good + bad</i>).
1190      * <p>
1191      * Naive implementation with runtime <i>O(samples)</i>.
1192      *
1193      * @param {Number} good ways to make a good selection
1194      * @param {Number} bad ways to make a bad selection
1195      * @param {Number} samples number of items sampled
1196      * @returns
1197      * @memberof JXG.Math.Statistics
1198      */
1199     randomHypergeometric: function (good, bad, k) {
1200         var i, u,
1201             x = 0,
1202             // kk,
1203             // n = good + bad,
1204             d1 = good + bad - k,
1205             d2 = Math.min(good, bad),
1206             y = d2;
1207 
1208         if (good < 1 || bad < 1 || k > good + bad) {
1209             return NaN;
1210         }
1211 
1212         // Naive method
1213         // kk = Math.min(k, n - k);
1214         // for (i = 0; i < k; i ++) {
1215         //     u = Math.random();
1216         //     if (n * u <= good) {
1217         //         x += 1;
1218         //         if (x === good) {
1219         //             return x;
1220         //         }
1221         //         good -= 1;
1222         //     }
1223         //     n -= 1;
1224         // }
1225         // return x;
1226 
1227         // Implementation from
1228         // Monte Carlo by George S. Fishman
1229         // https://link.springer.com/book/10.1007/978-1-4757-2553-7
1230         // page 218
1231         //
1232         i = k;
1233         while (y * i > 0) {
1234             u = Math.random();
1235             y -= Math.floor(u + y / (d1 + i));
1236             i -= 1;
1237         }
1238         x = d2 - y;
1239         if (good <= bad) {
1240             return x;
1241         } else {
1242             return k - x;
1243         }
1244     },
1245 
1246     /**
1247      * Compute the histogram of a dataset.
1248      * Optional parameters can be supplied through a JavaScript object
1249      * with the following default values:
1250      * <pre>
1251      * {
1252      *   bins: 10,          // Number of bins
1253      *   range: false,      // false or array. The lower and upper range of the bins.
1254      *                      // If not provided, range is simply [min(x), max(x)].
1255      *                      // Values outside the range are ignored.
1256      *   density: false,    // If true, normalize the counts by dividing by sum(counts)
1257      *   cumulative: false
1258      * }
1259      * </pre>
1260      * The function returns an array containing two arrays. The first array is of length bins+1
1261      * containing the start values of the bins. The last entry contains the end values of the last bin.
1262      * <p>
1263      * The second array contains the counts of each bin.
1264      * @param {Array} x
1265      * @param {Object} opt Optional parameters
1266      * @returns Array [bin, counts] Array bins contains start values of bins, array counts contains
1267      * the number of entries of x which are contained in each bin.
1268      * @memberof JXG.Math.Statistics
1269      *
1270      * @example
1271      *  let board = JXG.JSXGraph.initBoard('jxgbox',
1272      *       { boundingbox: [-1.7, .5, 20, -.03], axis: true});
1273      *  let board2 = JXG.JSXGraph.initBoard('jxgbox2',
1274      *       { boundingbox: [-1.6, 1.1, 20, -.06], axis: true});
1275      *
1276      *   let runs = [
1277      *       [0.5, 1.0, 'brown'],
1278      *       [1.0, 2.0, 'red'],
1279      *       [2.0, 2.0, 'orange'],
1280      *       [3.0, 2.0, 'yellow'],
1281      *       [5.0, 1.0, 'green'],
1282      *       [9.0, 0.5, 'black'],
1283      *       [7.5, 1.0, 'purple'],
1284      *   ]
1285      *
1286      *   let labelY = .4
1287      *   runs.forEach((run,i) => {
1288      *       board.create('segment',[[7,labelY-(i/50)],[9,labelY-(i/50)]],{strokeColor:run[2]})
1289      *       board.create('text',[10,labelY-(i/50),`k=${run[0]}, θ=${run[1]}`])
1290      *
1291      *       // density
1292      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomGamma(run[0],run[1]))
1293      *       let res = JXG.Math.Statistics.histogram(x, { bins: 50, density: true, cumulative: false, range: [0, 20] });
1294      *       board.create('curve', [res[1], res[0]], { strokeColor: run[2], strokeWidth:2});
1295      *
1296      *       // cumulative density
1297      *       res = JXG.Math.Statistics.histogram(x, { bins: 50, density: true, cumulative: true, range: [0, 20] });
1298      *       res[0].unshift(0)  // add zero to front so cumulative starts at zero
1299      *       res[1].unshift(0)
1300      *       board2.create('curve', [res[1], res[0]], { strokeColor: run[2], strokeWidth:2 });
1301      *   })
1302      *
1303      *
1304      * </pre><div id="JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302" class="jxgbox" style="width: 300px; height: 300px; float:left;"></div>
1305      * <div style='float:left;'>  </div>
1306      * <div id="JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-2" class="jxgbox" style="width: 300px; height: 300px;"></div>
1307      * <script type="text/javascript">
1308      * {
1309      *  let board = JXG.JSXGraph.initBoard('JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302',
1310      *       { boundingbox: [-1.7, .5, 20, -.03], axis: true});
1311      *  let board2 = JXG.JSXGraph.initBoard('JXGda56df4d-a5a5-4c87-9ffc-9bbc1b512302-2',
1312      *       { boundingbox: [-1.6, 1.1, 20, -.06], axis: true});
1313      *
1314      *   let runs = [
1315      *       [0.5, 1.0, 'brown'],
1316      *       [1.0, 2.0, 'red'],
1317      *       [2.0, 2.0, 'orange'],
1318      *       [3.0, 2.0, 'yellow'],
1319      *       [5.0, 1.0, 'green'],
1320      *       [9.0, 0.5, 'black'],
1321      *       [7.5, 1.0, 'purple'],
1322      *   ]
1323      *
1324      *   let labelY = .4
1325      *   runs.forEach((run,i) => {
1326      *       board.create('segment',[[7,labelY-(i/50)],[9,labelY-(i/50)]],{strokeColor:run[2]})
1327      *       board.create('text',[10,labelY-(i/50),`k=${run[0]}, θ=${run[1]}`])
1328      *
1329      *       let x = Array(50000).fill(0).map(() => JXG.Math.Statistics.randomGamma(run[0],run[1]))
1330      *       let res = JXG.Math.Statistics.histogram(x, { bins: 50, density: true, cumulative: false, range: [0, 20] });
1331      *       board.create('curve', [res[1], res[0]], { strokeColor: run[2], strokeWidth:2});
1332      *
1333      *       // cumulative density
1334      *       res = JXG.Math.Statistics.histogram(x, { bins: 50, density: true, cumulative: true, range: [0, 20] });
1335      *       res[0].unshift(0)  // add zero to front so cumulative starts at zero
1336      *       res[1].unshift(0)
1337      *       board2.create('curve', [res[1], res[0]], { strokeColor: run[2], strokeWidth:2 });
1338      *   })
1339      * }
1340      * </script><pre>
1341      *
1342      */
1343     histogram: function (x, opt) {
1344         var i, le, k,
1345             mi, ma, num_bins, delta,
1346             range,
1347             s,
1348             counts = [],
1349             bins = [],
1350             no_bin = 0;        // Count of long tail elements not in histogram range
1351 
1352         // Evaluate number of bins
1353         num_bins = opt.bins || 10;
1354 
1355         // Evaluate range
1356         range = opt.range || false;
1357         if (range === false) {
1358             mi = Math.min.apply(null, x);
1359             ma = Math.max.apply(null, x);
1360         } else {
1361             mi = range[0];
1362             ma = range[1];
1363         }
1364 
1365         // Set uniform delta
1366         if (num_bins > 0) {
1367             delta = (ma - mi) / (num_bins - 1);
1368         } else {
1369             delta = 0;
1370         }
1371 
1372         // Set the bins and init the counts array
1373         for (i = 0; i < num_bins; i++) {
1374             counts.push(0);
1375             bins.push(mi + i * delta);
1376         }
1377         // bins.push(ma);
1378 
1379         // Determine the counts
1380         le = x.length;
1381         for (i = 0; i < le; i++) {
1382             k = Math.floor((x[i] - mi) / delta);
1383             if (k >= 0 && k < num_bins) {
1384                 counts[k] += 1;
1385             } else {
1386                 no_bin += 1;
1387             }
1388         }
1389 
1390         // Normalize if density===true
1391         if (opt.density) {
1392             s = JXG.Math.Statistics.sum(counts) + no_bin; // Normalize including long tail
1393             for (i = 0; i < num_bins; i++) {
1394                 counts[i] /= (s * delta);
1395                 // counts[i] /= s;
1396             }
1397         }
1398 
1399         // Cumulative counts
1400         if (opt.cumulative) {
1401             if (opt.density) {
1402                 for (i = 0; i < num_bins; i++) {
1403                     counts[i] *= delta;  // Normalize
1404                 }
1405             } for (i = 1; i < num_bins; i++) {
1406                 counts[i] += counts[i - 1];
1407             }
1408         }
1409 
1410         return [counts, bins];
1411     }
1412 };
1413 
1414 export default Mat.Statistics;
1415