// UQO Éval — lois de probabilité pour l'inférence (t, F, χ², normale) sans dépendance. // Implémentations classiques (Numerical Recipes) : fonction bêta incomplète régularisée et gamma incomplète. function lnGamma(x: number): number { const cof = [76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5]; let y = x; const tmp = x + 5.5 - (x + 0.5) * Math.log(x + 5.5); let ser = 1.000000000190015; for (let j = 0; j < 6; j++) ser += cof[j] / ++y; return -tmp + Math.log((2.5066282746310005 * ser) / x); } function betacf(a: number, b: number, x: number): number { const MAXIT = 300; const EPS = 3e-14; const FPMIN = 1e-300; const qab = a + b; const qap = a + 1; const qam = a - 1; let c = 1; let d = 1 - (qab * x) / qap; if (Math.abs(d) < FPMIN) d = FPMIN; d = 1 / d; let h = d; for (let m = 1; m <= MAXIT; m++) { const m2 = 2 * m; let aa = (m * (b - m) * x) / ((qam + m2) * (a + m2)); d = 1 + aa * d; if (Math.abs(d) < FPMIN) d = FPMIN; c = 1 + aa / c; if (Math.abs(c) < FPMIN) c = FPMIN; d = 1 / d; h *= d * c; aa = (-(a + m) * (qab + m) * x) / ((a + m2) * (qap + m2)); d = 1 + aa * d; if (Math.abs(d) < FPMIN) d = FPMIN; c = 1 + aa / c; if (Math.abs(c) < FPMIN) c = FPMIN; d = 1 / d; const del = d * c; h *= del; if (Math.abs(del - 1) < EPS) break; } return h; } /** I_x(a, b) — bêta incomplète régularisée. */ export function betaInc(a: number, b: number, x: number): number { if (x <= 0) return 0; if (x >= 1) return 1; const bt = Math.exp(lnGamma(a + b) - lnGamma(a) - lnGamma(b) + a * Math.log(x) + b * Math.log(1 - x)); if (x < (a + 1) / (a + b + 2)) return (bt * betacf(a, b, x)) / a; return 1 - (bt * betacf(b, a, 1 - x)) / b; } /** P(a, x) — gamma incomplète régularisée. */ export function gammaInc(a: number, x: number): number { if (x <= 0) return 0; if (x < a + 1) { let ap = a; let sum = 1 / a; let del = sum; for (let n = 0; n < 500; n++) { ap += 1; del *= x / ap; sum += del; if (Math.abs(del) < Math.abs(sum) * 3e-14) break; } return sum * Math.exp(-x + a * Math.log(x) - lnGamma(a)); } // fraction continue (Lentz) const FPMIN = 1e-300; let b = x + 1 - a; let c = 1 / FPMIN; let d = 1 / b; let h = d; for (let i = 1; i < 500; i++) { const an = -i * (i - a); b += 2; d = an * d + b; if (Math.abs(d) < FPMIN) d = FPMIN; c = b + an / c; if (Math.abs(c) < FPMIN) c = FPMIN; d = 1 / d; const del = d * c; h *= del; if (Math.abs(del - 1) < 3e-14) break; } return 1 - Math.exp(-x + a * Math.log(x) - lnGamma(a)) * h; } /** Φ(z). */ export function normalCdf(z: number): number { return 0.5 * (1 + erf(z / Math.SQRT2)); } function erf(x: number): number { // Abramowitz-Stegun 7.1.26 raffiné (erreur < 1.5e-7) — suffisant pour des p-valeurs const t = 1 / (1 + 0.3275911 * Math.abs(x)); const y = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * Math.exp(-x * x); return x >= 0 ? y : -y; } /** Quantile de la normale standard (Acklam). */ export function normalQuantile(p: number): number { if (p <= 0) return -Infinity; if (p >= 1) return Infinity; const a = [-3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.38357751867269e2, -3.066479806614716e1, 2.506628277459239]; const b = [-5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2, 6.680131188771972e1, -1.328068155288572e1]; const c = [-7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783]; const d = [7.784695709041462e-3, 3.224671290700398e-1, 2.445134137142996, 3.754408661907416]; const pl = 0.02425; let q: number, r: number; if (p < pl) { 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); } if (p <= 1 - pl) { q = p - 0.5; 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); } 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); } /** p-valeur bilatérale de Student. */ export function tPValue(t: number, df: number): number { if (!Number.isFinite(t)) return NaN; if (df <= 0) return NaN; const x = df / (df + t * t); return betaInc(df / 2, 0.5, x); } /** Quantile t (bissection sur la CDF — suffisant). */ export function tQuantile(p: number, df: number): number { if (df > 200) return normalQuantile(p); let lo = 0; let hi = 50; const target = 2 * (1 - p); // p-valeur bilatérale correspondante for (let i = 0; i < 80; i++) { const mid = (lo + hi) / 2; if (tPValue(mid, df) > target) lo = mid; else hi = mid; } return (lo + hi) / 2; } /** P(χ²_df > x). */ export function chi2Sf(x: number, df: number): number { if (x <= 0) return 1; return 1 - gammaInc(df / 2, x / 2); } /** P(F_{d1,d2} > f). */ export function fSf(f: number, d1: number, d2: number): number { if (f <= 0) return 1; return betaInc(d2 / 2, d1 / 2, d2 / (d2 + d1 * f)); } export const stars = (p: number): string => (p < 0.001 ? "***" : p < 0.01 ? "**" : p < 0.05 ? "*" : p < 0.1 ? "·" : "");