Comparez deux échantillons
Une équipe web a réécrit une page de son site. La nouvelle version se charge-t-elle plus vite que l’ancienne ? Des patients traités guérissent-ils plus vite que des patients sous placebo ? Les abonnés venus des réseaux sociaux choisissent-ils la même formule que les autres ?
Toutes ces questions comparent deux groupes. On ne teste plus un paramètre contre une valeur fixée, mais deux paramètres l’un contre l’autre. Ce chapitre présente les tests correspondants, dans l’ordre où on les enchaîne en pratique, sur un fil rouge de temps de chargement.
Le fil rouge : deux versions d’une page
Section intitulée « Le fil rouge : deux versions d’une page »On mesure le temps de chargement (en secondes) de l’ancienne version A sur 10 visites et de la nouvelle version B sur 8 autres visites :
| Version | Temps de chargement (s) |
|---|---|
| A (ancienne) | 2,8 ; 3,4 ; 3,1 ; 2,6 ; 3,9 ; 3,3 ; 2,9 ; 3,6 ; 3,0 ; 3,4 |
| B (nouvelle) | 2,5 ; 2,9 ; 2,4 ; 3,1 ; 2,7 ; 2,2 ; 2,8 ; 2,6 |
Résumés : , s, ; , s, .
On suppose les deux échantillons indépendants (des visites différentes), gaussiens : et . La version B a l’air plus rapide de 0,55 s. Est-ce significatif ?
Sous ces hypothèses, deux résultats servent de base à tout le chapitre :
Le premier vient de ce qu’une différence de normales indépendantes est normale ; le second, du théorème de Fisher appliqué à chaque échantillon et de la définition de la loi de Fisher.
Étape 1 : comparer les variances avec le test de Fisher
Section intitulée « Étape 1 : comparer les variances avec le test de Fisher »Pourquoi commencer par les variances ? Parce que le test de comparaison des moyennes le plus simple suppose des variances égales. On teste donc d’abord contre .
Sous , les se simplifient dans le second résultat :
On rejette si est trop petit ou trop grand, c’est-à-dire hors de . En pratique, on place la plus grande des deux variances au numérateur : , et il suffit de comparer au quantile supérieur . C’est possible grâce à la relation .
Étape 2 : comparer les moyennes avec le test de Student
Section intitulée « Étape 2 : comparer les moyennes avec le test de Student »Variances égales
Section intitulée « Variances égales »Si , on estime cette variance commune en regroupant les deux échantillons. C’est la variance poolée :
Chaque échantillon y pèse selon son nombre de degrés de liberté. Sous ,
Les régions critiques sont celles du test de Student à un échantillon, avec degrés de liberté.
Variances inégales : le test de Welch
Section intitulée « Variances inégales : le test de Welch »Si les variances diffèrent, la statistique
n’a plus de loi exacte simple. Pour de grands échantillons, le TCL assure qu’elle suit approximativement sous , même sans normalité : c’est le test asymptotique vu en cours. Pour de petits échantillons gaussiens, Welch a proposé de l’approcher par une loi de Student dont le nombre de degrés de liberté se calcule à partir des données (formule de Welch-Satterthwaite). C’est le comportement par défaut de t.test en R.
Sur nos données, le test de Welch donne avec environ 15,9 degrés de liberté, et une p-valeur unilatérale d’environ 0,002 : même conclusion.
A <- c(2.8, 3.4, 3.1, 2.6, 3.9, 3.3, 2.9, 3.6, 3.0, 3.4)B <- c(2.5, 2.9, 2.4, 3.1, 2.7, 2.2, 2.8, 2.6)var.test(A, B) # Fisher : F = 1.88, p = 0.42t.test(A, B, var.equal = TRUE, alternative = "greater") # Student : t = 3.30, p = 0.002t.test(A, B, alternative = "greater") # Welch (défaut de R)from scipy import statsA = [2.8, 3.4, 3.1, 2.6, 3.9, 3.3, 2.9, 3.6, 3.0, 3.4]B = [2.5, 2.9, 2.4, 3.1, 2.7, 2.2, 2.8, 2.6]print(stats.ttest_ind(A, B, equal_var=True, alternative="greater")) # Studentprint(stats.ttest_ind(A, B, equal_var=False, alternative="greater")) # WelchComparer deux proportions
Section intitulée « Comparer deux proportions »Le même raisonnement vaut pour deux proportions. Sur visiteurs de la version A, ont acheté ; sur de la version B, . Sous , on estime la proportion commune par , et
C’est le test des tests A/B sur les taux de conversion (prop.test(c(x1, x2), c(n1, n2)) en R). Il est équivalent au test du chi-deux d’indépendance sur un tableau , que nous voyons maintenant.
Complément : le test d’indépendance du chi-deux
Section intitulée « Complément : le test d’indépendance du chi-deux »Nos supports de cours ne présentent pas ce test ; c’est un complément standard, très utilisé en analyse de données. Il répond à la question : deux variables qualitatives sont-elles liées ?
Un service de streaming croise la formule choisie par 160 nouveaux abonnés et le canal par lequel ils l’ont découvert :
| Observé | Réseaux sociaux | Moteur de recherche | Bouche-à-oreille | Total |
|---|---|---|---|---|
| Mensuel | 48 | 30 | 22 | 100 |
| Annuel | 12 | 20 | 28 | 60 |
| Total | 60 | 50 | 50 | 160 |
On teste : « la formule est indépendante du canal » contre : « elles sont liées ». Sous , la probabilité d’une case est le produit des probabilités marginales, d’où l’effectif attendu
où est le total de la ligne , celui de la colonne et l’effectif total. Par exemple, . La statistique
compare observé et attendu, pour un tableau à lignes et colonnes. Le nombre de degrés de liberté, , correspond au nombre de cases qu’on peut remplir librement une fois les marges fixées.
tab <- matrix(c(48, 12, 30, 20, 22, 28), nrow = 2, dimnames = list(formule = c("mensuel", "annuel"), canal = c("reseaux", "moteur", "bouche")))test <- chisq.test(tab)test # X-squared = 15.27, df = 2, p-value = 0.00048test$expected # effectifs attendus sous H0from scipy import statstab = [[48, 30, 22], [12, 20, 28]]chi2, p, ddl, attendus = stats.chi2_contingency(tab)print(chi2, p, ddl) # 15.27, 0.00048, 2En résumé
Section intitulée « En résumé »- Deux échantillons indépendants et gaussiens : Fisher pour les variances, , la plus grande au numérateur.
- Student à variances égales avec la variance poolée , et degrés de liberté ; Welch si les variances diffèrent ; test asymptotique normal pour de grands échantillons.
- Des données appariées se traitent par un test à un échantillon sur les différences.
- Deux proportions : test normal avec proportion commune estimée, base des tests A/B.
- Indépendance du chi-deux (complément) : , ; les contributions montrent d’où vient le lien.
Exercices
Section intitulée « Exercices »Exercice · Enchaîner Fisher et Student
facileDeux engrais sont testés sur des parcelles indépendantes. Engrais 1 : , quintaux par hectare, . Engrais 2 : , , . On suppose la normalité. Au niveau 5 %, les rendements moyens diffèrent-ils ? ( ; .)
Voir la solution
Variances. On met la plus grande au numérateur : . On conserve l’égalité des variances.
Moyennes. , donc .
Comme , on conserve au niveau 5 % : la différence n’est pas significative, de justesse (p-valeur ). Une expérience avec plus de parcelles serait utile.
En code
import numpy as npfrom scipy import stats
n1, x_bar, s2_x = 12, 52.0, 16n2, y_bar, s2_y = 12, 48.5, 20
# Fisher : plus grande variance au numérateurF0 = s2_y / s2_xprint(F0, stats.f.ppf(0.975, n2 - 1, n1 - 1)) # 1.25 < 3.47 : variances compatibles
# Student à variance poolée, à partir des résumésres = stats.ttest_ind_from_stats(x_bar, np.sqrt(s2_x), n1, y_bar, np.sqrt(s2_y), n2, equal_var=True)print(res.statistic, res.pvalue) # 2.02 0.056 : on conserve H0n1 <- 12; x_bar <- 52.0; s2_x <- 16n2 <- 12; y_bar <- 48.5; s2_y <- 20
# Fisher : plus grande variance au numérateurF0 <- s2_y / s2_xF0; qf(0.975, n2 - 1, n1 - 1) # 1.25 < 3.47 : variances compatibles
# Student à variance poolée, à partir des résuméss2_p <- ((n1 - 1) * s2_x + (n2 - 1) * s2_y) / (n1 + n2 - 2) # 18t0 <- (x_bar - y_bar) / sqrt(s2_p * (1 / n1 + 1 / n2))t0; 2 * pt(-abs(t0), df = n1 + n2 - 2) # 2.02 0.056 : on conserve H0Exercice · Un test A/B sur un taux de conversion
moyenVersion A : 120 achats sur 2 400 visiteurs. Version B : 156 achats sur 2 400 visiteurs. Au niveau 5 %, B convertit-elle mieux ?
Voir la solution
et . On teste contre . Proportion commune : .
Comme , on rejette : B convertit significativement mieux (p-valeur ). Attention toutefois : ce calcul n’est valide que si la taille d’échantillon a été fixée à l’avance, et non en arrêtant l’expérience au premier résultat favorable.
En code
import numpy as npfrom scipy import statsfrom statsmodels.stats.proportion import proportions_ztest
achats = np.array([156, 120]) # B puis Avisiteurs = np.array([2400, 2400])
# À la main, avec la proportion communep1, p2 = achats / visiteursp = achats.sum() / visiteurs.sum() # 0.0575t0 = (p1 - p2) / np.sqrt(p * (1 - p) * (1 / 2400 + 1 / 2400))print(t0, stats.norm.sf(t0)) # 2.23 0.013
# Même test avec statsmodels (H1 : p_B > p_A)print(proportions_ztest(achats, visiteurs, alternative="larger")) # (2.23, 0.013)achats <- c(156, 120) # B puis Avisiteurs <- c(2400, 2400)
# À la main, avec la proportion communep_hat <- achats / visiteursp <- sum(achats) / sum(visiteurs) # 0.0575t0 <- (p_hat[1] - p_hat[2]) / sqrt(p * (1 - p) * (1 / 2400 + 1 / 2400))t0; 1 - pnorm(t0) # 2.23 0.013
# Même test avec prop.test (H1 : p_B > p_A) ; X-squared = t0^2prop.test(achats, visiteurs, alternative = "greater", correct = FALSE)$p.value # 0.013Exercice · Indépendance sur un tableau 2 × 2
difficileSur 200 patients, 100 reçoivent un traitement et 100 un placebo. Guérisons : 70 sous traitement, 55 sous placebo. Calculez la statistique du chi-deux d’indépendance (sans correction) et vérifiez qu’elle vaut le carré de la statistique du test de comparaison de deux proportions.
Voir la solution
Tableau : traitement (70 guéris, 30 non), placebo (55, 45). Marges : 125 guéris, 75 non guéris, 100 par groupe.
Effectifs attendus : guéris et non guéris dans chaque groupe. Chaque écart vaut :
Avec degré de liberté, on compare à : on rejette l’indépendance.
Test de deux proportions : , , . , et . De même, : les deux tests bilatéraux sont identiques.
En code
import numpy as npfrom scipy import statsfrom statsmodels.stats.proportion import proportions_ztest
tab = np.array([[70, 30], # traitement : guéris, non guéris [55, 45]]) # placeboD, p, ddl, attendus = stats.chi2_contingency(tab, correction=False)print(attendus) # [[62.5 37.5] [62.5 37.5]]print(D, ddl, p) # 4.8 1 0.028
z, p_z = proportions_ztest([70, 55], [100, 100])print(z, z**2, p_z) # 2.19 4.8 0.028 : même testtab <- matrix(c(70, 55, 30, 45), nrow = 2, dimnames = list(groupe = c("traitement", "placebo"), issue = c("gueri", "non gueri")))test <- chisq.test(tab, correct = FALSE)test$expected # 62.5 et 37.5 dans chaque groupetest$statistic; test$p.value # 4.8 0.028
p1 <- 0.70; p2 <- 0.55; p <- 125 / 200t0 <- (p1 - p2) / sqrt(p * (1 - p) * (1 / 100 + 1 / 100))t0; t0^2 # 2.19 4.8 : même statistique