1 /*
  2     Copyright 2008-2021
  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 <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  math/math
 39  utils/type
 40  */
 41 
 42 define(['jxg', 'math/math', 'utils/type'], function (JXG, Mat, Type) {
 43 
 44     "use strict";
 45 
 46     /**
 47      * Functions for mathematical statistics. Most functions are like in the statistics package R.
 48      * @name JXG.Math.Statistics
 49      * @exports Mat.Statistics as JXG.Math.Statistics
 50      * @namespace
 51      */
 52     Mat.Statistics = {
 53         /**
 54          * Sums up all elements of the given array.
 55          * @param {Array} arr An array of numbers.
 56          * @returns {Number}
 57          * @memberof JXG.Math.Statistics
 58          */
 59         sum: function (arr) {
 60             var i,
 61                 len = arr.length,
 62                 res = 0;
 63 
 64             for (i = 0; i < len; i++) {
 65                 res += arr[i];
 66             }
 67             return res;
 68         },
 69 
 70         /**
 71          * Multiplies all elements of the given array.
 72          * @param {Array} arr An array of numbers.
 73          * @returns {Number}
 74          * @memberof JXG.Math.Statistics
 75          */
 76         prod: function (arr) {
 77             var i,
 78                 len = arr.length,
 79                 res = 1;
 80 
 81             for (i = 0; i < len; i++) {
 82                 res *= arr[i];
 83             }
 84             return res;
 85         },
 86 
 87         /**
 88          * Determines the mean value of the values given in an array.
 89          * @param {Array} arr
 90          * @returns {Number}
 91          * @memberof JXG.Math.Statistics
 92          */
 93         mean: function (arr) {
 94             if (arr.length > 0) {
 95                 return this.sum(arr) / arr.length;
 96             }
 97 
 98             return 0.0;
 99         },
100 
101         /**
102          * The median of a finite set of values is the value that divides the set
103          * into two equal sized subsets.
104          * @param {Array} arr The set of values.
105          * @returns {Number}
106          * @memberof JXG.Math.Statistics
107          */
108         median: function (arr) {
109             var tmp, len;
110 
111             if (arr.length > 0) {
112                 if (ArrayBuffer.isView(arr)) {
113                     tmp = new Float64Array(arr);
114                     tmp.sort();
115                 } else {
116                     tmp = arr.slice(0);
117                     tmp.sort(function (a, b) {
118                         return a - b;
119                     });
120                 }
121                 len = tmp.length;
122 
123                 if (len & 1) { // odd
124                     return tmp[parseInt(len * 0.5, 10)];
125                 }
126 
127                 return (tmp[len * 0.5 - 1] + tmp[len * 0.5]) * 0.5;
128             }
129 
130             return 0.0;
131         },
132 
133         /**
134          * Bias-corrected sample variance. A variance is a measure of how far a
135          * set of numbers are spread out from each other.
136          * @param {Array} arr
137          * @returns {Number}
138          * @memberof JXG.Math.Statistics
139          */
140         variance: function (arr) {
141             var m, res, i, len = arr.length;
142 
143             if (len > 1) {
144                 m = this.mean(arr);
145                 res = 0;
146                 for (i = 0; i < len; i++) {
147                     res += (arr[i] - m) * (arr[i] - m);
148                 }
149                 return res / (arr.length - 1);
150             }
151 
152             return 0.0;
153         },
154 
155         /**
156          * Determines the <strong>s</strong>tandard <strong>d</strong>eviation which shows how much
157          * variation there is from the average value of a set of numbers.
158          * @param {Array} arr
159          * @returns {Number}
160          * @memberof JXG.Math.Statistics
161          */
162         sd: function (arr) {
163             return Math.sqrt(this.variance(arr));
164         },
165 
166         /**
167          * Weighted mean value is basically the same as {@link JXG.Math.Statistics.mean} but here the values
168          * are weighted, i.e. multiplied with another value called <em>weight</em>. The weight values are given
169          * as a second array with the same length as the value array..
170          * @throws {Error} If the dimensions of the arrays don't match.
171          * @param {Array} arr Set of alues.
172          * @param {Array} w Weight values.
173          * @returns {Number}
174          * @memberof JXG.Math.Statistics
175          */
176         weightedMean: function (arr, w) {
177             if (arr.length !== w.length) {
178                 throw new Error('JSXGraph error (Math.Statistics.weightedMean): Array dimension mismatch.');
179             }
180 
181             if (arr.length > 0) {
182                 return this.mean(this.multiply(arr, w));
183             }
184 
185             return 0.0;
186         },
187 
188         /**
189          * Extracts the maximum value from the array.
190          * @param {Array} arr
191          * @returns {Number} The highest number from the array. It returns <tt>NaN</tt> if not every element could be
192          * interpreted as a number and <tt>-Infinity</tt> if an empty array is given or no element could be interpreted
193          * as a number.
194          * @memberof JXG.Math.Statistics
195          */
196         max: function (arr) {
197             return Math.max.apply(this, arr);
198         },
199 
200         /**
201          * Extracts the minimum value from the array.
202          * @param {Array} arr
203          * @returns {Number} The lowest number from the array. It returns <tt>NaN</tt> if not every element could be
204          * interpreted as a number and <tt>Infinity</tt> if an empty array is given or no element could be interpreted
205          * as a number.
206          * @memberof JXG.Math.Statistics
207          */
208         min: function (arr) {
209             return Math.min.apply(this, arr);
210         },
211 
212         /**
213          * Determines the lowest and the highest value from the given array.
214          * @param {Array} arr
215          * @returns {Array} The minimum value as the first and the maximum value as the second value.
216          * @memberof JXG.Math.Statistics
217          */
218         range: function (arr) {
219             return [this.min(arr), this.max(arr)];
220         },
221 
222         /**
223          * Determines the absolute value of every given value.
224          * @param {Array|Number} arr
225          * @returns {Array|Number}
226          * @memberof JXG.Math.Statistics
227          */
228         abs: function (arr) {
229             var i, len, res;
230 
231             if (Type.isArray(arr)) {
232                 if (arr.map) {
233                     res = arr.map(Math.abs);
234                 } else {
235                     len = arr.length;
236                     res = [];
237     
238                     for (i = 0; i < len; i++) {
239                         res[i] = Math.abs(arr[i]);
240                     }
241                 }
242             } else if (ArrayBuffer.isView(arr)) {
243                 res = arr.map(Math.abs);
244             } else {
245                 res = Math.abs(arr);
246             }
247             return res;
248         },
249 
250         /**
251          * Adds up two (sequences of) values. If one value is an array and the other one is a number the number
252          * is added to every element of the array. If two arrays are given and the lengths don't match the shortest
253          * length is taken.
254          * @param {Array|Number} arr1
255          * @param {Array|Number} arr2
256          * @returns {Array|Number}
257          * @memberof JXG.Math.Statistics
258          */
259         add: function (arr1, arr2) {
260             var i, len, res = [];
261 
262             arr1 = Type.evalSlider(arr1);
263             arr2 = Type.evalSlider(arr2);
264 
265             if (Type.isArray(arr1) && Type.isNumber(arr2)) {
266                 len = arr1.length;
267 
268                 for (i = 0; i < len; i++) {
269                     res[i] = arr1[i] + arr2;
270                 }
271             } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
272                 len = arr2.length;
273 
274                 for (i = 0; i < len; i++) {
275                     res[i] = arr1 + arr2[i];
276                 }
277             } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
278                 len = Math.min(arr1.length, arr2.length);
279 
280                 for (i = 0; i < len; i++) {
281                     res[i] = arr1[i] + arr2[i];
282                 }
283             } else {
284                 res = arr1 + arr2;
285             }
286 
287             return res;
288         },
289 
290         /**
291          * Divides two (sequences of) values. If two arrays are given and the lengths don't match the shortest length
292          * is taken.
293          * @param {Array|Number} arr1 Dividend
294          * @param {Array|Number} arr2 Divisor
295          * @returns {Array|Number}
296          * @memberof JXG.Math.Statistics
297          */
298         div: function (arr1, arr2) {
299             var i, len, res = [];
300 
301             arr1 = Type.evalSlider(arr1);
302             arr2 = Type.evalSlider(arr2);
303 
304             if (Type.isArray(arr1) && Type.isNumber(arr2)) {
305                 len = arr1.length;
306 
307                 for (i = 0; i < len; i++) {
308                     res[i] = arr1[i] / arr2;
309                 }
310             } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
311                 len = arr2.length;
312 
313                 for (i = 0; i < len; i++) {
314                     res[i] = arr1 / arr2[i];
315                 }
316             } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
317                 len = Math.min(arr1.length, arr2.length);
318 
319                 for (i = 0; i < len; i++) {
320                     res[i] = arr1[i] / arr2[i];
321                 }
322             } else {
323                 res = arr1 / arr2;
324             }
325 
326             return res;
327         },
328 
329         /**
330          * @function
331          * @deprecated Use {@link JXG.Math.Statistics.div} instead.
332          * @memberof JXG.Math.Statistics
333          */
334         divide: function () {
335             JXG.deprecated('Statistics.divide()', 'Statistics.div()');
336             Mat.Statistics.div.apply(Mat.Statistics, arguments);
337         },
338 
339         /**
340          * Divides two (sequences of) values and returns the remainder. If two arrays are given and the lengths don't
341          * match the shortest length is taken.
342          * @param {Array|Number} arr1 Dividend
343          * @param {Array|Number} arr2 Divisor
344          * @param {Boolean} [math=false] Mathematical mod or symmetric mod? Default is symmetric, the JavaScript <tt>%</tt> operator.
345          * @returns {Array|Number}
346          * @memberof JXG.Math.Statistics
347          */
348         mod: function (arr1, arr2, math) {
349             var i, len, res = [], mod = function (a, m) {
350                 return a % m;
351             };
352 
353             math = Type.def(math, false);
354 
355             if (math) {
356                 mod = Mat.mod;
357             }
358 
359             arr1 = Type.evalSlider(arr1);
360             arr2 = Type.evalSlider(arr2);
361 
362             if (Type.isArray(arr1) && Type.isNumber(arr2)) {
363                 len = arr1.length;
364 
365                 for (i = 0; i < len; i++) {
366                     res[i] = mod(arr1[i], arr2);
367                 }
368             } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
369                 len = arr2.length;
370 
371                 for (i = 0; i < len; i++) {
372                     res[i] = mod(arr1, arr2[i]);
373                 }
374             } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
375                 len = Math.min(arr1.length, arr2.length);
376 
377                 for (i = 0; i < len; i++) {
378                     res[i] = mod(arr1[i], arr2[i]);
379                 }
380             } else {
381                 res = mod(arr1, arr2);
382             }
383 
384             return res;
385         },
386 
387         /**
388          * Multiplies two (sequences of) values. If one value is an array and the other one is a number the number
389          * is multiplied to every element of the array. If two arrays are given and the lengths don't match the shortest
390          * length is taken.
391          * @param {Array|Number} arr1
392          * @param {Array|Number} arr2
393          * @returns {Array|Number}
394          * @memberof JXG.Math.Statistics
395          */
396         multiply: function (arr1, arr2) {
397             var i, len, res = [];
398 
399             arr1 = Type.evalSlider(arr1);
400             arr2 = Type.evalSlider(arr2);
401 
402             if (Type.isArray(arr1) && Type.isNumber(arr2)) {
403                 len = arr1.length;
404 
405                 for (i = 0; i < len; i++) {
406                     res[i] = arr1[i] * arr2;
407                 }
408             } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
409                 len = arr2.length;
410 
411                 for (i = 0; i < len; i++) {
412                     res[i] = arr1 * arr2[i];
413                 }
414             } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
415                 len = Math.min(arr1.length, arr2.length);
416 
417                 for (i = 0; i < len; i++) {
418                     res[i] = arr1[i] * arr2[i];
419                 }
420             } else {
421                 res = arr1 * arr2;
422             }
423 
424             return res;
425         },
426 
427         /**
428          * Subtracts two (sequences of) values. If two arrays are given and the lengths don't match the shortest
429          * length is taken.
430          * @param {Array|Number} arr1 Minuend
431          * @param {Array|Number} arr2 Subtrahend
432          * @returns {Array|Number}
433          * @memberof JXG.Math.Statistics
434          */
435         subtract: function (arr1, arr2) {
436             var i, len, res = [];
437 
438             arr1 = Type.evalSlider(arr1);
439             arr2 = Type.evalSlider(arr2);
440 
441             if (Type.isArray(arr1) && Type.isNumber(arr2)) {
442                 len = arr1.length;
443 
444                 for (i = 0; i < len; i++) {
445                     res[i] = arr1[i] - arr2;
446                 }
447             } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
448                 len = arr2.length;
449 
450                 for (i = 0; i < len; i++) {
451                     res[i] = arr1 - arr2[i];
452                 }
453             } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
454                 len = Math.min(arr1.length, arr2.length);
455 
456                 for (i = 0; i < len; i++) {
457                     res[i] = arr1[i] - arr2[i];
458                 }
459             } else {
460                 res = arr1 - arr2;
461             }
462 
463             return res;
464         },
465 
466         /**
467          * The Theil-Sen estimator can be used to determine a more robust linear regression of a set of sample
468          * points than least squares regression in {@link JXG.Math.Numerics.regressionPolynomial}.
469          * @param {Array} coords Array of {@link JXG.Coords}.
470          * @returns {Array} The stdform of the regression line.
471          * @memberof JXG.Math.Statistics
472          */
473         TheilSenRegression: function (coords) {
474             var i, j,
475                 slopes = [],
476                 tmpslopes = [],
477                 yintercepts = [];
478 
479             for (i = 0; i < coords.length; i++) {
480                 tmpslopes.length = 0;
481 
482                 for (j = 0; j < coords.length; j++) {
483                     if (Math.abs(coords[j].usrCoords[1] - coords[i].usrCoords[1]) > Mat.eps) {
484                         tmpslopes[j] = (coords[j].usrCoords[2] - coords[i].usrCoords[2]) /
485                             (coords[j].usrCoords[1] - coords[i].usrCoords[1]);
486                     }
487                 }
488 
489                 slopes[i] = this.median(tmpslopes);
490                 yintercepts.push(coords[i].usrCoords[2] - slopes[i] * coords[i].usrCoords[1]);
491             }
492 
493             return [this.median(yintercepts), this.median(slopes), -1];
494         },
495 
496         /**
497          * Generate values of a standard normal random variable with the Marsaglia polar method, see
498          * https://en.wikipedia.org/wiki/Marsaglia_polar_method .
499          *
500          * @param {Number} mean mean value of the normal distribution
501          * @param {Number} stdDev standard deviation of the normal distribution
502          * @returns {Number} value of a standard normal random variable
503          */
504         generateGaussian: function (mean, stdDev) {
505             var u, v, s;
506 
507             if (this.hasSpare) {
508                 this.hasSpare = false;
509                 return this.spare * stdDev + mean;
510             }
511 
512             do {
513                 u = Math.random() * 2 - 1;
514                 v = Math.random() * 2 - 1;
515                 s = u * u + v * v;
516             } while (s >= 1 || s === 0);
517 
518             s = Math.sqrt(-2.0 * Math.log(s) / s);
519 
520             this.spare = v * s;
521             this.hasSpare = true;
522             return mean + stdDev * u * s;
523         }
524     };
525 
526     return Mat.Statistics;
527 });
528