Il est terriblement décevant de ne pas voir implémenté de manière standard les distributions de Kolmogorov dans des logiciels de calcul aussi sérieux que Maple, Scilab, Mat... (et non, je viens de vérifier sur le web, c'est fait !). Il faut dire que ça paraît compliqué de prime abord, surtout si on n'a jamais eu la curiosité d'approfondir un peu le test de Kolmogorov--Smirnov, et qu'on n'a pas eu la chance de trouver l'article de George Marsaglia and Wai Wan Tsang, « Evaluating Kolmogorov's Distribution ». Avec cet article, tout paraît facile (modulo la lecture des travaux de Dudley), même le code C dont l'apreté peut refroidir le débutant. On trouvera ci-dessous des définition des commandes
kolmocorovcdf(x, n) et kolmocorovicdf(p, n)
(cdf pour cumulative distribution function et icdf pour inverse cumulative distribution function comme il se doit) pour les langages
(et TeX ?). Des copier-coller devraient suffire ou presque pour ajouter à ses macros usuelles les fonctions de répartition des lois de Kolmogorov (le paramètre n en seconde position est la taille de l'échantillon) et ainsi construire sa petite procédure de test de Kolmogorov--Smirnov « maison ». On rappelle à cet effet que la statistique du test est
ks = maxi = 1,...,n { ( F(x(i)) - (i-1)/n ) v ( i/n - F(x(i)) ) }
où F est la fonction de répartition cible (qui est supposée continue, et c'est la seule restriction de ce test), a v b = max(a, b), et l'échantillon x, de taille n, est supposé trié dans l'ordre croissant. Si
1 - kolmogorovcdf(ks, n) < α,
où α est le seuil du test qu'on a choisi, alors on rejette l'hypothèse selon laquelle l'échantillon a pu être obtenu à partir de la loi dont F est la fonction de répartition. (Et si avec tout ça, il en a encore qui rechigne à faire des tests de Kolmogorov--Smirnov...)
Remarque. --- Le code de George Marsaglia and Wai Wan Tsang utilise le calcul asymptotique uniquement si le calcul exact n'apporte rien de mieux sur les 7 premières décimales (...). Pour inverser la fonction de répartition, j'ai juste écrit une petite dichotomie, ce qui, comparé au code des auteurs, est une horreur.
// Distributions de Kolmogorov (Scilab) // d'apr\`es George Marsaglia and Wai Wan Tsang, // ``Evaluating Kolmogorov's Distribution'' // puissance de matrices avec conservation d'exposants function [V, eV] = mPower(A, eA, n); if n == 1 then V = A; eV = eA; return; else [V, eV] = mPower(A, eA, floor(n/2)); B = V*V; eB = 2*eV; if modulo(n, 2) == 0 then V = B; eV = eB; return; else V=A*B; eV = eA; return; end end endfunction function p = kolmogorovcdf(d, n) // local k m eH eQ i j k // entiers // h s H eH Q eQ; // doubles // Supprimer les prochaines lignes pour une // pr\'ecision sup\'erieure \`a 7 d\'ecimales s = d*d*n; if s > 7.24 | (s > 3.76 & n > 99) p = 1-2*exp(-(2.000071+.331/sqrt(n)+1.409/n)*s); return; end k = floor((n*d)+1); m = 2*k-1; h = k-n*d; H = ones(m, m); Q = zeros(m, m); for i = 1:m; for j = 1:m; if (i+1 < j) then H(i,j) = 0; end end end for i = 1:m; // le calcul des puissances $h^n$ pourrait \^etre am\'elior\'e // au niveau du temps. La question se pose au niveau // de la pr\'ecision... H(i,1) = H(i,1)-h^i; H(m,i) = H(m, i)-h^(m-i+1); end if 2*h-1 > 0 then H(m,1) = H(m,1)+(2*h-1)^m; end for i = 1:m; for j = 1:m; if i-j+1 > 0 then for g = 1:(i-j+1); H(i,j) = H(i,j)/g; end end end end eH = 0; [Q, eQ] = mPower(H, eH, n); p = Q(k,k); for i = 1:n; p = p*i/n; if p < 1e-140 then p = p*1e140; eQ = eQ-140; end end p = p*(10^eQ); endfunction function x = kolmogorovicdf(p, n) global accuracy; // local a b;// doubles if p > 1-accuracy then x = 1; return; elseif p < accuracy then x = 0; return; else a = 0; b = 1; while b-a >= accuracy; if kolmogorovcdf((a+b)/2, n) < p then a = (a+b)/2; else b = (a+b)/2; end end x = (a+b)/2; return; end endfunction // la fonction suivante calcule la statistique de Kolmogorov--Smirnov // lorsque la fonction de r\'epartition cible est donn\'ee par $F$. // syntaxe : KSstat(<\'echantillon>, <fonction de r\'epartition>) // exemple : // --> deff("y = F(x)", "y = x"); // --> k := KSstat(u, F); // --> p := 1-kolmogorovcdf(k, n); function k = KSstat(u, F) // local k v n i; k = 0; n = size(u,1); v = -gsort(-u);// sort() est d\'epr\'ci\'e (attention \`a l'ordre) for i = 1:n; k = max(k, F(v(i))-(i-1)/n, i/n-F(v(i))); end endfunction
# Distributions de Kolmogorov (Maple) # d'apr\`es George Marsaglia and Wai Wan Tsang, # ``Evaluating Kolmogorov's Distribution'' with(LinearAlgebra): # puissance de matrices avec conservation d'exposants accuracy := 1e-8: mPower := proc(A, eA, m, n) local B, eB;# double, int B := Matrix(m); if n = 1 then return A, eA; else (B, eB) := mPower(A, eA, m, floor(n/2)); B := B.B; eB := 2*eB; if modp(n, 2) = 0 then return B, eB; else return A.B, eA; end if; end if; end proc: kolmogorovcdf := proc(d, n) local k, m, eH, eQ, i, j, g,# int p, h, s, H, Q;# double # Supprimer les prochaines lignes pour une # pr\'ecision sup\'erieure \`a 7 d\'ecimales s := d*d*n; if (s > 7.24 or (s > 3.76 and n > 99)) then return evalf(1-2*exp(-(2.000071+.331/sqrt(n)+1.409/n)*s)); end if; k := floor((n*d)+1); m := 2*k-1; h := k-n*d; H := Matrix(m); Q := Matrix(m); for i from 1 to m do for j from 1 to m do if (i+1 < j) then H[i,j] := 0; else H[i,j] := 1; end if; end do; end do; for i from 1 to m do # le calcul des puissances $h^n$ pourrait \^etre am\'elior\'e # au niveau du temps. La question se pose au niveau # de la pr\'ecision... H[i,1] := H[i,1]-h^i; H[m,i] := H[m, i]-h^(m-i+1); end do; if 2*h-1 > 0 then H[m,1] := H[m,1]+(2*h-1)^m; end if; for i from 1 to m do for j from 1 to m do if i-j+1 > 0 then for g from 1 to (i-j+1) do H[i,j] := H[i,j]/g; end do; end if; end do; end do; eH := 0; (Q, eQ) := mPower(H, eH, m, n); p := Q[k,k]; for i from 1 to n do p := p*i/n; if p < 1e-140 then p := p*1e140; eQ := eQ-140; end if; end do; return p*(10^eQ); end proc: kolmogorovicdf := proc(p, n) global accuracy; local a, b;# double if p > 1-accuracy then return 1; elif p < accuracy then return 0; else a := 0; b := 1; while b-a >= accuracy do if kolmogorovcdf((a+b)/2, n) < p then a := (a+b)/2; else b := (a+b)/2; end if; end do; return evalf((a+b)/2); end if; end proc: # la fonction suivante calcule la statistique de Kolmogorov--Smirnov # lorsque la fonction de r\'epartition cible est donn\'ee par $F$. # syntaxe : KSstat(<\'echantillon>, <fonction de r\'epartition>) # exemple : F := x -> x; k := KSstat(u, F); p := 1-kolmogorovcdf(k, n); KSstat := proc(u, F) local k, v, n, i; k := 0; n := nops(u); v := sort(u); for i from 1 to n do k := evalf(max(k, max(F(v[i])-(i-1)/n, i/n-F(v[i])))); end do; return k; end proc:
/* test-kolmogorov.h d'apr\`es George Marsaglia and Wai Wan Tsang, ``Evaluating Kolmogorov's Distribution'' */ #include <stdio.h> #include <math.h> #include <stdlib.h> double accuracy = 1e-12; /* Les matrices sont trait\'ees comme des colonnes unidimensionnelles de flottants en double pr\'ecision. */ void mMultiply(double *A, double *B, double *C, int m){ int i, j, k; double s; for (i = 0; i < m; i++) for (j = 0; j < m; j++) {s = 0.; /* initialisation d'un flottant */ for (k = 0; k < m; k++) s += A[i*m+k]*B[k*m+j]; C[i*m+j] = s;} } void mPower(double *A, int eA, double *V, int *eV, int m, int n){ double *B; int eB, i; if (n == 1) {for (i = 0; i < m*m; i++) V[i] = A[i]; *eV = eA; return;} mPower(A, eA, V, eV, m, n/2); /* n/2 est en fait \'egal \`a floor(n/2) */ B = (double*)malloc((m*m)*sizeof(double)); mMultiply(V, V, B, m); eB = 2*(*eV); if (n % 2 == 0) {for (i = 0; i < m*m; i++) V[i] = B[i]; *eV = eB;} else {mMultiply(A, B, V, m); *eV = eA+eB;} if (V[(m/2)*m+(m/2)] > 1e140) {for (i = 0; i < m*m; i++) V[i] = V[i]*1e-140; *eV += 140;} free(B); } double kolmogorovcdf(double d, int n){ int k, m, i, j, g, eH, eQ; double h, s, *H, *Q; /* Supprimer les 3 prochaines lignes pour une pr\'ecision sup\'erieure \à 7 d\'ecimales */ s = d*d*n; if (s > 7.24 || (s > 3.76 && n > 99)) return 1-2*exp(-(2.000071+.331/sqrt(n)+1.409/n)*s); k = (int)(n*d)+1; m = 2*k-1; h = k-n*d; H = (double*)malloc((m*m)*sizeof(double)); Q = (double*)malloc((m*m)*sizeof(double)); for (i = 0; i < m; i++) for (j = 0; j < m; j++) if (i-j+1 < 0) H[i*m+j] = 0; else H[i*m+j] = 1; for (i = 0; i < m; i++) {H[i*m] -= pow(h, i+1); H[(m-1)*m+i] -= pow(h, (m-i));} H[(m-1)*m] += (2*h-1 > 0 ? pow(2*h-1, m) : 0); for (i = 0; i < m; i++) for (j = 0; j < m; j++) if (i-j+1 > 0) for (g = 1; g <= i-j+1; g++) H[i*m+j] /= g; eH = 0; mPower(H, eH, Q, &eQ, m, n); s = Q[(k-1)*m+k-1]; for (i = 1; i <= n; i++) {s = s*i/n; if (s < 1e-140) {s *= 1e140; eQ -= 140;}} s *= pow(10., eQ); /* attention au flottant 10.*/ free(H); free(Q); return s; } double kolmogorovicdf(double alpha, int n){ double a, b; if (alpha > 1-accuracy) return 1; else if (alpha < accuracy) return 0; else { a = 0; b = 1; while(b-a >= accuracy) {if (kolmogorovcdf((a+b)/2, n) < alpha) a = (a+b)/2; else b = (a+b)/2; } return (a+b)/2; } } /* Trial int main(){ int n = 10; double d = .274; printf("kolmogorovcdf(%1.3f, %d) = %f\n", d, n, kolmogorovcdf(d, n)); return 0; } */
% test-kolmogorov.mp % d'apr\`es George Marsaglia and Wai Wan Tsang, % ``Evaluating Kolmogorov's Distribution'' newinternal accuracy; accuracy := 0.00001; % Les matrices sont trait\'ees comme des colonnes % unidimensionnelles de ``pair'' (flottants). fbasis := 64; vardef reducefloat expr z = if xpart z = 0: (0,0) else: save x, y; x = xpart z; y = ypart z; % if floor y <> y: x := x*(fbasis**(y-floor y)); y := floor y; fi forever: if abs x >= fbasis: x := x/fbasis; y := y+1; fi if abs x < 1: x := x*fbasis; y := y-1; fi exitif (abs x >= 1) and (abs x < fbasis); endfor (x, y) fi enddef; tertiarydef x floatPlus y = if ypart x-10 > ypart y: x elseif ypart y-10 > ypart x: y else: begingroup save m; m = max(ypart x, ypart y); reducefloat(xpart x*(fbasis**(ypart x-m)) +xpart y*(fbasis**(ypart y-m)), m) endgroup fi enddef; tertiarydef x floatMinus y = if ypart x-10 > ypart y: x elseif ypart y-10 > ypart x: (-xpart y, ypart y) else: begingroup save m; m = max(ypart x, ypart y); reducefloat(xpart x*(fbasis**(ypart x-m)) -xpart y*(fbasis**(ypart y-m)), m) endgroup fi enddef; secondarydef x floatTimes y = reducefloat(xpart x*xpart y, ypart x+ypart y) enddef; vardef floatPower(expr x, n) = (1, 0) for i = 1 upto n: floatTimes x endfor enddef; vardef exp primary x = mexp(256x) enddef; def mMultiply(suffix A, B, C)(expr m)= begingroup save s; pair s; for i = 0 upto m-1: for j = 0 upto m-1: s := (0, 0); for k = 0 upto m-1: s := s floatPlus (A[i*m+k] floatTimes B[k*m+j]); endfor C[i*m+j] := s; endfor endfor endgroup enddef; def mPower(suffix A, V)(expr m, n) = if n = 1: for i = 0 upto m*m-1: V[i] := A[i]; endfor else: begingroup save B, eB; mPower(A, V, m, floor(n/2)); pair B[]; mMultiply(V, V, B, m); if n mod 2 = 0: for i = 0 upto m*m-1: V[i] := B[i]; endfor else: mMultiply(A, B, V, m); fi endgroup fi enddef; vardef kolmogorovcdf(expr d, n) = save k, m, eH, eQ, h, s, H, Q; % Omit 3 next lines if you require > 7 digits accuracy on the right tail s = d*d*n; if (s > 7.24) or ((s > 3.76) and (n > 99)): message "asymptotic kolmogorovcdf"; 1-2*exp(-(2.000071+.331/sqrt(n)+1.409/n)*s) else: k = floor(n*d)+1; m = 2*k-1; h = k-n*d; pair H[], Q[]; for i = 0 upto m-1: for j = 0 upto m-1: if i-j+1 < 0: H[i*m+j] = (0, 0); else: H[i*m+j] = (1, 0); fi endfor endfor pair hi, hmi; hi := reducefloat(h, 0); hmi := floatPower((h,0), m); for i = 0 upto m-1: H[i*m] := H[i*m] floatMinus hi; H[(m-1)*m+i] := H[(m-1)*m+i] floatMinus hmi; hi := hi floatTimes (h,0); hmi := hmi floatTimes (1/h,0); endfor if 2*h-1 > 0: H[(m-1)*m] := H[(m-1)*m] floatPlus ((2*h-1)**m,0); fi for i = 0 upto m-1: for j = 0 upto m-1: if i-j+1 > 0: for g = 1 upto i-j+1: H[i*m+j] := reducefloat(xpart H[i*m+j]/g, ypart H[i*m+j]); endfor fi endfor endfor mPower(H, Q, m, n); pair s; s := Q[(k-1)*m+k-1]; for i = 1 upto n: s := s floatTimes (i/n, 0); endfor xpart s % if ypart s > 0: for i = 1 upto ypart s: * fbasis endfor % elseif ypart s < 0: for i = 1 upto -ypart s: /fbasis endfor % fi fi enddef; vardef kolmogorovicdf(p, n) = save a, b; if p > 1-accuracy: 1 elseif p < accuracy: 0 else a = 0; b = 1; forever: exitif b-a < accuracy; if kolmogorovcdf((a+b)/2, n) < p: a = (a+b)/2; else: b = (a+b)/2; fi endfor (a+b)/2 fi enddef; %% %% trial %% %n := 10; d:=.274; %%for d = 0 step 0.01 until 1: %message "kolmogorovcdf(" & decimal d &", " % & decimal n & ") = " & decimal(kolmogorovcdf(d, n)); %%endfor %end.