1 /*
  2     Copyright 2008-2023
  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";
 36 import Mat from "./math";
 37 import Type from "../utils/type";
 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 ( 0 < P ≤ 100 ) of a list of N ordered values (sorted from least to greatest)
129      * is the smallest value in the list such that no more than P percent of the data is strictly less
130      * than the value and at least P percent of the data is less than or equal to that value. See {@link https://en.wikipedia.org/wiki/Percentile}.
131      *
132      * Here, the <i>linear interpolation between closest ranks</i> method is used.
133      * @param {Array} arr The set of values, need not be ordered.
134      * @param {Number|Array} percentile One or several percentiles
135      * @returns {Number|Array} Depending if a number or an array is the input for percentile, a number or an array containing the percentils
136      * is returned.
137      */
138     percentile: function (arr, percentile) {
139         var tmp,
140             len,
141             i,
142             p,
143             res = [],
144             per;
145 
146         if (arr.length > 0) {
147             if (ArrayBuffer.isView(arr)) {
148                 tmp = new Float64Array(arr);
149                 tmp.sort();
150             } else {
151                 tmp = arr.slice(0);
152                 tmp.sort(function (a, b) {
153                     return a - b;
154                 });
155             }
156             len = tmp.length;
157 
158             if (Type.isArray(percentile)) {
159                 p = percentile;
160             } else {
161                 p = [percentile];
162             }
163 
164             for (i = 0; i < p.length; i++) {
165                 per = len * p[i] * 0.01;
166                 if (parseInt(per, 10) === per) {
167                     res.push((tmp[per - 1] + tmp[per]) * 0.5);
168                 } else {
169                     res.push(tmp[parseInt(per, 10)]);
170                 }
171             }
172 
173             if (Type.isArray(percentile)) {
174                 return res;
175             } else {
176                 return res[0];
177             }
178         }
179 
180         return 0.0;
181     },
182 
183     /**
184      * Bias-corrected sample variance. A variance is a measure of how far a
185      * set of numbers are spread out from each other.
186      * @param {Array} arr
187      * @returns {Number}
188      * @memberof JXG.Math.Statistics
189      */
190     variance: function (arr) {
191         var m,
192             res,
193             i,
194             len = arr.length;
195 
196         if (len > 1) {
197             m = this.mean(arr);
198             res = 0;
199             for (i = 0; i < len; i++) {
200                 res += (arr[i] - m) * (arr[i] - m);
201             }
202             return res / (arr.length - 1);
203         }
204 
205         return 0.0;
206     },
207 
208     /**
209      * Determines the <strong>s</strong>tandard <strong>d</strong>eviation which shows how much
210      * variation there is from the average value of a set of numbers.
211      * @param {Array} arr
212      * @returns {Number}
213      * @memberof JXG.Math.Statistics
214      */
215     sd: function (arr) {
216         return Math.sqrt(this.variance(arr));
217     },
218 
219     /**
220      * Weighted mean value is basically the same as {@link JXG.Math.Statistics.mean} but here the values
221      * are weighted, i.e. multiplied with another value called <em>weight</em>. The weight values are given
222      * as a second array with the same length as the value array..
223      * @throws {Error} If the dimensions of the arrays don't match.
224      * @param {Array} arr Set of alues.
225      * @param {Array} w Weight values.
226      * @returns {Number}
227      * @memberof JXG.Math.Statistics
228      */
229     weightedMean: function (arr, w) {
230         if (arr.length !== w.length) {
231             throw new Error(
232                 "JSXGraph error (Math.Statistics.weightedMean): Array dimension mismatch."
233             );
234         }
235 
236         if (arr.length > 0) {
237             return this.mean(this.multiply(arr, w));
238         }
239 
240         return 0.0;
241     },
242 
243     /**
244      * Extracts the maximum value from the array.
245      * @param {Array} arr
246      * @returns {Number} The highest number from the array. It returns <tt>NaN</tt> if not every element could be
247      * interpreted as a number and <tt>-Infinity</tt> if an empty array is given or no element could be interpreted
248      * as a number.
249      * @memberof JXG.Math.Statistics
250      */
251     max: function (arr) {
252         return Math.max.apply(this, arr);
253     },
254 
255     /**
256      * Extracts the minimum value from the array.
257      * @param {Array} arr
258      * @returns {Number} The lowest number from the array. It returns <tt>NaN</tt> if not every element could be
259      * interpreted as a number and <tt>Infinity</tt> if an empty array is given or no element could be interpreted
260      * as a number.
261      * @memberof JXG.Math.Statistics
262      */
263     min: function (arr) {
264         return Math.min.apply(this, arr);
265     },
266 
267     /**
268      * Determines the lowest and the highest value from the given array.
269      * @param {Array} arr
270      * @returns {Array} The minimum value as the first and the maximum value as the second value.
271      * @memberof JXG.Math.Statistics
272      */
273     range: function (arr) {
274         return [this.min(arr), this.max(arr)];
275     },
276 
277     /**
278      * Determines the absolute value of every given value.
279      * @param {Array|Number} arr
280      * @returns {Array|Number}
281      * @memberof JXG.Math.Statistics
282      */
283     abs: function (arr) {
284         var i, len, res;
285 
286         if (Type.isArray(arr)) {
287             if (arr.map) {
288                 res = arr.map(Math.abs);
289             } else {
290                 len = arr.length;
291                 res = [];
292 
293                 for (i = 0; i < len; i++) {
294                     res[i] = Math.abs(arr[i]);
295                 }
296             }
297         } else if (ArrayBuffer.isView(arr)) {
298             res = arr.map(Math.abs);
299         } else {
300             res = Math.abs(arr);
301         }
302         return res;
303     },
304 
305     /**
306      * Adds up two (sequences of) values. If one value is an array and the other one is a number the number
307      * is added to every element of the array. If two arrays are given and the lengths don't match the shortest
308      * length is taken.
309      * @param {Array|Number} arr1
310      * @param {Array|Number} arr2
311      * @returns {Array|Number}
312      * @memberof JXG.Math.Statistics
313      */
314     add: function (arr1, arr2) {
315         var i,
316             len,
317             res = [];
318 
319         arr1 = Type.evalSlider(arr1);
320         arr2 = Type.evalSlider(arr2);
321 
322         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
323             len = arr1.length;
324 
325             for (i = 0; i < len; i++) {
326                 res[i] = arr1[i] + arr2;
327             }
328         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
329             len = arr2.length;
330 
331             for (i = 0; i < len; i++) {
332                 res[i] = arr1 + arr2[i];
333             }
334         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
335             len = Math.min(arr1.length, arr2.length);
336 
337             for (i = 0; i < len; i++) {
338                 res[i] = arr1[i] + arr2[i];
339             }
340         } else {
341             res = arr1 + arr2;
342         }
343 
344         return res;
345     },
346 
347     /**
348      * Divides two (sequences of) values. If two arrays are given and the lengths don't match the shortest length
349      * is taken.
350      * @param {Array|Number} arr1 Dividend
351      * @param {Array|Number} arr2 Divisor
352      * @returns {Array|Number}
353      * @memberof JXG.Math.Statistics
354      */
355     div: function (arr1, arr2) {
356         var i,
357             len,
358             res = [];
359 
360         arr1 = Type.evalSlider(arr1);
361         arr2 = Type.evalSlider(arr2);
362 
363         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
364             len = arr1.length;
365 
366             for (i = 0; i < len; i++) {
367                 res[i] = arr1[i] / arr2;
368             }
369         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
370             len = arr2.length;
371 
372             for (i = 0; i < len; i++) {
373                 res[i] = arr1 / arr2[i];
374             }
375         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
376             len = Math.min(arr1.length, arr2.length);
377 
378             for (i = 0; i < len; i++) {
379                 res[i] = arr1[i] / arr2[i];
380             }
381         } else {
382             res = arr1 / arr2;
383         }
384 
385         return res;
386     },
387 
388     /**
389      * @function
390      * @deprecated Use {@link JXG.Math.Statistics.div} instead.
391      * @memberof JXG.Math.Statistics
392      */
393     divide: function () {
394         JXG.deprecated("Statistics.divide()", "Statistics.div()");
395         Mat.Statistics.div.apply(Mat.Statistics, arguments);
396     },
397 
398     /**
399      * Divides two (sequences of) values and returns the remainder. If two arrays are given and the lengths don't
400      * match the shortest length is taken.
401      * @param {Array|Number} arr1 Dividend
402      * @param {Array|Number} arr2 Divisor
403      * @param {Boolean} [math=false] Mathematical mod or symmetric mod? Default is symmetric, the JavaScript <tt>%</tt> operator.
404      * @returns {Array|Number}
405      * @memberof JXG.Math.Statistics
406      */
407     mod: function (arr1, arr2, math) {
408         var i,
409             len,
410             res = [],
411             mod = function (a, m) {
412                 return a % m;
413             };
414 
415         math = Type.def(math, false);
416 
417         if (math) {
418             mod = Mat.mod;
419         }
420 
421         arr1 = Type.evalSlider(arr1);
422         arr2 = Type.evalSlider(arr2);
423 
424         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
425             len = arr1.length;
426 
427             for (i = 0; i < len; i++) {
428                 res[i] = mod(arr1[i], arr2);
429             }
430         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
431             len = arr2.length;
432 
433             for (i = 0; i < len; i++) {
434                 res[i] = mod(arr1, arr2[i]);
435             }
436         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
437             len = Math.min(arr1.length, arr2.length);
438 
439             for (i = 0; i < len; i++) {
440                 res[i] = mod(arr1[i], arr2[i]);
441             }
442         } else {
443             res = mod(arr1, arr2);
444         }
445 
446         return res;
447     },
448 
449     /**
450      * Multiplies two (sequences of) values. If one value is an array and the other one is a number the number
451      * is multiplied to every element of the array. If two arrays are given and the lengths don't match the shortest
452      * length is taken.
453      * @param {Array|Number} arr1
454      * @param {Array|Number} arr2
455      * @returns {Array|Number}
456      * @memberof JXG.Math.Statistics
457      */
458     multiply: function (arr1, arr2) {
459         var i,
460             len,
461             res = [];
462 
463         arr1 = Type.evalSlider(arr1);
464         arr2 = Type.evalSlider(arr2);
465 
466         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
467             len = arr1.length;
468 
469             for (i = 0; i < len; i++) {
470                 res[i] = arr1[i] * arr2;
471             }
472         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
473             len = arr2.length;
474 
475             for (i = 0; i < len; i++) {
476                 res[i] = arr1 * arr2[i];
477             }
478         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
479             len = Math.min(arr1.length, arr2.length);
480 
481             for (i = 0; i < len; i++) {
482                 res[i] = arr1[i] * arr2[i];
483             }
484         } else {
485             res = arr1 * arr2;
486         }
487 
488         return res;
489     },
490 
491     /**
492      * Subtracts two (sequences of) values. If two arrays are given and the lengths don't match the shortest
493      * length is taken.
494      * @param {Array|Number} arr1 Minuend
495      * @param {Array|Number} arr2 Subtrahend
496      * @returns {Array|Number}
497      * @memberof JXG.Math.Statistics
498      */
499     subtract: function (arr1, arr2) {
500         var i,
501             len,
502             res = [];
503 
504         arr1 = Type.evalSlider(arr1);
505         arr2 = Type.evalSlider(arr2);
506 
507         if (Type.isArray(arr1) && Type.isNumber(arr2)) {
508             len = arr1.length;
509 
510             for (i = 0; i < len; i++) {
511                 res[i] = arr1[i] - arr2;
512             }
513         } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
514             len = arr2.length;
515 
516             for (i = 0; i < len; i++) {
517                 res[i] = arr1 - arr2[i];
518             }
519         } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
520             len = Math.min(arr1.length, arr2.length);
521 
522             for (i = 0; i < len; i++) {
523                 res[i] = arr1[i] - arr2[i];
524             }
525         } else {
526             res = arr1 - arr2;
527         }
528 
529         return res;
530     },
531 
532     /**
533      * The Theil-Sen estimator can be used to determine a more robust linear regression of a set of sample
534      * points than least squares regression in {@link JXG.Math.Numerics.regressionPolynomial}.
535      *
536      * If the function should be applied to an array a of points, a the coords array can be generated with
537      * JavaScript array.map:
538      *
539      * <pre>
540      * JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords));
541      * </pre>
542      *
543      * @param {Array} coords Array of {@link JXG.Coords}.
544      * @returns {Array} A stdform array of the regression line.
545      * @memberof JXG.Math.Statistics
546      *
547      * @example
548      * var board = JXG.JSXGraph.initBoard('jxgbox', { boundingbox: [-6,6,6,-6], axis : true });
549      * var a=[];
550      * a[0]=board.create('point', [0,0]);
551      * a[1]=board.create('point', [3,0]);
552      * a[2]=board.create('point', [0,3]);
553      *
554      * board.create('line', [
555      *     () => JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords))
556      *   ],
557      *   {strokeWidth:1, strokeColor:'black'});
558      *
559      * </pre><div id="JXG0a28be85-91c5-44d3-aae6-114e81217cf0" class="jxgbox" style="width: 300px; height: 300px;"></div>
560      * <script type="text/javascript">
561      *     (function() {
562      *         var board = JXG.JSXGraph.initBoard('JXG0a28be85-91c5-44d3-aae6-114e81217cf0',
563      *             {boundingbox: [-6,6,6,-6], axis: true, showcopyright: false, shownavigation: false});
564      *     var a=[];
565      *     a[0]=board.create('point', [0,0]);
566      *     a[1]=board.create('point', [3,0]);
567      *     a[2]=board.create('point', [0,3]);
568      *
569      *     board.create('line', [
570      *         () => JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords))
571      *       ],
572      *       {strokeWidth:1, strokeColor:'black'});
573      *
574      *     })();
575      *
576      * </script><pre>
577      *
578      */
579     TheilSenRegression: function (coords) {
580         var i,
581             j,
582             slopes = [],
583             tmpslopes = [],
584             yintercepts = [];
585 
586         for (i = 0; i < coords.length; i++) {
587             tmpslopes.length = 0;
588 
589             for (j = 0; j < coords.length; j++) {
590                 if (Math.abs(coords[j].usrCoords[1] - coords[i].usrCoords[1]) > Mat.eps) {
591                     tmpslopes[j] =
592                         (coords[j].usrCoords[2] - coords[i].usrCoords[2]) /
593                         (coords[j].usrCoords[1] - coords[i].usrCoords[1]);
594                 }
595             }
596 
597             slopes[i] = this.median(tmpslopes);
598             yintercepts.push(coords[i].usrCoords[2] - slopes[i] * coords[i].usrCoords[1]);
599         }
600 
601         return [this.median(yintercepts), this.median(slopes), -1];
602     },
603 
604     /**
605      * Generate values of a standard normal random variable with the Marsaglia polar method, see
606      * https://en.wikipedia.org/wiki/Marsaglia_polar_method .
607      *
608      * @param {Number} mean mean value of the normal distribution
609      * @param {Number} stdDev standard deviation of the normal distribution
610      * @returns {Number} value of a standard normal random variable
611      */
612     generateGaussian: function (mean, stdDev) {
613         var u, v, s;
614 
615         if (this.hasSpare) {
616             this.hasSpare = false;
617             return this.spare * stdDev + mean;
618         }
619 
620         do {
621             u = Math.random() * 2 - 1;
622             v = Math.random() * 2 - 1;
623             s = u * u + v * v;
624         } while (s >= 1 || s === 0);
625 
626         s = Math.sqrt((-2.0 * Math.log(s)) / s);
627 
628         this.spare = v * s;
629         this.hasSpare = true;
630         return mean + stdDev * u * s;
631     }
632 };
633 
634 export default Mat.Statistics;
635