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