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