Aller au contenu

05 · Critique des résultats

Intuition

C'est le troisième temps exigé par le programme : « analyser et critiquer la qualité (pertinence et performance) des résultats obtenus ».

Trois questions, dans cet ordre :

  1. Le résultat est-il numériquement ce que je crois ? (précision)
  2. Le calcul passe-t-il à l'échelle ? (performance)
  3. Le modèle décrit-il la réalité ? (pertinence)

La troisième est la plus importante et la moins souvent posée.

1. Précision numérique

1.1 L'arithmétique flottante n'est pas l'arithmétique

Les trois faits à connaître

1. L'addition n'est pas associative.

>>> (0.1 + 0.2) + 0.3 == 0.1 + (0.2 + 0.3)
False

2. L'égalité n'a pas de sens.

>>> 0.1 + 0.2 == 0.3
False
>>> 0.1 + 0.2
0.30000000000000004
On teste \(|a-b| < \varepsilon\), jamais \(a = b\).

3. La précision est relative, pas absolue. Un double a environ 16 chiffres significatifs. Sur \(10^{20}\), l'écart entre deux flottants consécutifs vaut \(\approx16\,000\).

1.2 Les trois sources d'erreur

Source Cause Remède
Troncature On arrête une série, on discrétise Réduire le pas — jusqu'à un point
Arrondi Chaque opération arrondit Réduire le nombre d'opérations
Annulation catastrophique Soustraire deux nombres proches Réécrire la formule

L'annulation catastrophique — l'erreur la plus vicieuse

Calculer \(\sqrt{x+1}-\sqrt x\) pour \(x\) grand : les deux racines sont presque égales, leur différence perd tous ses chiffres significatifs.

>>> x = 1e16
>>> (x + 1)**0.5 - x**0.5
0.0

La bonne écriture, par quantité conjuguée (chapitre Prérequis 01) :

\[ \sqrt{x+1}-\sqrt x = \frac{1}{\sqrt{x+1}+\sqrt x} \]
>>> 1 / ((x + 1)**0.5 + x**0.5)
5e-09

Zéro contre la bonne réponse. Les deux formules sont mathématiquement identiques et numériquement incomparables.

Autres réécritures salvatrices

Formule dangereuse Formule stable
\(\frac{-b+\sqrt{b^2-4ac}}{2a}\) quand \(b>0\) \(\frac{-2c}{b+\sqrt{b^2-4ac}}\)
\(1-\cos x\) pour \(x\) petit \(2\sin^2\frac x2\)
\(\ln(1+x)\) pour \(x\) petit math.log1p(x)
\(e^x-1\) pour \(x\) petit math.expm1(x)
\(\frac{a+b}{2}\) (débordement) \(a+\frac{b-a}{2}\)

La dernière est le bug de dichotomie du chapitre Prérequis 02.

1.3 Conditionnement

Définition

Le conditionnement d'un problème mesure l'amplification d'une erreur d'entrée. Pour un système linéaire :

\[ \kappa(\mathbf{A}) = \lVert\mathbf{A}\rVert\cdot\lVert\mathbf{A}^{-1}\rVert \]

Une erreur relative \(\varepsilon\) sur les données produit une erreur relative jusqu'à \(\kappa\varepsilon\) sur la solution.

Ordre de grandeur : si \(\kappa = 10^{10}\) et que l'on travaille en double précision (\(\varepsilon\approx10^{-16}\)), il reste 6 chiffres significatifs. Si \(\kappa=10^{16}\), il n'en reste aucun.

Un problème mal conditionné ne se répare pas par un meilleur algorithme

Le conditionnement est une propriété du problème, pas de la méthode. Aucun algorithme ne peut extraire une information que les données ne contiennent pas.

La seule solution est de reformuler : régulariser, changer de base, utiliser une décomposition plus stable.

2. Performance

2.1 Mesurer, pas supposer

La complexité asymptotique ne prédit pas le temps réel

Trois exemples déjà rencontrés dans ce cours :

Cas Asymptotique Réalité
Strassen (Algèbre 05) \(O(n^{2{,}807})\) meilleur Perdant sous \(n\approx1000\)
Stein (Algèbre 11) Plus d'itérations Plus rapide — pas de division
\(n\log n\) vs \(n^{1{,}2}\) (Analyse 04) \(n\log n\) meilleur Perdant sous \(n\approx3\times10^6\)

Le \(O\) décrit la limite, la constante décide de la pratique.

2.2 Ce que la complexité ignore

Facteur Effet
Caches Un accès mémoire dispersé coûte 100× un accès contigu
Allocations Créer un objet dans une boucle domine souvent le calcul
Branchements Une condition imprévisible coûte ~15 cycles
Parallélisme Un algorithme séquentiel « meilleur » perd contre un parallélisable
Mémoire Une matrice \(10^5\times10^5\) dense fait 80 Go

Le protocole de mesure

  1. Mesurer sur plusieurs tailles, espacées géométriquement (\(n, 2n, 4n, \dots\)).
  2. Répéter chaque mesure et prendre la médiane — pas la moyenne, à cause des valeurs aberrantes de planification.
  3. Ajuster une loi de puissance sur \((\ln n, \ln t)\) et vérifier que l'exposant correspond à la théorie (chapitre 03).
  4. Si l'exposant mesuré diffère nettement du théorique, chercher pourquoi — c'est souvent un effet de cache, et c'est le résultat le plus intéressant du TP.

3. Pertinence du modèle

C'est la partie la plus difficile, et celle où le programme met l'accent.

Les cinq questions à poser systématiquement

  1. Quelles hypothèses ai-je faites ? Y compris implicites.
  2. Laquelle est la plus fausse ? Il y en a toujours une.
  3. Dans quel sens biaise-t-elle le résultat ? Sur-estimation ou sous-estimation ?
  4. Quel est le domaine de validité ? Où le modèle cesse-t-il de valoir ?
  5. Que faudrait-il pour le vérifier ? Quelle donnée manque ?

3.1 Le tableau des trahisons classiques

Hypothèse Quand elle est fausse Conséquence
Indépendance Pannes corrélées, réponses de sondage Risque sous-estimé
Stationnarité Trafic web, saisonnalité Pics invisibles
Variance finie Temps de réponse, tailles de fichiers Moyenne instable
Normalité Données bornées ou asymétriques Queues mal estimées
Homogénéité Populations mélangées Paradoxe de Simpson
Linéarité Effets de seuil, saturation Extrapolation absurde

Le paradoxe de Simpson

Une tendance présente dans chaque sous-groupe peut s'inverser quand on agrège les données.

Exemple réel : en 1973, l'université de Berkeley semblait discriminer les femmes — 44 % des hommes admis contre 35 % des femmes. Département par département, les femmes étaient pourtant admises au moins aussi souvent.

Explication : les femmes candidataient davantage dans les départements les plus sélectifs. L'agrégation mélangeait deux populations de difficultés différentes.

La leçon : agréger des populations hétérogènes peut créer des tendances qui n'existent nulle part.

3.2 Extrapolation

Un modèle n'est valide que sur le domaine où il a été ajusté

Extrapoler, c'est supposer que la structure observée persiste — ce que rien ne garantit.

Exemples de désastres :

  • une croissance exponentielle extrapolée donne toujours l'infini, alors que toute ressource sature ;
  • une régression linéaire sur des températures de 10 à 30 °C ne dit rien à \(-20\) °C ;
  • un modèle de complexité ajusté sur \(n\leqslant10^4\) ne prédit rien à \(n=10^9\), où les effets de cache et de mémoire dominent.

4. La grille de critique

À joindre à tout compte rendu

Précision

  • [ ] Le résultat est donné avec son incertitude.
  • [ ] La source dominante d'erreur est identifiée (troncature ? arrondi ? données ?).
  • [ ] Une vérification indépendante a été faite (cas limite, formule close, autre méthode).

Performance

  • [ ] Le temps de calcul est mesuré, pas estimé.
  • [ ] Le comportement en fonction de la taille est testé sur au moins trois valeurs.
  • [ ] La limite de faisabilité est chiffrée (« au-delà de \(n = \dots\), il faudrait \(\dots\) »).

Pertinence

  • [ ] Les hypothèses sont listées explicitement.
  • [ ] La plus fragile est nommée, avec le sens de son biais.
  • [ ] Le domaine de validité est borné.
  • [ ] Ce qu'il faudrait pour aller plus loin est indiqué.

Exemples traités

Exemple 1 — Une critique complète

Résultat obtenu : « la probabilité de perte de données du serveur RAID 5 est de 2,3 % par an ».

Précision. Le calcul est exact — loi binomiale, 13 termes, aucune approximation. L'incertitude vient entièrement du paramètre \(p = 0{,}02\), donné par le constructeur. Les études de terrain le situent plutôt entre 2 % et 8 % selon l'âge et le modèle.

Sensibilité : pour \(p = 0{,}04\), le résultat passe à 8,1 % — plus du triple. Le résultat est plus sensible au paramètre qu'au calcul.

Performance. Sans objet : calcul instantané.

Pertinence — l'hypothèse fragile. L'indépendance des pannes. Or les disques d'un même serveur partagent :

  • l'alimentation et la température ;
  • souvent le même lot de fabrication ;
  • la même charge de travail.

Les études (Schroeder & Gibson, 2007 ; Pinheiro et al., 2007) montrent une corrélation forte : la probabilité d'une seconde panne dans les heures suivant la première est plusieurs fois le taux de base.

Sens du biais : le modèle SOUS-ESTIME le risque.

Domaine de validité. Le calcul vaut pour des disques neufs, sur une année, dans des conditions nominales. Il ne vaut plus après 3 ans (usure) ni en cas de reconstruction RAID — période où la charge extrême multiplie le risque.

Ce qu'il faudrait. Des données de terrain sur le parc réel, et un modèle à pannes corrélées (par exemple une loi bêta-binomiale, ou un modèle de choc commun).

Conclusion honnête : « au moins 2,3 % par an, probablement davantage ; le RAID 6 ou une réplication hors-lot sont justifiés. »

Exemple 2 — Quand la précision numérique décide

On veut tester si une matrice est inversible. Deux approches.

import random

def det2(m):
    return m[0][0]*m[1][1] - m[0][1]*m[1][0]

# matrice presque singuliere
eps = 1e-17
A = [[1.0, 1.0], [1.0, 1.0 + eps]]

print("det =", det2(A))
print("det == 0 ?", det2(A) == 0)

Sortie :

det = 0.0
det == 0 ? True

Le test dit « singulière » alors que la matrice ne l'est pas — son déterminant exact vaut \(10^{-17}\).

Mais l'inverse : une matrice \(100\times100\) inversible dont on multiplie tous les coefficients par \(0{,}1\) voit son déterminant multiplié par \(10^{-100}\). Le test det == 0 la déclarerait singulière alors qu'elle est parfaitement inversible.

Conclusion. det(A) == 0 est inutilisable dans les deux sens. Le bon critère est le conditionnement ou le comptage des valeurs singulières au-dessus d'un seuil relatif — ce que fait numpy.linalg.matrix_rank, qui ne calcule aucun déterminant.

(Voir l'exercice 10 du chapitre Algèbre 07.)

Exercices

★★ Exercice 1. Pour chaque expression, identifier le risque numérique et proposer une réécriture stable.

a) \(\sqrt{x^2+1}-x\) pour \(x\) grand b) \(\frac{1-\cos x}{x^2}\) pour \(x\) petit c) \(\ln(1+x)\) pour \(x\approx10^{-16}\) d) Les racines de \(x^2 - 10^8 x + 1 = 0\)

★★ Exercice 2. Mesurer expérimentalement le seuil au-delà duquel (0.1 + 0.2) + 0.3 diffère de 0.1 + (0.2 + 0.3), puis étudier l'effet de l'ordre de sommation sur \(\sum_{k=1}^{10^7}\frac1k\) (croissant vs décroissant).

★★★ Exercice 3. On calcule \(e^{-20}\) par la série \(\sum\frac{(-20)^k}{k!}\).

a) Implémenter et comparer à math.exp(-20). b) Expliquer l'écart. c) Proposer une méthode stable.

★★★ Exercice 4. Rédiger la critique complète d'un résultat de simulation Monte-Carlo estimant \(\pi\) à \(10^{-3}\) près avec \(10^6\) tirages.

★★★★ Exercice 5. On ajuste une croissance exponentielle sur les 20 premiers points d'un phénomène qui sature ensuite (courbe logistique).

a) Simuler les données et ajuster. b) Extrapoler à \(t=50\) et comparer à la vraie valeur. c) Quel diagnostic aurait permis de détecter le problème avant d'extrapoler ?

★★★★ Exercice 6. Construire un exemple de paradoxe de Simpson avec des nombres simples, et expliquer le mécanisme.


Corrigés

Corrigé — Exercice 1

a) \(\sqrt{x^2+1}-x\). Annulation catastrophique pour \(x\) grand.

\[ \sqrt{x^2+1}-x = \frac{1}{\sqrt{x^2+1}+x} \]

x = 1e8
print("naif  :", (x*x + 1)**0.5 - x)
print("stable:", 1 / ((x*x + 1)**0.5 + x))
naif  : 0.0
stable: 5e-09

b) \(\frac{1-\cos x}{x^2}\). Pour \(x\) petit, \(\cos x\to1\) : annulation.

\[ 1-\cos x = 2\sin^2\frac x2 \implies \frac{1-\cos x}{x^2} = \frac{1}{2}\left(\frac{\sin(x/2)}{x/2}\right)^2 \]

import math
x = 1e-8
print("naif  :", (1 - math.cos(x)) / x**2)
print("stable:", 0.5 * (math.sin(x/2) / (x/2))**2)
naif  : 0.0
stable: 0.5

La valeur exacte tend vers \(\frac12\) (limite usuelle du chapitre Analyse 02). La formule naïve donne 0 — une erreur de 100 %.

c) \(\ln(1+x)\) pour \(x\approx10^{-16}\). Le calcul de \(1+x\) perd tous les chiffres de \(x\).

import math
x = 1e-16
print("naif  :", math.log(1 + x))
print("stable:", math.log1p(x))
naif  : 0.0
stable: 1e-16

d) Racines de \(x^2-10^8x+1\). Ici \(b = -10^8\), \(\Delta = 10^{16}-4\).

La racine \(\frac{-b-\sqrt\Delta}{2}\) soustrait deux nombres presque égaux.

import math
a, b, c = 1.0, -1e8, 1.0
d = math.sqrt(b*b - 4*a*c)
r1 = (-b + d) / (2*a)            # grande racine : stable
r2_naif = (-b - d) / (2*a)       # petite racine : instable
r2_stable = c / (a * r1)         # via le produit des racines = c/a
print(f"r1        = {r1:.6e}")
print(f"r2 naif   = {r2_naif:.10e}   verif: r1*r2 = {r1*r2_naif:.6f}")
print(f"r2 stable = {r2_stable:.10e}   verif: r1*r2 = {r1*r2_stable:.6f}")
r1        = 1.000000e+08
r2 naif   = 7.4505805969e-09   verif: r1*r2 = 0.745058
r2 stable = 1.0000000000e-08   verif: r1*r2 = 1.000000

La méthode naïve se trompe de 25 % sur la petite racine.

Le contrôle par le produit \(x_1x_2 = \frac ca = 1\) (chapitre Prérequis 03) détecte l'erreur immédiatement — et fournit la correction.

Corrigé — Exercice 2
print((0.1 + 0.2) + 0.3, 0.1 + (0.2 + 0.3))
print("egaux ?", (0.1 + 0.2) + 0.3 == 0.1 + (0.2 + 0.3))

n = 10**7
croissant = 0.0
for k in range(1, n + 1):
    croissant += 1.0 / k
decroissant = 0.0
for k in range(n, 0, -1):
    decroissant += 1.0 / k

print(f"croissant   = {croissant:.15f}")
print(f"decroissant = {decroissant:.15f}")
print(f"ecart       = {abs(croissant - decroissant):.3e}")

Résultats.

0.6000000000000001 0.6
egaux ? False
croissant   = 16.695311365857272
decroissant = 16.695311365859965
ecart       = 2.693e-12

Lecture. L'écart de \(2{,}7\times10^{-12}\) sur une somme de \(16{,}7\) correspond à une erreur relative de \(1{,}6\times10^{-13}\) — soit environ mille fois l'epsilon machine.

Pourquoi. Dans l'ordre croissant, on additionne des termes de plus en plus petits à une somme de plus en plus grande : chaque petit terme est partiellement perdu à l'arrondi.

Dans l'ordre décroissant, on commence par les petits termes, qui s'additionnent entre eux avant d'être ajoutés aux grands : moins de perte. C'est cette version qui est la plus précise.

La règle générale

Additionner du plus petit au plus grand. Et pour les cas critiques, utiliser la sommation compensée de Kahan, qui ramène l'erreur à \(O(\varepsilon)\) indépendamment de \(n\).

Corrigé — Exercice 3
import math

def exp_serie(x, n_termes=200):
    terme = 1.0
    s = 1.0
    for k in range(1, n_termes):
        terme *= x / k
        s += terme
    return s

x = -20.0
approx = exp_serie(x)
exact = math.exp(x)
print(f"serie    = {approx:.6e}")
print(f"math.exp = {exact:.6e}")
print(f"erreur relative = {abs(approx-exact)/exact:.3e}")

Résultats.

serie    = 6.147562e-09
math.exp = 2.061154e-09
erreur relative = 1.983e+00

a) L'erreur relative vaut 198 % — le résultat n'a aucun chiffre juste.

b) Explication. Les termes de la série alternent en signe et deviennent énormes avant de décroître. Le plus grand est atteint vers \(k=20\) :

\[ \frac{20^{20}}{20!} \approx 4{,}3\times10^7 \]

On additionne donc des termes de l'ordre de \(10^7\) pour obtenir un résultat de l'ordre de \(10^{-9}\) : seize ordres de grandeur d'annulation.

En double précision, on dispose de 16 chiffres — ils sont tous consommés par l'annulation. Il ne reste rien.

c) Méthode stable.

\[ e^{-20} = \frac{1}{e^{20}} \]

stable = 1.0 / exp_serie(20.0)
print(f"stable   = {stable:.6e}   erreur = {abs(stable-exact)/exact:.3e}")
stable   = 2.061154e-09   erreur = 4.013e-16

Erreur de l'ordre de l'epsilon machine — le maximum de précision atteignable. La série pour \(x>0\) n'a que des termes positifs : aucune annulation.

La leçon

Deux formules mathématiquement identiques — \(e^{-x}\) et \(\frac{1}{e^{x}}\) — ont des comportements numériques radicalement différents.

Règle générale : éviter les sommes alternées de grands termes. Chercher une formulation à termes de même signe.

Corrigé — Exercice 4

Résultat. \(\hat\pi = 3{,}14231\) avec \(10^6\) tirages, intervalle de confiance à 95 % : \(\pm 0{,}0033\).

Précision.

  • Source dominante : l'erreur statistique, en \(\frac{1}{\sqrt n}\). L'erreur d'arrondi est totalement négligeable devant elle (\(10^{-16}\) contre \(3\times10^{-3}\)).
  • Vérification indépendante : l'erreur réelle (\(8\times10^{-4}\)) est bien dans l'intervalle annoncé ✓
  • Limite : atteindre \(10^{-6}\) demanderait \(n\approx10^{13}\) tirages, soit environ 3 heures à \(10^9\) tirages/seconde — pour un résultat que n'importe quelle série donne en microsecondes.

Performance.

  • \(10^6\) tirages : environ 0,5 s en Python pur, 5 ms en C.
  • Le coût est linéaire en \(n\), la mémoire est constante.
  • Parallélisable trivialement : chaque tirage est indépendant. Sur \(k\) cœurs, on divise le temps par \(k\) — mais l'erreur ne diminue que d'un facteur \(\sqrt k\).

Pertinence.

  • Hypothèse fragile : la qualité du générateur pseudo-aléatoire. Le Mersenne Twister de Python a une période de \(2^{19937}-1\) et passe les tests standards — largement suffisant ici. Mais certains générateurs linéaires congruentiels anciens produisent des couples \((U_1,U_2)\) alignés sur des hyperplans, ce qui fausserait précisément ce calcul.
  • Domaine de validité : la méthode est correcte pour toute précision, mais économiquement absurde au-delà de \(10^{-4}\).
  • Pour aller plus loin : utiliser des suites à discrépance faible (Sobol, Halton) — l'erreur passe alors de \(O(n^{-1/2})\) à \(O\!\left(\frac{\ln^d n}{n}\right)\), un gain considérable.

Conclusion honnête : « la méthode fonctionne et l'incertitude est maîtrisée, mais elle n'est pas le bon outil pour ce problème. Elle le devient en dimension supérieure à 4. »

Corrigé — Exercice 5
import math

def logistique(t, K=1000.0, r=0.3, t0=25.0):
    return K / (1 + math.exp(-r * (t - t0)))

def regression(xs, ys):
    n = len(xs)
    mx, my = sum(xs)/n, sum(ys)/n
    sxy = sum((x-mx)*(y-my) for x, y in zip(xs, ys))
    sxx = sum((x-mx)**2 for x in xs)
    syy = sum((y-my)**2 for y in ys)
    a = sxy/sxx
    return a, my - a*mx, sxy**2/(sxx*syy)

ts = list(range(20))
ys = [logistique(t) for t in ts]
ln_y = [math.log(y) for y in ys]

r_est, ln_A, r2 = regression(ts, ln_y)
A = math.exp(ln_A)
print(f"modele ajuste : y = {A:.4f} * exp({r_est:.4f} t)   R2 = {r2:.6f}")

for t in (20, 30, 50):
    pred = A * math.exp(r_est * t)
    vrai = logistique(t)
    print(f"t={t:3d}  predit={pred:12.1f}   vrai={vrai:8.1f}   "
          f"facteur d'erreur = {pred/vrai:.1f}")

Résultats.

modele ajuste : y = 0.5683 * exp(0.2939 t)   R2 = 0.999810
t= 20  predit=       203.1   vrai=   182.4   facteur d'erreur = 1.1
t= 30  predit=      3840.0   vrai=   817.6   facteur d'erreur = 4.7
t= 50  predit=   1372478.8   vrai=   999.4   facteur d'erreur = 1373.2

a) L'ajustement est excellent : \(R^2 = 0{,}99981\) sur la phase initiale.

b) L'extrapolation est catastrophique : à \(t=50\), le modèle prédit 1,37 million au lieu de 1000. Une erreur d'un facteur 1373.

c) Le diagnostic qui aurait alerté.

  1. Regarder les résidus. Ils sont minuscules mais systématiquement structurés : le modèle exponentiel sous-estime au début et surestime à la fin de la plage, signe d'une courbure non capturée.
  2. Ajuster sur des sous-fenêtres. Le taux \(r\) estimé sur \([0;10]\) diffère de celui estimé sur \([10;20]\) — pour une vraie exponentielle, il serait constant. C'est le test décisif.
  3. Le sens physique. Toute croissance réelle sature : ressources finies, population totale, capacité maximale. Un modèle exponentiel non borné est a priori faux à long terme, quel que soit son \(R^2\).

Le message central

Aucun indicateur statistique d'ajustement ne prédit la qualité d'une extrapolation. Un \(R^2\) de 0,9998 sur la plage observée est parfaitement compatible avec une erreur d'un facteur 1400 hors de celle-ci.

C'est l'illustration la plus nette du §3.2, et c'est la raison pour laquelle le sens physique du modèle prime toujours sur son ajustement.

Corrigé — Exercice 6

Construction. Deux traitements médicaux, deux gravités de maladie.

Traitement A Traitement B
Cas légers 81 succès / 87 = 93 % 234 / 270 = 87 %
Cas graves 192 / 263 = 73 % 55 / 80 = 69 %
Total 273 / 350 = 78 % 289 / 350 = 83 %

A gagne dans les deux sous-groupes, et perd au total.

cas = {
    "leger":  {"A": (81, 87),  "B": (234, 270)},
    "grave":  {"A": (192, 263), "B": (55, 80)},
}
for g, d in cas.items():
    for t, (s, n) in d.items():
        print(f"{g:6s} {t} : {s}/{n} = {100*s/n:.0f} %")
for t in ("A", "B"):
    s = sum(cas[g][t][0] for g in cas)
    n = sum(cas[g][t][1] for g in cas)
    print(f"total  {t} : {s}/{n} = {100*s/n:.0f} %")

Mécanisme. Le taux de succès dépend fortement de la gravité (93 %/87 % contre 73 %/69 %), et les deux traitements n'ont pas reçu la même répartition de patients :

  • le traitement A a soigné 75 % de cas graves (263 sur 350) ;
  • le traitement B seulement 23 % (80 sur 350).

A a donc traité une population beaucoup plus difficile. Son taux global en souffre, alors qu'il est meilleur à gravité égale.

L'implication

Agréger des populations hétérogènes peut inverser une conclusion.

La variable « gravité » est une variable de confusion : elle influence à la fois le traitement reçu et le résultat. Ne pas la contrôler produit une conclusion inversée.

C'est exactement le mécanisme de l'exemple 2 du chapitre 01 — glaces et noyades — et c'est pourquoi les essais cliniques sont randomisés : la randomisation garantit que les groupes sont comparables sur toutes les variables, y compris celles qu'on n'a pas pensé à mesurer.


Fin de la partie Modélisation. Suite : Annexes.