Files
METH_Transcendence/site/real_game/node_modules/statsjs/lib/stats.js
Kum1ta 64b2e8ab73 Game
- Starting solo game with physics
2024-08-29 18:43:23 +02:00

954 lines
25 KiB
JavaScript

/*!
* Stats.js (https://github.com/angusgibbs/stats.js)
* Copyright 2012 Angus Gibbs
*/
(function(root) {
// Create the top level stats object
// =================================
// Wrapper to create a chainable stats object.
//
// arr - The array to work with.
//
// Returns a new chainable object.
var stats = function(arr) {
return new stats.init(arguments.length > 1 ?
Array.prototype.slice.call(arguments, 0) :
arr);
};
// Creates a new chainable array object.
//
// arr - The array to work with.
//
// Returns a new chainable object.
stats.init = function(arr) {
this.arr = arr;
this.length = arr.length;
};
// Define the methods for the stats object
stats.init.prototype = {
// Calls a function on each element in an array or JSON object.
//
// fn - The function to call on each element.
// el - The array or object element
// index - The index or key of the array element
// arr - The array or object
//
// Returns nothing.
each: function(fn) {
if (this.arr.length === undefined) {
// The wrapped array is a JSON object
for (var key in arr) {
fn.call(this.arr[key], key, this.arr[key], this.arr);
}
} else {
// The wrapper array is an array
for (var i = 0, l = this.arr.length; i < l; i++) {
fn.call(this.arr[i], this.arr[i], i, this.arr);
}
}
return this;
},
// Replaces each element in an array or JSON object with the result of
// the function that is called against each element.
//
// fn - The function to call on each element
// el - The array or object element
// index - The index or key of the array element
//
// Returns nothing.
map: function(fn) {
var arr = this.arr;
if (arr.length === undefined) {
// The wrapped array is a JSON object
for (var key in arr) {
this.arr[key] = fn.call(arr[key], arr[key], key, arr);
}
} else {
// The wrapped array is an array
for (var i = 0, l = this.arr.length; i < l; i++) {
this.arr[i] = fn.call(arr[i], arr[i], i, arr);
}
}
return this;
},
// Replaces each element of the array with the attribute of that given
// element.
//
// attr - The attribute to pluck.
//
// Returns nothing.
pluck: function(attr) {
var newArr = [];
if (this.arr.length === undefined) {
// The wrapped array is a JSON object
for (var key in arr) {
newArr.push(this.arr[key][attr]);
}
} else {
// The wrapped array is an array
for (var i = 0, l = this.arr.length; i < l; i++) {
newArr.push(this.arr[i][attr]);
}
}
return stats(newArr);
},
// Finds the smallest number.
//
// attr - Optional. If passed, the elemnt with the minimum value for the
// given attribute will be returned.
//
// Returns the minimum.
min: function(attr) {
// Get the numbers
var arr = this.arr;
// Go through each of the numbers and find the minimum
var minimum = attr == null ? arr[0] : arr[0][attr];
var minimumEl = attr == null ? arr[0] : arr[0];
stats(arr).each(function(num, index) {
if ((attr == null ? num : num[attr]) < minimum) {
minimum = attr == null ? num : num[attr];
minimumEl = num;
}
});
return minimumEl;
},
// Finds the largest number.
//
// attr - Optional. If passed, the elemnt with the maximum value for the
// given attribute will be returned.
//
// Returns the maximum.
max: function(attr) {
// Get the numbers
var arr = this.arr;
// Go through each of the numbers and find the maximum
var maximum = attr == null ? arr[0] : arr[0][attr];
var maximumEl = attr == null ? arr[0] : arr[0];
stats(arr).each(function(num, index) {
if ((attr == null ? num : num[attr]) > maximum) {
maximum = attr == null ? num : num[attr];
maximumEl = num;
}
});
return maximumEl;
},
// Finds the median of the numbers.
//
// Returns the median.
median: function() {
// Sort the numbers
var arr = this.clone().sort().toArray();
if (arr.length % 2 === 0) {
// There are an even number of elements in the array; the median
// is the average of the middle two
return (arr[arr.length / 2 - 1] + arr[arr.length / 2]) / 2;
} else {
// There are an odd number of elements in the array; the median
// is the middle one
return arr[(arr.length - 1) / 2];
}
},
// Finds the first quartile of the numbers.
//
// Returns the first quartile.
q1: function() {
// Handle the single element case
if (this.length == 1) {
return this.arr[0];
}
// Sort the numbers
var nums = this.clone().sort();
// The first quartile is the median of the lower half of the numbers
return nums.slice(0, Math.floor(nums.size() / 2)).median();
},
// Finds the third quartile of the numbers.
//
// Returns the third quartile.
q3: function() {
// Handle the single element case
if (this.length == 1) {
return this.arr[0];
}
// Sort the numbers
var nums = this.clone().sort();
// The third quartile is the median of the upper half of the numbers
return nums.slice(Math.ceil(nums.size() / 2)).median();
},
// Finds the interquartile range of the data set.
//
// Returns the IQR.
iqr: function() {
return this.q3() - this.q1();
},
// Finds all outliers in the data set, using the 1.5 * IQR away from
// the median test.
//
// Returns a new stats object with the outliers.
findOutliers: function() {
// Get the median and the range that the number must fall within
var median = this.median();
var range = this.iqr() * 1.5;
// Create a new stats object to hold the outliers
var outliers = stats([]);
// Go through each element in the data set and test to see if it
// is an outlier
this.each(function(num) {
if (Math.abs(num - median) > range) {
// The number is an outlier
outliers.push(num);
}
});
return outliers;
},
// Tests if the given number would be an outlier in the data set.
//
// num - The number to test.
//
// Returns a boolean.
testOutlier: function(num) {
return (Math.abs(num - this.median()) > this.iqr() * 1.5);
},
// Removes all the outliers from the data set.
//
// Returns nothing.
removeOutliers: function() {
// Get the median and the range that the number must fall within
var median = this.median();
var range = this.iqr() * 1.5;
// Create a new stats object that will hold all the non-outliers
var notOutliers = stats([]);
// Go through each element in the data set and test to see if it
// is an outlier
this.each(function(num) {
if (Math.abs(num - median) <= range) {
// The number is not an outlier
notOutliers.push(num);
}
});
return notOutliers;
},
// Finds the mean of the numbers.
//
// Returns the mean.
mean: function() {
return this.sum() / this.size();
},
// Finds the sum of the numbers.
//
// Returns the sum.
sum: function() {
var result = 0;
this.each(function(num) {
result += num;
});
return result;
},
// Finds the standard deviation of the numbers.
//
// Returns the standard deviation.
stdDev: function() {
// Get the mean
var mean = this.mean();
// Get a new stats object to work with
var nums = this.clone();
// Map each element of nums to the square of the element minus the
// mean
nums.map(function(num) {
return Math.pow(num - mean, 2);
});
// Return the standard deviation
return Math.sqrt(nums.sum() / (nums.size() - 1));
},
// Calculates the correlation coefficient for the data set.
//
// Returns the value of r.
r: function() {
// Get the x and y coordinates
var xCoords = this.pluck('x');
var yCoords = this.pluck('y');
// Get the means for the x and y coordinates
var meanX = xCoords.mean();
var meanY = yCoords.mean();
// Get the standard deviations for the x and y coordinates
var stdDevX = xCoords.stdDev();
var stdDevY = yCoords.stdDev();
// Map each element to the difference of the element and the mean
// divided by the standard deviation
xCoords.map(function(num) {
return (num - meanX) / stdDevX;
});
yCoords.map(function(num) {
return (num - meanY) / stdDevY;
});
// Multiply each element in the x by the corresponding value in
// the y
var nums = this.clone().map(function(num, index) {
return xCoords.get(index) * yCoords.get(index);
});
// r is the sum of xCoords over the number of points minus 1
return nums.sum() / (nums.size() - 1);
},
// Calculates the Least Squares Regression line for the data set.
//
// Returns an object with the slope and y intercept.
linReg: function() {
// Get the x and y coordinates
var xCoords = this.pluck('x');
var yCoords = this.pluck('y');
// Get the means for the x and y coordinates
var meanX = xCoords.mean();
var meanY = yCoords.mean();
// Get the standard deviations for the x and y coordinates
var stdDevX = xCoords.stdDev();
var stdDevY = yCoords.stdDev();
// Calculate the correlation coefficient
var r = this.r();
// Calculate the slope
var slope = r * (stdDevY / stdDevX);
// Calculate the y-intercept
var yIntercept = meanY - slope * meanX;
return {
slope: slope,
yIntercept: yIntercept,
r: r
};
},
// Calculates the exponential regression line for the data set.
//
// Returns an object with the coefficient, base, and correlation
// coefficient for the linearized data.
expReg: function() {
// Get y coordinates
var yCoords = this.pluck('y');
// Do a semi-log transformation of the coordinates
yCoords.map(function(num) {
return Math.log(num);
});
// Get a new stats object to work with that has the transformed data
var nums = this.clone().map(function(coord, index) {
return {
x: coord.x,
y: yCoords.get(index)
};
});
// Calculate the linear regression for the linearized data
var linReg = nums.linReg();
// Calculate the coefficient for the exponential equation
var coefficient = Math.pow(Math.E, linReg.yIntercept);
// Calculate the base for the exponential equation
var base = Math.pow(Math.E, linReg.slope);
return {
coefficient: coefficient,
base: base,
r: linReg.r
};
},
// Calculates the power regression line for the data set.
//
// Returns an object with the coefficient, base, and correlation
// coefficient for the linearized data.
powReg: function() {
// Get y coordinates
var xCoords = this.pluck('x');
var yCoords = this.pluck('y');
// Do a log-log transformation of the coordinates
xCoords.map(function(num) {
return Math.log(num);
});
yCoords.map(function(num) {
return Math.log(num);
});
// Get a new stats object to work with that has the transformed data
var nums = this.clone().map(function(coord, index) {
return {
x: xCoords.get(index),
y: yCoords.get(index)
};
});
// Calculate the linear regression for the linearized data
var linReg = nums.linReg();
// Calculate the coefficient for the power equation
var coefficient = Math.pow(Math.E, linReg.yIntercept);
// Calculate the exponent for the power equation
var exponent = linReg.slope;
return {
coefficient: coefficient,
exponent: exponent,
r: linReg.r
};
},
// Returns the number of elements.
size: function() {
return this.arr.length;
},
// Clones the current stats object, providing a new stats object which
// can be changed without modifying the original object.
//
// Returns a new stats object.
clone: function() {
return stats(this.arr.slice(0));
},
// Sorts the internal array, optionally by an attribute.
//
// attr - The attribute of the JSON object to sort by. (Optional.)
//
// Returns nothing.
sort: function(attr) {
// Create the sort function
var sortFn;
// CASE: Simple ascending sort
if (attr == null) {
sortFn = function(a, b) { return a - b; };
}
// CASE: Simple descending sort
else if (attr === true) {
sortFn = function(a, b) { return b - a; };
}
// CASE: Sort by an attribute
else if (typeof attr === 'string') {
sortFn = function(a, b) { return a[attr] - b[attr]; };
}
// CASE: Sort by a function
else {
sortFn = attr;
}
this.arr = this.arr.sort(sortFn);
return this;
},
// Gets an element from the object.
//
// i - The index to retrieve.
//
// Returns the element at that index.
get: function(i) {
return this.arr[i];
},
// Sets an element on the object.
//
// i - The index to set.
// val - The value to set the index to.
//
// Returns nothing.
set: function(i, val) {
this.arr[i] = val;
return this;
},
// Calculates the greatest common divisor of the set.
//
// Returns a Number, the gcd.
gcd: function() {
// Create a new stats object to work with
var nums = this.clone();
// Go through each element and make the element the gcd of it
// and the element to its left
for (var i = 1; i < nums.size(); i++) {
nums.set(i, gcd(nums.get(i - 1), nums.get(i)));
}
// The gcd of all the numbers is now in the final element
return nums.get(nums.size() - 1);
},
// Calculates the least common multiple of the set.
//
// Returns a Number, the lcm.
lcm: function() {
// Create a new stats object to work with
var nums = this.clone();
// Go through each element and make the element the lcm of it
// and the element to its left
for (var i = 1; i < nums.size(); i++) {
nums.set(i, lcm(nums.get(i - 1), nums.get(i)));
}
// The lcm of all the numbers if now in the final element
return nums.get(nums.size() - 1);
}
};
// Private. Calculates the gcd of two numbers using Euclid's method.
//
// Returns a Number.
function gcd(a, b) {
if (b === 0) {
return a;
}
return gcd(b, a - b * Math.floor(a / b));
}
// Private. Calculates the lcm of two numbers.
//
// Returns a Number.
function lcm(a, b) {
// The least common multiple is the absolute value of the product of
// the numbers divided by the greatest common denominator
return Math.abs(a * b) / gcd(a, b);
}
// Provide built in JavaScript array mutator methods for the data list
var mutators = ['pop', 'push', 'shift', 'splice', 'unshift'];
for (var i = 0; i < mutators.length; i++) {
stats.init.prototype[mutators[i]] = (function(method) {
return function() {
return Array.prototype[method].apply(
this.arr,
Array.prototype.slice.call(arguments, 0)
);
};
})(mutators[i]);
}
// Provide built in JavaScript array accessor methods for the data list
var accessors = ['concat', 'join', 'slice', 'reverse'];
for (var i = 0; i < accessors.length; i++) {
stats.init.prototype[accessors[i]] = (function(method) {
return function() {
this.arr = Array.prototype[method].apply(
this.arr,
Array.prototype.slice.call(arguments, 0)
);
return this;
};
})(accessors[i]);
}
// Override the built-in #toJSON() and #toArray() methods
stats.init.prototype.toJSON = stats.init.prototype.toArray = function() {
return this.arr;
};
// Creates a list from the specified lower bound to the specified upper
// bound.
//
// lower - The lower bound.
// upper - The upper bound.
// step - The amount to count by.
//
// Returns a new stats object.
stats.list = function(lower, upper, step) {
// Create the array
var arr = [];
for (var i = lower; i <= upper; i += step || 1) {
arr[i - lower] = i;
}
return stats(arr);
};
// Computes the factorial of a number.
//
// num - The number.
//
// Returns the factorial.
stats.factorial = function(num) {
// Handle the special case of 0
if (num === 0) {
return 1;
}
// Otherwise compute the factorial
for (var i = num - 1; i > 1; i--) {
num *= i;
}
return num;
};
// Computes a permutation.
//
// n - The length of the set.
// r - The number of elements in the subset.
//
// Returns the permutation.
stats.permutation = stats.nPr = function(n, r) {
return stats.factorial(n) / stats.factorial(n - r);
};
// Computes a combination.
//
// n - The length of the set.
// r - The number of elements in the subset.
//
// Returns the combination.
stats.combination = stats.nCr = function(n, r) {
return stats.factorial(n) /
(stats.factorial(r) * stats.factorial(n - r));
};
// Computes the probability of a binomial event.
//
// trials - The number of trials.
// p - The probability of success.
// x - The event number (optional).
//
// If x is not passed, an array with all the probabilities
// will be returned.
//
// Returns a number or an array.
stats.binompdf = function(trials, p, x) {
// CASE: Specific event was passed
if (x != null) {
// Return 0 if the event does not exist
if (x > trials || x < 0) {
return 0;
}
// Return the probability otherwise
return stats.nCr(trials, x) * Math.pow(p, x) *
Math.pow(1 - p, trials - x);
}
// CASE: No specific event was passed
else {
// Compute the probabilities
return stats.list(0, trials).map(function(num) {
return stats.binompdf(trials, p, num);
}).toArray();
}
};
// Computes the cumulative probability of a binomial event.
//
// trials - The number of trials.
// p - The probability of success.
// x - The upper bound (inclusive).
//
// Returns the probability.
stats.binomcdf = function(trials, p, x) {
return stats.list(0, x).map(function(num) {
return stats.binompdf(trials, p, num);
}).sum();
};
// Computes the probability of a geometric event.
//
// p - The probability of success.
// x - The event number.
//
// Returns the probability.
stats.geompdf = function(p, x) {
return Math.pow(1 - p, x - 1) * p;
};
// Computes the cumulative probability of a geometric event.
//
// p - The probability of success.
// x - The event number.
//
// Returns the probability.
stats.geomcdf = function(p, x) {
return stats.list(1, x).map(function(num) {
return stats.geompdf(p, num);
}).sum();
};
// Computes the normal probability of an event.
//
// x - The event number.
// mu - The mean.
// sigma - The standard deviation.
//
// Returns the probability.
stats.normalpdf = function(x, mu, sigma) {
return (1 / (sigma * Math.sqrt(2 * Math.PI))) *
Math.exp(-1 * (x - mu) * (x - mu) /
(2 * sigma * sigma));
};
// Computes the cumulative normal probability of an event.
//
// x - The event number.
// mu - The mean.
// sigma - The standard deviation.
//
// If four parameters are passed the first two are taken as the
// low and high.
//
// Returns the probability.
stats.normalcdf = function(x, mu, sigma) {
// If four parameters were passed, return the difference of
// the probabilities of the upper and lower
if (arguments.length === 4) {
// The cumulative probability is the difference of the
// probabilities of the upper and the lower
return stats.normalcdf(arguments[1], arguments[2], arguments[3]) -
stats.normalcdf(arguments[0], arguments[2], arguments[3]);
}
// Convert the event to a z score
var z = (x - mu) / sigma;
// The probability will be stored here
var p;
// Coefficients
var p0 = 220.2068679123761;
var p1 = 221.2135961699311;
var p2 = 112.0792914978709;
var p3 = 33.91286607838300;
var p4 = 6.373962203531650;
var p5 = .7003830644436881;
var p6 = .03526249659989109;
var q0 = 440.4137358247522;
var q1 = 793.8265125199484;
var q2 = 637.3336333788311;
var q3 = 296.5642487796737;
var q4 = 86.78073220294608;
var q5 = 16.06417757920695;
var q6 = 1.755667163182642;
var q7 = .08838834764831844;
// Another coefficient (10/sqrt(2))
var cutoff = 7.071;
// Cache the absolute value of the z score
var zabs = Math.abs(z);
// If you're more than 37 z scores away just return 1 or 0
if (z > 37) {
return 1;
}
if (z < -37) {
return 0.0;
}
// Compute the normalpdf of this event
var exp = Math.exp(-.5 * zabs * zabs);
var pdf = exp / Math.sqrt(2 * Math.PI);
// Compute the probability
if (zabs < cutoff) {
p = exp*((((((p6*zabs + p5)*zabs + p4)*zabs + p3)*zabs +
p2)*zabs + p1)*zabs + p0)/(((((((q7*zabs + q6)*zabs +
q5)*zabs + q4)*zabs + q3)*zabs + q2)*zabs + q1)*zabs +
q0);
}
else {
p = pdf/(zabs + 1.0/(zabs + 2.0/(zabs + 3.0/(zabs + 4.0/
(zabs + 0.65)))));
}
if (z < 0.0) {
return p;
} else {
return 1 - p;
}
};
// Computes the inverse normal.
//
// p - The probability of x being less than or equal to *x*, the value we
// are looking for.
//
// Returns the x value.
stats.invNorm = function(p) {
// Coefficients for the rational approximation
var a = [
-3.969683028665376e+01,
2.209460984245205e+02,
-2.759285104469687e+02,
1.383577518672690e+02,
-3.066479806614716e+01,
2.506628277459239e+00
];
var b = [
-5.447609879822406e+01,
1.615858368580409e+02,
-1.556989798598866e+02,
6.680131188771972e+01,
-1.328068155288572e+01
];
var c = [
-7.784894002430293e-03,
-3.223964580411365e-01,
-2.400758277161838e+00,
-2.549732539343734e+00,
4.374664141464968e+00,
2.938163982698783e+00
];
var d = [
7.784695709041462e-03,
3.224671290700398e-01,
2.445134137142996e+00,
3.754408661907416e+00
];
// Breakpoints
var pLow = .02425;
var pHigh = 1 - pLow;
// Rational appoximation for the lower region
if (0 < p && p < pLow) {
var q = Math.sqrt(-2 * Math.log(p));
return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) *q + 1);
}
else if (pLow <= p && p <= pHigh) {
var q = p - 0.5;
var r = q * q;
return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q /
(((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
}
else if (pHigh < p && p < 1) {
var q = Math.sqrt(-2 * Math.log(1 - p));
return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
}
};
// Runs a 1-mean z-test.
//
// muZero - The proposed population mean.
// sigma - The standard deviation of the population.
// xBar - The sample mean.
// n - The sample size.
// inequality - One of "notequal", "lessthan", or "greaterthan", depending
// on what you're testing
//
// Returns an object with the z-test statistic (z) and P-value (p).
stats.ZTest = function(muZero, sigma, xBar, n, inequality) {
// Calculate the z-test statistic
var z = (xBar - muZero) / (sigma / Math.sqrt(n));
// Calculate the P-value
var p;
if (z < 0) {
// Use normalcdf from -infinity to the z statistic
p = stats.normalcdf(-Infinity, z, 0, 1);
}
else {
// Use normalcdf from the z statistic to +infinity
p = stats.normalcdf(z, Infinity, 0, 1);
}
// Multiply the P-value by two if you're doing a not equal test
if (inequality === 'notequal') {
p *= 2;
}
return {
z: z,
p: p
};
};
// Computes a 1-mean z-interval.
//
// sigma - The population standard deviation.
// xBar - The sample mean.
// n - The sample size.
// level - The confidence level (between 0 and 1, exclusive).
//
// Returns an object with the low, high, and margin of error (moe).
stats.ZInterval = function(sigma, xBar, n, level) {
// Compute the margin of error
var moe = -stats.invNorm((1 - level) / 2) * sigma / Math.sqrt(n);
return {
low: xBar - moe,
high: xBar + moe,
moe: moe
};
};
// Export the stats object
// =================================================
if (typeof define === 'function') {
// Expose to AMD loaders
define(function() {
return stats;
});
} else if (typeof module !== 'undefined' && module.exports) {
// Expose to Node and similar environments
module.exports = stats;
} else {
// Just write to window (or whatever is the root object)
root.stats = stats;
}
}(this));