time-to-botec/squiggle/node_modules/jstat/src/distribution.js
NunoSempere b6addc7f05 feat: add the node modules
Necessary in order to clearly see the squiggle hotwiring.
2022-12-03 12:44:49 +00:00

1597 lines
40 KiB
JavaScript

(function(jStat, Math) {
// generate all distribution instance methods
(function(list) {
for (var i = 0; i < list.length; i++) (function(func) {
// distribution instance method
jStat[func] = function f(a, b, c) {
if (!(this instanceof f))
return new f(a, b, c);
this._a = a;
this._b = b;
this._c = c;
return this;
};
// distribution method to be used on a jStat instance
jStat.fn[func] = function(a, b, c) {
var newthis = jStat[func](a, b, c);
newthis.data = this;
return newthis;
};
// sample instance method
jStat[func].prototype.sample = function(arr) {
var a = this._a;
var b = this._b;
var c = this._c;
if (arr)
return jStat.alter(arr, function() {
return jStat[func].sample(a, b, c);
});
else
return jStat[func].sample(a, b, c);
};
// generate the pdf, cdf and inv instance methods
(function(vals) {
for (var i = 0; i < vals.length; i++) (function(fnfunc) {
jStat[func].prototype[fnfunc] = function(x) {
var a = this._a;
var b = this._b;
var c = this._c;
if (!x && x !== 0)
x = this.data;
if (typeof x !== 'number') {
return jStat.fn.map.call(x, function(x) {
return jStat[func][fnfunc](x, a, b, c);
});
}
return jStat[func][fnfunc](x, a, b, c);
};
})(vals[i]);
})('pdf cdf inv'.split(' '));
// generate the mean, median, mode and variance instance methods
(function(vals) {
for (var i = 0; i < vals.length; i++) (function(fnfunc) {
jStat[func].prototype[fnfunc] = function() {
return jStat[func][fnfunc](this._a, this._b, this._c);
};
})(vals[i]);
})('mean median mode variance'.split(' '));
})(list[i]);
})((
'beta centralF cauchy chisquare exponential gamma invgamma kumaraswamy ' +
'laplace lognormal noncentralt normal pareto studentt weibull uniform ' +
'binomial negbin hypgeom poisson triangular tukey arcsine'
).split(' '));
// extend beta function with static methods
jStat.extend(jStat.beta, {
pdf: function pdf(x, alpha, beta) {
// PDF is zero outside the support
if (x > 1 || x < 0)
return 0;
// PDF is one for the uniform case
if (alpha == 1 && beta == 1)
return 1;
if (alpha < 512 && beta < 512) {
return (Math.pow(x, alpha - 1) * Math.pow(1 - x, beta - 1)) /
jStat.betafn(alpha, beta);
} else {
return Math.exp((alpha - 1) * Math.log(x) +
(beta - 1) * Math.log(1 - x) -
jStat.betaln(alpha, beta));
}
},
cdf: function cdf(x, alpha, beta) {
return (x > 1 || x < 0) ? (x > 1) * 1 : jStat.ibeta(x, alpha, beta);
},
inv: function inv(x, alpha, beta) {
return jStat.ibetainv(x, alpha, beta);
},
mean: function mean(alpha, beta) {
return alpha / (alpha + beta);
},
median: function median(alpha, beta) {
return jStat.ibetainv(0.5, alpha, beta);
},
mode: function mode(alpha, beta) {
return (alpha - 1 ) / ( alpha + beta - 2);
},
// return a random sample
sample: function sample(alpha, beta) {
var u = jStat.randg(alpha);
return u / (u + jStat.randg(beta));
},
variance: function variance(alpha, beta) {
return (alpha * beta) / (Math.pow(alpha + beta, 2) * (alpha + beta + 1));
}
});
// extend F function with static methods
jStat.extend(jStat.centralF, {
// This implementation of the pdf function avoids float overflow
// See the way that R calculates this value:
// https://svn.r-project.org/R/trunk/src/nmath/df.c
pdf: function pdf(x, df1, df2) {
var p, q, f;
if (x < 0)
return 0;
if (df1 <= 2) {
if (x === 0 && df1 < 2) {
return Infinity;
}
if (x === 0 && df1 === 2) {
return 1;
}
return (1 / jStat.betafn(df1 / 2, df2 / 2)) *
Math.pow(df1 / df2, df1 / 2) *
Math.pow(x, (df1/2) - 1) *
Math.pow((1 + (df1 / df2) * x), -(df1 + df2) / 2);
}
p = (df1 * x) / (df2 + x * df1);
q = df2 / (df2 + x * df1);
f = df1 * q / 2.0;
return f * jStat.binomial.pdf((df1 - 2) / 2, (df1 + df2 - 2) / 2, p);
},
cdf: function cdf(x, df1, df2) {
if (x < 0)
return 0;
return jStat.ibeta((df1 * x) / (df1 * x + df2), df1 / 2, df2 / 2);
},
inv: function inv(x, df1, df2) {
return df2 / (df1 * (1 / jStat.ibetainv(x, df1 / 2, df2 / 2) - 1));
},
mean: function mean(df1, df2) {
return (df2 > 2) ? df2 / (df2 - 2) : undefined;
},
mode: function mode(df1, df2) {
return (df1 > 2) ? (df2 * (df1 - 2)) / (df1 * (df2 + 2)) : undefined;
},
// return a random sample
sample: function sample(df1, df2) {
var x1 = jStat.randg(df1 / 2) * 2;
var x2 = jStat.randg(df2 / 2) * 2;
return (x1 / df1) / (x2 / df2);
},
variance: function variance(df1, df2) {
if (df2 <= 4)
return undefined;
return 2 * df2 * df2 * (df1 + df2 - 2) /
(df1 * (df2 - 2) * (df2 - 2) * (df2 - 4));
}
});
// extend cauchy function with static methods
jStat.extend(jStat.cauchy, {
pdf: function pdf(x, local, scale) {
if (scale < 0) { return 0; }
return (scale / (Math.pow(x - local, 2) + Math.pow(scale, 2))) / Math.PI;
},
cdf: function cdf(x, local, scale) {
return Math.atan((x - local) / scale) / Math.PI + 0.5;
},
inv: function(p, local, scale) {
return local + scale * Math.tan(Math.PI * (p - 0.5));
},
median: function median(local/*, scale*/) {
return local;
},
mode: function mode(local/*, scale*/) {
return local;
},
sample: function sample(local, scale) {
return jStat.randn() *
Math.sqrt(1 / (2 * jStat.randg(0.5))) * scale + local;
}
});
// extend chisquare function with static methods
jStat.extend(jStat.chisquare, {
pdf: function pdf(x, dof) {
if (x < 0)
return 0;
return (x === 0 && dof === 2) ? 0.5 :
Math.exp((dof / 2 - 1) * Math.log(x) - x / 2 - (dof / 2) *
Math.log(2) - jStat.gammaln(dof / 2));
},
cdf: function cdf(x, dof) {
if (x < 0)
return 0;
return jStat.lowRegGamma(dof / 2, x / 2);
},
inv: function(p, dof) {
return 2 * jStat.gammapinv(p, 0.5 * dof);
},
mean : function(dof) {
return dof;
},
// TODO: this is an approximation (is there a better way?)
median: function median(dof) {
return dof * Math.pow(1 - (2 / (9 * dof)), 3);
},
mode: function mode(dof) {
return (dof - 2 > 0) ? dof - 2 : 0;
},
sample: function sample(dof) {
return jStat.randg(dof / 2) * 2;
},
variance: function variance(dof) {
return 2 * dof;
}
});
// extend exponential function with static methods
jStat.extend(jStat.exponential, {
pdf: function pdf(x, rate) {
return x < 0 ? 0 : rate * Math.exp(-rate * x);
},
cdf: function cdf(x, rate) {
return x < 0 ? 0 : 1 - Math.exp(-rate * x);
},
inv: function(p, rate) {
return -Math.log(1 - p) / rate;
},
mean : function(rate) {
return 1 / rate;
},
median: function (rate) {
return (1 / rate) * Math.log(2);
},
mode: function mode(/*rate*/) {
return 0;
},
sample: function sample(rate) {
return -1 / rate * Math.log(jStat._random_fn());
},
variance : function(rate) {
return Math.pow(rate, -2);
}
});
// extend gamma function with static methods
jStat.extend(jStat.gamma, {
pdf: function pdf(x, shape, scale) {
if (x < 0)
return 0;
return (x === 0 && shape === 1) ? 1 / scale :
Math.exp((shape - 1) * Math.log(x) - x / scale -
jStat.gammaln(shape) - shape * Math.log(scale));
},
cdf: function cdf(x, shape, scale) {
if (x < 0)
return 0;
return jStat.lowRegGamma(shape, x / scale);
},
inv: function(p, shape, scale) {
return jStat.gammapinv(p, shape) * scale;
},
mean : function(shape, scale) {
return shape * scale;
},
mode: function mode(shape, scale) {
if(shape > 1) return (shape - 1) * scale;
return undefined;
},
sample: function sample(shape, scale) {
return jStat.randg(shape) * scale;
},
variance: function variance(shape, scale) {
return shape * scale * scale;
}
});
// extend inverse gamma function with static methods
jStat.extend(jStat.invgamma, {
pdf: function pdf(x, shape, scale) {
if (x <= 0)
return 0;
return Math.exp(-(shape + 1) * Math.log(x) - scale / x -
jStat.gammaln(shape) + shape * Math.log(scale));
},
cdf: function cdf(x, shape, scale) {
if (x <= 0)
return 0;
return 1 - jStat.lowRegGamma(shape, scale / x);
},
inv: function(p, shape, scale) {
return scale / jStat.gammapinv(1 - p, shape);
},
mean : function(shape, scale) {
return (shape > 1) ? scale / (shape - 1) : undefined;
},
mode: function mode(shape, scale) {
return scale / (shape + 1);
},
sample: function sample(shape, scale) {
return scale / jStat.randg(shape);
},
variance: function variance(shape, scale) {
if (shape <= 2)
return undefined;
return scale * scale / ((shape - 1) * (shape - 1) * (shape - 2));
}
});
// extend kumaraswamy function with static methods
jStat.extend(jStat.kumaraswamy, {
pdf: function pdf(x, alpha, beta) {
if (x === 0 && alpha === 1)
return beta;
else if (x === 1 && beta === 1)
return alpha;
return Math.exp(Math.log(alpha) + Math.log(beta) + (alpha - 1) *
Math.log(x) + (beta - 1) *
Math.log(1 - Math.pow(x, alpha)));
},
cdf: function cdf(x, alpha, beta) {
if (x < 0)
return 0;
else if (x > 1)
return 1;
return (1 - Math.pow(1 - Math.pow(x, alpha), beta));
},
inv: function inv(p, alpha, beta) {
return Math.pow(1 - Math.pow(1 - p, 1 / beta), 1 / alpha);
},
mean : function(alpha, beta) {
return (beta * jStat.gammafn(1 + 1 / alpha) *
jStat.gammafn(beta)) / (jStat.gammafn(1 + 1 / alpha + beta));
},
median: function median(alpha, beta) {
return Math.pow(1 - Math.pow(2, -1 / beta), 1 / alpha);
},
mode: function mode(alpha, beta) {
if (!(alpha >= 1 && beta >= 1 && (alpha !== 1 && beta !== 1)))
return undefined;
return Math.pow((alpha - 1) / (alpha * beta - 1), 1 / alpha);
},
variance: function variance(/*alpha, beta*/) {
throw new Error('variance not yet implemented');
// TODO: complete this
}
});
// extend lognormal function with static methods
jStat.extend(jStat.lognormal, {
pdf: function pdf(x, mu, sigma) {
if (x <= 0)
return 0;
return Math.exp(-Math.log(x) - 0.5 * Math.log(2 * Math.PI) -
Math.log(sigma) - Math.pow(Math.log(x) - mu, 2) /
(2 * sigma * sigma));
},
cdf: function cdf(x, mu, sigma) {
if (x < 0)
return 0;
return 0.5 +
(0.5 * jStat.erf((Math.log(x) - mu) / Math.sqrt(2 * sigma * sigma)));
},
inv: function(p, mu, sigma) {
return Math.exp(-1.41421356237309505 * sigma * jStat.erfcinv(2 * p) + mu);
},
mean: function mean(mu, sigma) {
return Math.exp(mu + sigma * sigma / 2);
},
median: function median(mu/*, sigma*/) {
return Math.exp(mu);
},
mode: function mode(mu, sigma) {
return Math.exp(mu - sigma * sigma);
},
sample: function sample(mu, sigma) {
return Math.exp(jStat.randn() * sigma + mu);
},
variance: function variance(mu, sigma) {
return (Math.exp(sigma * sigma) - 1) * Math.exp(2 * mu + sigma * sigma);
}
});
// extend noncentralt function with static methods
jStat.extend(jStat.noncentralt, {
pdf: function pdf(x, dof, ncp) {
var tol = 1e-14;
if (Math.abs(ncp) < tol) // ncp approx 0; use student-t
return jStat.studentt.pdf(x, dof)
if (Math.abs(x) < tol) { // different formula for x == 0
return Math.exp(jStat.gammaln((dof + 1) / 2) - ncp * ncp / 2 -
0.5 * Math.log(Math.PI * dof) - jStat.gammaln(dof / 2));
}
// formula for x != 0
return dof / x *
(jStat.noncentralt.cdf(x * Math.sqrt(1 + 2 / dof), dof+2, ncp) -
jStat.noncentralt.cdf(x, dof, ncp));
},
cdf: function cdf(x, dof, ncp) {
var tol = 1e-14;
var min_iterations = 200;
if (Math.abs(ncp) < tol) // ncp approx 0; use student-t
return jStat.studentt.cdf(x, dof);
// turn negative x into positive and flip result afterwards
var flip = false;
if (x < 0) {
flip = true;
ncp = -ncp;
}
var prob = jStat.normal.cdf(-ncp, 0, 1);
var value = tol + 1;
// use value at last two steps to determine convergence
var lastvalue = value;
var y = x * x / (x * x + dof);
var j = 0;
var p = Math.exp(-ncp * ncp / 2);
var q = Math.exp(-ncp * ncp / 2 - 0.5 * Math.log(2) -
jStat.gammaln(3 / 2)) * ncp;
while (j < min_iterations || lastvalue > tol || value > tol) {
lastvalue = value;
if (j > 0) {
p *= (ncp * ncp) / (2 * j);
q *= (ncp * ncp) / (2 * (j + 1 / 2));
}
value = p * jStat.beta.cdf(y, j + 0.5, dof / 2) +
q * jStat.beta.cdf(y, j+1, dof/2);
prob += 0.5 * value;
j++;
}
return flip ? (1 - prob) : prob;
}
});
// extend normal function with static methods
jStat.extend(jStat.normal, {
pdf: function pdf(x, mean, std) {
return Math.exp(-0.5 * Math.log(2 * Math.PI) -
Math.log(std) - Math.pow(x - mean, 2) / (2 * std * std));
},
cdf: function cdf(x, mean, std) {
return 0.5 * (1 + jStat.erf((x - mean) / Math.sqrt(2 * std * std)));
},
inv: function(p, mean, std) {
return -1.41421356237309505 * std * jStat.erfcinv(2 * p) + mean;
},
mean : function(mean/*, std*/) {
return mean;
},
median: function median(mean/*, std*/) {
return mean;
},
mode: function (mean/*, std*/) {
return mean;
},
sample: function sample(mean, std) {
return jStat.randn() * std + mean;
},
variance : function(mean, std) {
return std * std;
}
});
// extend pareto function with static methods
jStat.extend(jStat.pareto, {
pdf: function pdf(x, scale, shape) {
if (x < scale)
return 0;
return (shape * Math.pow(scale, shape)) / Math.pow(x, shape + 1);
},
cdf: function cdf(x, scale, shape) {
if (x < scale)
return 0;
return 1 - Math.pow(scale / x, shape);
},
inv: function inv(p, scale, shape) {
return scale / Math.pow(1 - p, 1 / shape);
},
mean: function mean(scale, shape) {
if (shape <= 1)
return undefined;
return (shape * Math.pow(scale, shape)) / (shape - 1);
},
median: function median(scale, shape) {
return scale * (shape * Math.SQRT2);
},
mode: function mode(scale/*, shape*/) {
return scale;
},
variance : function(scale, shape) {
if (shape <= 2)
return undefined;
return (scale*scale * shape) / (Math.pow(shape - 1, 2) * (shape - 2));
}
});
// extend studentt function with static methods
jStat.extend(jStat.studentt, {
pdf: function pdf(x, dof) {
dof = dof > 1e100 ? 1e100 : dof;
return (1/(Math.sqrt(dof) * jStat.betafn(0.5, dof/2))) *
Math.pow(1 + ((x * x) / dof), -((dof + 1) / 2));
},
cdf: function cdf(x, dof) {
var dof2 = dof / 2;
return jStat.ibeta((x + Math.sqrt(x * x + dof)) /
(2 * Math.sqrt(x * x + dof)), dof2, dof2);
},
inv: function(p, dof) {
var x = jStat.ibetainv(2 * Math.min(p, 1 - p), 0.5 * dof, 0.5);
x = Math.sqrt(dof * (1 - x) / x);
return (p > 0.5) ? x : -x;
},
mean: function mean(dof) {
return (dof > 1) ? 0 : undefined;
},
median: function median(/*dof*/) {
return 0;
},
mode: function mode(/*dof*/) {
return 0;
},
sample: function sample(dof) {
return jStat.randn() * Math.sqrt(dof / (2 * jStat.randg(dof / 2)));
},
variance: function variance(dof) {
return (dof > 2) ? dof / (dof - 2) : (dof > 1) ? Infinity : undefined;
}
});
// extend weibull function with static methods
jStat.extend(jStat.weibull, {
pdf: function pdf(x, scale, shape) {
if (x < 0 || scale < 0 || shape < 0)
return 0;
return (shape / scale) * Math.pow((x / scale), (shape - 1)) *
Math.exp(-(Math.pow((x / scale), shape)));
},
cdf: function cdf(x, scale, shape) {
return x < 0 ? 0 : 1 - Math.exp(-Math.pow((x / scale), shape));
},
inv: function(p, scale, shape) {
return scale * Math.pow(-Math.log(1 - p), 1 / shape);
},
mean : function(scale, shape) {
return scale * jStat.gammafn(1 + 1 / shape);
},
median: function median(scale, shape) {
return scale * Math.pow(Math.log(2), 1 / shape);
},
mode: function mode(scale, shape) {
if (shape <= 1)
return 0;
return scale * Math.pow((shape - 1) / shape, 1 / shape);
},
sample: function sample(scale, shape) {
return scale * Math.pow(-Math.log(jStat._random_fn()), 1 / shape);
},
variance: function variance(scale, shape) {
return scale * scale * jStat.gammafn(1 + 2 / shape) -
Math.pow(jStat.weibull.mean(scale, shape), 2);
}
});
// extend uniform function with static methods
jStat.extend(jStat.uniform, {
pdf: function pdf(x, a, b) {
return (x < a || x > b) ? 0 : 1 / (b - a);
},
cdf: function cdf(x, a, b) {
if (x < a)
return 0;
else if (x < b)
return (x - a) / (b - a);
return 1;
},
inv: function(p, a, b) {
return a + (p * (b - a));
},
mean: function mean(a, b) {
return 0.5 * (a + b);
},
median: function median(a, b) {
return jStat.mean(a, b);
},
mode: function mode(/*a, b*/) {
throw new Error('mode is not yet implemented');
},
sample: function sample(a, b) {
return (a / 2 + b / 2) + (b / 2 - a / 2) * (2 * jStat._random_fn() - 1);
},
variance: function variance(a, b) {
return Math.pow(b - a, 2) / 12;
}
});
// Got this from http://www.math.ucla.edu/~tom/distributions/binomial.html
function betinc(x, a, b, eps) {
var a0 = 0;
var b0 = 1;
var a1 = 1;
var b1 = 1;
var m9 = 0;
var a2 = 0;
var c9;
while (Math.abs((a1 - a2) / a1) > eps) {
a2 = a1;
c9 = -(a + m9) * (a + b + m9) * x / (a + 2 * m9) / (a + 2 * m9 + 1);
a0 = a1 + c9 * a0;
b0 = b1 + c9 * b0;
m9 = m9 + 1;
c9 = m9 * (b - m9) * x / (a + 2 * m9 - 1) / (a + 2 * m9);
a1 = a0 + c9 * a1;
b1 = b0 + c9 * b1;
a0 = a0 / b1;
b0 = b0 / b1;
a1 = a1 / b1;
b1 = 1;
}
return a1 / a;
}
// extend uniform function with static methods
jStat.extend(jStat.binomial, {
pdf: function pdf(k, n, p) {
return (p === 0 || p === 1) ?
((n * p) === k ? 1 : 0) :
jStat.combination(n, k) * Math.pow(p, k) * Math.pow(1 - p, n - k);
},
cdf: function cdf(x, n, p) {
var betacdf;
var eps = 1e-10;
if (x < 0)
return 0;
if (x >= n)
return 1;
if (p < 0 || p > 1 || n <= 0)
return NaN;
x = Math.floor(x);
var z = p;
var a = x + 1;
var b = n - x;
var s = a + b;
var bt = Math.exp(jStat.gammaln(s) - jStat.gammaln(b) -
jStat.gammaln(a) + a * Math.log(z) + b * Math.log(1 - z));
if (z < (a + 1) / (s + 2))
betacdf = bt * betinc(z, a, b, eps);
else
betacdf = 1 - bt * betinc(1 - z, b, a, eps);
return Math.round((1 - betacdf) * (1 / eps)) / (1 / eps);
}
});
// extend uniform function with static methods
jStat.extend(jStat.negbin, {
pdf: function pdf(k, r, p) {
if (k !== k >>> 0)
return false;
if (k < 0)
return 0;
return jStat.combination(k + r - 1, r - 1) *
Math.pow(1 - p, k) * Math.pow(p, r);
},
cdf: function cdf(x, r, p) {
var sum = 0,
k = 0;
if (x < 0) return 0;
for (; k <= x; k++) {
sum += jStat.negbin.pdf(k, r, p);
}
return sum;
}
});
// extend uniform function with static methods
jStat.extend(jStat.hypgeom, {
pdf: function pdf(k, N, m, n) {
// Hypergeometric PDF.
// A simplification of the CDF algorithm below.
// k = number of successes drawn
// N = population size
// m = number of successes in population
// n = number of items drawn from population
if(k !== k | 0) {
return false;
} else if(k < 0 || k < m - (N - n)) {
// It's impossible to have this few successes drawn.
return 0;
} else if(k > n || k > m) {
// It's impossible to have this many successes drawn.
return 0;
} else if (m * 2 > N) {
// More than half the population is successes.
if(n * 2 > N) {
// More than half the population is sampled.
return jStat.hypgeom.pdf(N - m - n + k, N, N - m, N - n)
} else {
// Half or less of the population is sampled.
return jStat.hypgeom.pdf(n - k, N, N - m, n);
}
} else if(n * 2 > N) {
// Half or less is successes.
return jStat.hypgeom.pdf(m - k, N, m, N - n);
} else if(m < n) {
// We want to have the number of things sampled to be less than the
// successes available. So swap the definitions of successful and sampled.
return jStat.hypgeom.pdf(k, N, n, m);
} else {
// If we get here, half or less of the population was sampled, half or
// less of it was successes, and we had fewer sampled things than
// successes. Now we can do this complicated iterative algorithm in an
// efficient way.
// The basic premise of the algorithm is that we partially normalize our
// intermediate product to keep it in a numerically good region, and then
// finish the normalization at the end.
// This variable holds the scaled probability of the current number of
// successes.
var scaledPDF = 1;
// This keeps track of how much we have normalized.
var samplesDone = 0;
for(var i = 0; i < k; i++) {
// For every possible number of successes up to that observed...
while(scaledPDF > 1 && samplesDone < n) {
// Intermediate result is growing too big. Apply some of the
// normalization to shrink everything.
scaledPDF *= 1 - (m / (N - samplesDone));
// Say we've normalized by this sample already.
samplesDone++;
}
// Work out the partially-normalized hypergeometric PDF for the next
// number of successes
scaledPDF *= (n - i) * (m - i) / ((i + 1) * (N - m - n + i + 1));
}
for(; samplesDone < n; samplesDone++) {
// Apply all the rest of the normalization
scaledPDF *= 1 - (m / (N - samplesDone));
}
// Bound answer sanely before returning.
return Math.min(1, Math.max(0, scaledPDF));
}
},
cdf: function cdf(x, N, m, n) {
// Hypergeometric CDF.
// This algorithm is due to Prof. Thomas S. Ferguson, <tom@math.ucla.edu>,
// and comes from his hypergeometric test calculator at
// <http://www.math.ucla.edu/~tom/distributions/Hypergeometric.html>.
// x = number of successes drawn
// N = population size
// m = number of successes in population
// n = number of items drawn from population
if(x < 0 || x < m - (N - n)) {
// It's impossible to have this few successes drawn or fewer.
return 0;
} else if(x >= n || x >= m) {
// We will always have this many successes or fewer.
return 1;
} else if (m * 2 > N) {
// More than half the population is successes.
if(n * 2 > N) {
// More than half the population is sampled.
return jStat.hypgeom.cdf(N - m - n + x, N, N - m, N - n)
} else {
// Half or less of the population is sampled.
return 1 - jStat.hypgeom.cdf(n - x - 1, N, N - m, n);
}
} else if(n * 2 > N) {
// Half or less is successes.
return 1 - jStat.hypgeom.cdf(m - x - 1, N, m, N - n);
} else if(m < n) {
// We want to have the number of things sampled to be less than the
// successes available. So swap the definitions of successful and sampled.
return jStat.hypgeom.cdf(x, N, n, m);
} else {
// If we get here, half or less of the population was sampled, half or
// less of it was successes, and we had fewer sampled things than
// successes. Now we can do this complicated iterative algorithm in an
// efficient way.
// The basic premise of the algorithm is that we partially normalize our
// intermediate sum to keep it in a numerically good region, and then
// finish the normalization at the end.
// Holds the intermediate, scaled total CDF.
var scaledCDF = 1;
// This variable holds the scaled probability of the current number of
// successes.
var scaledPDF = 1;
// This keeps track of how much we have normalized.
var samplesDone = 0;
for(var i = 0; i < x; i++) {
// For every possible number of successes up to that observed...
while(scaledCDF > 1 && samplesDone < n) {
// Intermediate result is growing too big. Apply some of the
// normalization to shrink everything.
var factor = 1 - (m / (N - samplesDone));
scaledPDF *= factor;
scaledCDF *= factor;
// Say we've normalized by this sample already.
samplesDone++;
}
// Work out the partially-normalized hypergeometric PDF for the next
// number of successes
scaledPDF *= (n - i) * (m - i) / ((i + 1) * (N - m - n + i + 1));
// Add to the CDF answer.
scaledCDF += scaledPDF;
}
for(; samplesDone < n; samplesDone++) {
// Apply all the rest of the normalization
scaledCDF *= 1 - (m / (N - samplesDone));
}
// Bound answer sanely before returning.
return Math.min(1, Math.max(0, scaledCDF));
}
}
});
// extend uniform function with static methods
jStat.extend(jStat.poisson, {
pdf: function pdf(k, l) {
if (l < 0 || (k % 1) !== 0 || k < 0) {
return 0;
}
return Math.pow(l, k) * Math.exp(-l) / jStat.factorial(k);
},
cdf: function cdf(x, l) {
var sumarr = [],
k = 0;
if (x < 0) return 0;
for (; k <= x; k++) {
sumarr.push(jStat.poisson.pdf(k, l));
}
return jStat.sum(sumarr);
},
mean : function(l) {
return l;
},
variance : function(l) {
return l;
},
sampleSmall: function sampleSmall(l) {
var p = 1, k = 0, L = Math.exp(-l);
do {
k++;
p *= jStat._random_fn();
} while (p > L);
return k - 1;
},
sampleLarge: function sampleLarge(l) {
var lam = l;
var k;
var U, V, slam, loglam, a, b, invalpha, vr, us;
slam = Math.sqrt(lam);
loglam = Math.log(lam);
b = 0.931 + 2.53 * slam;
a = -0.059 + 0.02483 * b;
invalpha = 1.1239 + 1.1328 / (b - 3.4);
vr = 0.9277 - 3.6224 / (b - 2);
while (1) {
U = Math.random() - 0.5;
V = Math.random();
us = 0.5 - Math.abs(U);
k = Math.floor((2 * a / us + b) * U + lam + 0.43);
if ((us >= 0.07) && (V <= vr)) {
return k;
}
if ((k < 0) || ((us < 0.013) && (V > us))) {
continue;
}
/* log(V) == log(0.0) ok here */
/* if U==0.0 so that us==0.0, log is ok since always returns */
if ((Math.log(V) + Math.log(invalpha) - Math.log(a / (us * us) + b)) <= (-lam + k * loglam - jStat.loggam(k + 1))) {
return k;
}
}
},
sample: function sample(l) {
if (l < 10)
return this.sampleSmall(l);
else
return this.sampleLarge(l);
}
});
// extend triangular function with static methods
jStat.extend(jStat.triangular, {
pdf: function pdf(x, a, b, c) {
if (b <= a || c < a || c > b) {
return NaN;
} else {
if (x < a || x > b) {
return 0;
} else if (x < c) {
return (2 * (x - a)) / ((b - a) * (c - a));
} else if (x === c) {
return (2 / (b - a));
} else { // x > c
return (2 * (b - x)) / ((b - a) * (b - c));
}
}
},
cdf: function cdf(x, a, b, c) {
if (b <= a || c < a || c > b)
return NaN;
if (x <= a)
return 0;
else if (x >= b)
return 1;
if (x <= c)
return Math.pow(x - a, 2) / ((b - a) * (c - a));
else // x > c
return 1 - Math.pow(b - x, 2) / ((b - a) * (b - c));
},
inv: function inv(p, a, b, c) {
if (b <= a || c < a || c > b) {
return NaN;
} else {
if (p <= ((c - a) / (b - a))) {
return a + (b - a) * Math.sqrt(p * ((c - a) / (b - a)));
} else { // p > ((c - a) / (b - a))
return a + (b - a) * (1 - Math.sqrt((1 - p) * (1 - ((c - a) / (b - a)))));
}
}
},
mean: function mean(a, b, c) {
return (a + b + c) / 3;
},
median: function median(a, b, c) {
if (c <= (a + b) / 2) {
return b - Math.sqrt((b - a) * (b - c)) / Math.sqrt(2);
} else if (c > (a + b) / 2) {
return a + Math.sqrt((b - a) * (c - a)) / Math.sqrt(2);
}
},
mode: function mode(a, b, c) {
return c;
},
sample: function sample(a, b, c) {
var u = jStat._random_fn();
if (u < ((c - a) / (b - a)))
return a + Math.sqrt(u * (b - a) * (c - a))
return b - Math.sqrt((1 - u) * (b - a) * (b - c));
},
variance: function variance(a, b, c) {
return (a * a + b * b + c * c - a * b - a * c - b * c) / 18;
}
});
// extend arcsine function with static methods
jStat.extend(jStat.arcsine, {
pdf: function pdf(x, a, b) {
if (b <= a) return NaN;
return (x <= a || x >= b) ? 0 :
(2 / Math.PI) *
Math.pow(Math.pow(b - a, 2) -
Math.pow(2 * x - a - b, 2), -0.5);
},
cdf: function cdf(x, a, b) {
if (x < a)
return 0;
else if (x < b)
return (2 / Math.PI) * Math.asin(Math.sqrt((x - a)/(b - a)));
return 1;
},
inv: function(p, a, b) {
return a + (0.5 - 0.5 * Math.cos(Math.PI * p)) * (b - a);
},
mean: function mean(a, b) {
if (b <= a) return NaN;
return (a + b) / 2;
},
median: function median(a, b) {
if (b <= a) return NaN;
return (a + b) / 2;
},
mode: function mode(/*a, b*/) {
throw new Error('mode is not yet implemented');
},
sample: function sample(a, b) {
return ((a + b) / 2) + ((b - a) / 2) *
Math.sin(2 * Math.PI * jStat.uniform.sample(0, 1));
},
variance: function variance(a, b) {
if (b <= a) return NaN;
return Math.pow(b - a, 2) / 8;
}
});
function laplaceSign(x) { return x / Math.abs(x); }
jStat.extend(jStat.laplace, {
pdf: function pdf(x, mu, b) {
return (b <= 0) ? 0 : (Math.exp(-Math.abs(x - mu) / b)) / (2 * b);
},
cdf: function cdf(x, mu, b) {
if (b <= 0) { return 0; }
if(x < mu) {
return 0.5 * Math.exp((x - mu) / b);
} else {
return 1 - 0.5 * Math.exp(- (x - mu) / b);
}
},
mean: function(mu/*, b*/) {
return mu;
},
median: function(mu/*, b*/) {
return mu;
},
mode: function(mu/*, b*/) {
return mu;
},
variance: function(mu, b) {
return 2 * b * b;
},
sample: function sample(mu, b) {
var u = jStat._random_fn() - 0.5;
return mu - (b * laplaceSign(u) * Math.log(1 - (2 * Math.abs(u))));
}
});
function tukeyWprob(w, rr, cc) {
var nleg = 12;
var ihalf = 6;
var C1 = -30;
var C2 = -50;
var C3 = 60;
var bb = 8;
var wlar = 3;
var wincr1 = 2;
var wincr2 = 3;
var xleg = [
0.981560634246719250690549090149,
0.904117256370474856678465866119,
0.769902674194304687036893833213,
0.587317954286617447296702418941,
0.367831498998180193752691536644,
0.125233408511468915472441369464
];
var aleg = [
0.047175336386511827194615961485,
0.106939325995318430960254718194,
0.160078328543346226334652529543,
0.203167426723065921749064455810,
0.233492536538354808760849898925,
0.249147045813402785000562436043
];
var qsqz = w * 0.5;
// if w >= 16 then the integral lower bound (occurs for c=20)
// is 0.99999999999995 so return a value of 1.
if (qsqz >= bb)
return 1.0;
// find (f(w/2) - 1) ^ cc
// (first term in integral of hartley's form).
var pr_w = 2 * jStat.normal.cdf(qsqz, 0, 1, 1, 0) - 1; // erf(qsqz / M_SQRT2)
// if pr_w ^ cc < 2e-22 then set pr_w = 0
if (pr_w >= Math.exp(C2 / cc))
pr_w = Math.pow(pr_w, cc);
else
pr_w = 0.0;
// if w is large then the second component of the
// integral is small, so fewer intervals are needed.
var wincr;
if (w > wlar)
wincr = wincr1;
else
wincr = wincr2;
// find the integral of second term of hartley's form
// for the integral of the range for equal-length
// intervals using legendre quadrature. limits of
// integration are from (w/2, 8). two or three
// equal-length intervals are used.
// blb and bub are lower and upper limits of integration.
var blb = qsqz;
var binc = (bb - qsqz) / wincr;
var bub = blb + binc;
var einsum = 0.0;
// integrate over each interval
var cc1 = cc - 1.0;
for (var wi = 1; wi <= wincr; wi++) {
var elsum = 0.0;
var a = 0.5 * (bub + blb);
// legendre quadrature with order = nleg
var b = 0.5 * (bub - blb);
for (var jj = 1; jj <= nleg; jj++) {
var j, xx;
if (ihalf < jj) {
j = (nleg - jj) + 1;
xx = xleg[j-1];
} else {
j = jj;
xx = -xleg[j-1];
}
var c = b * xx;
var ac = a + c;
// if exp(-qexpo/2) < 9e-14,
// then doesn't contribute to integral
var qexpo = ac * ac;
if (qexpo > C3)
break;
var pplus = 2 * jStat.normal.cdf(ac, 0, 1, 1, 0);
var pminus= 2 * jStat.normal.cdf(ac, w, 1, 1, 0);
// if rinsum ^ (cc-1) < 9e-14,
// then doesn't contribute to integral
var rinsum = (pplus * 0.5) - (pminus * 0.5);
if (rinsum >= Math.exp(C1 / cc1)) {
rinsum = (aleg[j-1] * Math.exp(-(0.5 * qexpo))) * Math.pow(rinsum, cc1);
elsum += rinsum;
}
}
elsum *= (((2.0 * b) * cc) / Math.sqrt(2 * Math.PI));
einsum += elsum;
blb = bub;
bub += binc;
}
// if pr_w ^ rr < 9e-14, then return 0
pr_w += einsum;
if (pr_w <= Math.exp(C1 / rr))
return 0;
pr_w = Math.pow(pr_w, rr);
if (pr_w >= 1) // 1 was iMax was eps
return 1;
return pr_w;
}
function tukeyQinv(p, c, v) {
var p0 = 0.322232421088;
var q0 = 0.993484626060e-01;
var p1 = -1.0;
var q1 = 0.588581570495;
var p2 = -0.342242088547;
var q2 = 0.531103462366;
var p3 = -0.204231210125;
var q3 = 0.103537752850;
var p4 = -0.453642210148e-04;
var q4 = 0.38560700634e-02;
var c1 = 0.8832;
var c2 = 0.2368;
var c3 = 1.214;
var c4 = 1.208;
var c5 = 1.4142;
var vmax = 120.0;
var ps = 0.5 - 0.5 * p;
var yi = Math.sqrt(Math.log(1.0 / (ps * ps)));
var t = yi + (((( yi * p4 + p3) * yi + p2) * yi + p1) * yi + p0)
/ (((( yi * q4 + q3) * yi + q2) * yi + q1) * yi + q0);
if (v < vmax) t += (t * t * t + t) / v / 4.0;
var q = c1 - c2 * t;
if (v < vmax) q += -c3 / v + c4 * t / v;
return t * (q * Math.log(c - 1.0) + c5);
}
jStat.extend(jStat.tukey, {
cdf: function cdf(q, nmeans, df) {
// Identical implementation as the R ptukey() function as of commit 68947
var rr = 1;
var cc = nmeans;
var nlegq = 16;
var ihalfq = 8;
var eps1 = -30.0;
var eps2 = 1.0e-14;
var dhaf = 100.0;
var dquar = 800.0;
var deigh = 5000.0;
var dlarg = 25000.0;
var ulen1 = 1.0;
var ulen2 = 0.5;
var ulen3 = 0.25;
var ulen4 = 0.125;
var xlegq = [
0.989400934991649932596154173450,
0.944575023073232576077988415535,
0.865631202387831743880467897712,
0.755404408355003033895101194847,
0.617876244402643748446671764049,
0.458016777657227386342419442984,
0.281603550779258913230460501460,
0.950125098376374401853193354250e-1
];
var alegq = [
0.271524594117540948517805724560e-1,
0.622535239386478928628438369944e-1,
0.951585116824927848099251076022e-1,
0.124628971255533872052476282192,
0.149595988816576732081501730547,
0.169156519395002538189312079030,
0.182603415044923588866763667969,
0.189450610455068496285396723208
];
if (q <= 0)
return 0;
// df must be > 1
// there must be at least two values
if (df < 2 || rr < 1 || cc < 2) return NaN;
if (!Number.isFinite(q))
return 1;
if (df > dlarg)
return tukeyWprob(q, rr, cc);
// calculate leading constant
var f2 = df * 0.5;
var f2lf = ((f2 * Math.log(df)) - (df * Math.log(2))) - jStat.gammaln(f2);
var f21 = f2 - 1.0;
// integral is divided into unit, half-unit, quarter-unit, or
// eighth-unit length intervals depending on the value of the
// degrees of freedom.
var ff4 = df * 0.25;
var ulen;
if (df <= dhaf) ulen = ulen1;
else if (df <= dquar) ulen = ulen2;
else if (df <= deigh) ulen = ulen3;
else ulen = ulen4;
f2lf += Math.log(ulen);
// integrate over each subinterval
var ans = 0.0;
for (var i = 1; i <= 50; i++) {
var otsum = 0.0;
// legendre quadrature with order = nlegq
// nodes (stored in xlegq) are symmetric around zero.
var twa1 = (2 * i - 1) * ulen;
for (var jj = 1; jj <= nlegq; jj++) {
var j, t1;
if (ihalfq < jj) {
j = jj - ihalfq - 1;
t1 = (f2lf + (f21 * Math.log(twa1 + (xlegq[j] * ulen))))
- (((xlegq[j] * ulen) + twa1) * ff4);
} else {
j = jj - 1;
t1 = (f2lf + (f21 * Math.log(twa1 - (xlegq[j] * ulen))))
+ (((xlegq[j] * ulen) - twa1) * ff4);
}
// if exp(t1) < 9e-14, then doesn't contribute to integral
var qsqz;
if (t1 >= eps1) {
if (ihalfq < jj) {
qsqz = q * Math.sqrt(((xlegq[j] * ulen) + twa1) * 0.5);
} else {
qsqz = q * Math.sqrt(((-(xlegq[j] * ulen)) + twa1) * 0.5);
}
// call wprob to find integral of range portion
var wprb = tukeyWprob(qsqz, rr, cc);
var rotsum = (wprb * alegq[j]) * Math.exp(t1);
otsum += rotsum;
}
// end legendre integral for interval i
// L200:
}
// if integral for interval i < 1e-14, then stop.
// However, in order to avoid small area under left tail,
// at least 1 / ulen intervals are calculated.
if (i * ulen >= 1.0 && otsum <= eps2)
break;
// end of interval i
// L330:
ans += otsum;
}
if (otsum > eps2) { // not converged
throw new Error('tukey.cdf failed to converge');
}
if (ans > 1)
ans = 1;
return ans;
},
inv: function(p, nmeans, df) {
// Identical implementation as the R qtukey() function as of commit 68947
var rr = 1;
var cc = nmeans;
var eps = 0.0001;
var maxiter = 50;
// df must be > 1 ; there must be at least two values
if (df < 2 || rr < 1 || cc < 2) return NaN;
if (p < 0 || p > 1) return NaN;
if (p === 0) return 0;
if (p === 1) return Infinity;
// Initial value
var x0 = tukeyQinv(p, cc, df);
// Find prob(value < x0)
var valx0 = jStat.tukey.cdf(x0, nmeans, df) - p;
// Find the second iterate and prob(value < x1).
// If the first iterate has probability value
// exceeding p then second iterate is 1 less than
// first iterate; otherwise it is 1 greater.
var x1;
if (valx0 > 0.0)
x1 = Math.max(0.0, x0 - 1.0);
else
x1 = x0 + 1.0;
var valx1 = jStat.tukey.cdf(x1, nmeans, df) - p;
// Find new iterate
var ans;
for(var iter = 1; iter < maxiter; iter++) {
ans = x1 - ((valx1 * (x1 - x0)) / (valx1 - valx0));
valx0 = valx1;
// New iterate must be >= 0
x0 = x1;
if (ans < 0.0) {
ans = 0.0;
valx1 = -p;
}
// Find prob(value < new iterate)
valx1 = jStat.tukey.cdf(ans, nmeans, df) - p;
x1 = ans;
// If the difference between two successive
// iterates is less than eps, stop
var xabs = Math.abs(x1 - x0);
if (xabs < eps)
return ans;
}
throw new Error('tukey.inv failed to converge');
}
});
}(jStat, Math));