Testez une médiane avec le test du signe et Wilcoxon
Une équipe ajoute un cache à son site et mesure le temps de chargement de dix pages, avant et après. Huit pages sur dix sont plus rapides, deux sont un peu plus lentes. Le cache a-t-il vraiment un effet, ou ce résultat pourrait-il être dû au hasard des mesures ? Avec dix paires seulement et des temps de chargement qui ne sont pas normaux, le test t apparié est discutable.
Ce chapitre présente deux réponses non paramétriques : le test du signe, qui ne regarde que le sens de chaque différence, et le test des rangs signés de Wilcoxon, qui regarde aussi son ampleur. Vous verrez qu’ils peuvent conclure très différemment sur les mêmes données.
Le fil rouge : dix pages avant et après le cache
Section intitulée « Le fil rouge : dix pages avant et après le cache »Voici les temps de chargement (en ms), mesurés sur les mêmes dix pages :
| Page | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| Avant | 312 | 285 | 340 | 298 | 405 | 276 | 330 | 358 | 290 | 321 |
| Après | 290 | 280 | 325 | 301 | 362 | 270 | 318 | 330 | 294 | 300 |
| Différence | −22 | −5 | −15 | +3 | −43 | −6 | −12 | −28 | +4 | −21 |
Les données sont appariées (paired) : chaque page est mesurée deux fois. On ne compare donc pas deux groupes, on étudie les différences . Un problème apparié se ramène ainsi à un problème à un échantillon : la médiane des différences est-elle nulle ?
On pose les hypothèses :
- : le cache n’a pas d’effet, la médiane des différences vaut 0 ;
- : la médiane des différences n’est pas nulle (test bilatéral).
Les mêmes tests servent aussi pour un seul échantillon non apparié : pour tester si la médiane d’une variable vaut , on travaille sur .
Le test du signe : ne compter que les sens
Section intitulée « Le test du signe : ne compter que les sens »L’idée est d’une simplicité désarmante. Si la médiane des différences est nulle, chaque différence a une chance sur deux d’être positive, indépendamment des autres. Le nombre de différences positives suit donc une loi binomiale :
où est le nombre de différences non nulles : les différences exactement nulles ne disent rien sur le sens de l’effet, on les retire.
C’est un test exact : la p-valeur se calcule directement avec la loi binomiale, sans aucune approximation, et avec une seule hypothèse (l’indépendance des paires). Il n’exige même pas que la loi soit continue ou symétrique.
Ce résultat est frustrant. Regardez les données : les deux différences positives sont minuscules (+3 et +4 ms), alors que certaines baisses dépassent 20 ou 40 ms. Le test du signe jette cette information, puisqu’il traite une baisse de 43 ms comme une baisse de 5 ms. Peut-on faire mieux sans supposer la normalité ? Oui, en tenant compte de l’ampleur des différences, par leurs rangs.
Le test des rangs signés de Wilcoxon
Section intitulée « Le test des rangs signés de Wilcoxon »La statistique
Section intitulée « La statistique »Le test de Wilcoxon (Wilcoxon signed-rank test) procède en trois étapes :
- on calcule les différences et on retire celles qui sont nulles ;
- on classe les valeurs absolues par ordre croissant : est le rang de ;
- on additionne les rangs des différences positives :
On définit de même pour les différences négatives, et , la somme de tous les rangs de 1 à .
La loi sous l’hypothèse nulle
Section intitulée « La loi sous l’hypothèse nulle »Pourquoi la loi de ne dépend-elle pas de la loi des données ? Sous , on suppose que la loi des différences est symétrique autour de 0. Alors, chaque rang a une chance sur deux, indépendamment des autres, de porter un signe « + ». Il y a affectations de signes équiprobables, et est la somme des rangs qui ont reçu un « + ».
Pour , on peut énumérer les cas. Combien donnent ? Les ensembles de rangs de somme au plus 3 sont : aucun rang, , , et , soit 5 cas. D’où :
Cette fois, on rejette au seuil de 5 %, et même de 1 %. Le cache a bien un effet.
L’approximation normale
Section intitulée « L’approximation normale »Pour grand, on n’énumère plus : sous ,
et suit approximativement la loi normale centrée réduite. Ici, , , donc et . C’est proche de la valeur exacte ; l’approximation devient très bonne au-delà d’une vingtaine de paires. Avec des ex aequo parmi les , on leur donne le rang moyen et on corrige la variance.
En pratique : Python et R
Section intitulée « En pratique : Python et R »Pour comparer, le test t apparié donne ici avec 9 degrés de liberté et . Les trois tests en Python :
import numpy as npfrom scipy import stats
avant = np.array([312, 285, 340, 298, 405, 276, 330, 358, 290, 321])apres = np.array([290, 280, 325, 301, 362, 270, 318, 330, 294, 300])d = apres - avant
# Test du signe : 2 différences positives sur 10 non nullesn_pos, n = (d > 0).sum(), (d != 0).sum()print(stats.binomtest(n_pos, n, p=0.5).pvalue) # 0.109
# Wilcoxon signé (loi exacte pour les petits n sans ex aequo)res = stats.wilcoxon(apres, avant)print(res.statistic, res.pvalue) # 3.0 et environ 0.0098
# Test t apparié, pour comparaisonprint(stats.ttest_rel(apres, avant).pvalue) # environ 0.012En bilatéral, scipy renvoie comme statistique le minimum de et , ici 3. En R :
avant <- c(312, 285, 340, 298, 405, 276, 330, 358, 290, 321)apres <- c(290, 280, 325, 301, 362, 270, 318, 330, 294, 300)d <- apres - avant
binom.test(sum(d > 0), sum(d != 0), p = 0.5) # p = 0.1094wilcox.test(apres, avant, paired = TRUE) # V = 3, p = 0.009766wilcox.test(apres, avant, paired = TRUE, conf.int = TRUE) # + estimation du décalageR appelle la statistique et renvoie . Avec conf.int = TRUE, il fournit l’estimateur de Hodges-Lehmann du décalage : la médiane de toutes les moyennes de paires avec (les « moyennes de Walsh »). Ici, il vaut −13,5 ms, un résumé robuste de l’effet du cache.
Quel test choisir ?
Section intitulée « Quel test choisir ? »| Test | Utilise | Hypothèse sur la loi des différences | Puissance |
|---|---|---|---|
| Signe | le sens | aucune (continuité non requise) | la plus faible |
| Wilcoxon signé | le sens et le rang de l’ampleur | continue et symétrique | proche du test t |
| t apparié | les valeurs | normale (ou grand) | la meilleure si normale |
Une règle simple : Wilcoxon par défaut ; le signe si les données sont très asymétriques, ordinales avec beaucoup d’ex aequo, ou si l’on ne connaît que le sens de chaque différence (« préfère A » ou « préfère B »).
En résumé
Section intitulée « En résumé »- Des données appariées se ramènent à un échantillon de différences ; on teste si leur médiane est nulle.
- Le test du signe compte les différences positives : sous , après retrait des différences nulles.
- Le test de Wilcoxon signé additionne les rangs des associés aux différences positives : , de moyenne et de variance sous .
- Wilcoxon est bien plus puissant que le signe, au prix d’une hypothèse de symétrie des différences.
- Sur notre exemple, le signe donne et Wilcoxon : l’ampleur des différences change la conclusion.
scipy.stats.binomtest,scipy.stats.wilcoxon; en R,binom.testetwilcox.test(..., paired = TRUE).
Exercices
Section intitulée « Exercices »Exercice · Un test du signe à la main
facileQuinze utilisateurs testent deux interfaces et indiquent laquelle ils préfèrent. Douze préfèrent la nouvelle, trois l’ancienne. Au seuil de 5 %, peut-on conclure à une préférence ?
Voir la solution
Sous (aucune préférence), le nombre de préférences pour la nouvelle interface suit . On a observé :
La p-valeur bilatérale vaut : on rejette au seuil de 5 %. Ici, seul le test du signe s’applique, puisqu’on ne dispose que du sens de chaque préférence, pas d’une ampleur.
En code
from scipy import stats
# 12 préférences pour la nouvelle interface sur 15, Binom(15, 1/2) sous H0print(stats.binom.sf(11, 15, 0.5)) # P(S >= 12) = 0.0176print(stats.binomtest(12, 15, p=0.5).pvalue) # bilatéral : 0.0352# 12 préférences pour la nouvelle interface sur 15, Binom(15, 1/2) sous H01 - pbinom(11, 15, 0.5) # P(S >= 12) = 0.0176binom.test(12, 15, p = 0.5)$p.value # bilatéral : 0.0352Exercice · Wilcoxon sur six paires
moyenSix coureurs mesurent leur temps sur 5 km avec deux paires de chaussures. Les différences (ancienne − nouvelle, en secondes) sont : 1,5 ; −0,4 ; 2,1 ; 0,8 ; −1,1 ; 3,0.
- Calculez et .
- La loi exacte de pour donne . Calculez la p-valeur bilatérale et concluez.
- Quelle est la plus petite p-valeur bilatérale possible avec six paires ?
Voir la solution
- Valeurs absolues triées : 0,4 (rang 1, négative), 0,8 (rang 2), 1,1 (rang 3, négative), 1,5 (rang 4), 2,1 (rang 5), 3,0 (rang 6). Donc et ; on vérifie que .
- La statistique la plus extrême est : . On ne peut pas conclure que les nouvelles chaussures font gagner du temps.
- Le cas le plus extrême est (ou ), qui n’arrive que pour une seule affectation de signes sur : . Avec six paires, il faut donc que toutes les différences aillent dans le même sens pour rejeter au seuil de 5 %. Avec cinq paires, : le rejet à 5 % est impossible, quelles que soient les données.
En code
import numpy as npfrom scipy import stats
d = np.array([1.5, -0.4, 2.1, 0.8, -1.1, 3.0])rangs = stats.rankdata(np.abs(d))print(rangs[d > 0].sum(), rangs[d < 0].sum()) # W+ = 17.0, W- = 4.0
res = stats.wilcoxon(d) # loi exacte (n = 6, pas d'ex aequo)print(res.statistic, res.pvalue) # 4.0 0.21875 (= 2 x 7/64)
# Plus petite p-valeur possible : toutes les différences du même signeprint(stats.wilcoxon([1, 2, 3, 4, 5, 6]).pvalue) # 0.03125 (= 2/64)print(stats.wilcoxon([1, 2, 3, 4, 5]).pvalue) # 0.0625 (= 2/32)d <- c(1.5, -0.4, 2.1, 0.8, -1.1, 3.0)rangs <- rank(abs(d))sum(rangs[d > 0]); sum(rangs[d < 0]) # W+ = 17, W- = 4
wilcox.test(d) # V = 17, p-value = 0.2188 (= 2 x 7/64)
# Plus petite p-valeur possible : toutes les différences du même signewilcox.test(1:6)$p.value # 0.03125 (= 2/64)wilcox.test(1:5)$p.value # 0.0625 (= 2/32)Exercice · Signe ou Wilcoxon ?
difficileReprenez les dix pages du fil rouge, mais supposez que la page 4 ait donné une différence de +50 ms au lieu de +3 ms (les autres différences sont inchangées).
- La p-valeur du test du signe change-t-elle ?
- Recalculez . Sans calculer la p-valeur exacte, dites si la conclusion de Wilcoxon risque de changer.
- Qu’en retenez-vous sur ce que mesure chaque test ?
Voir la solution
- Non : il y a toujours 2 différences positives sur 10, donc . Le test du signe ignore l’ampleur.
- Les valeurs absolues triées deviennent 4, 5, 6, 12, 15, 21, 22, 28, 43, 50. Les différences positives sont +4 (rang 1) et +50 (rang 10) : , . On s’éloigne moins de la valeur attendue 27,5 qu’avec : la p-valeur augmente nettement (en approximation normale, , soit ; la loi exacte donne ). La conclusion change : on ne rejette plus au seuil de 5 %.
- Wilcoxon est sensible à l’ampleur relative des différences, via leurs rangs. Une page nettement ralentie pèse autant que la plus forte accélération. Le test du signe, lui, ne voit que la proportion de pages ralenties. C’est pourquoi Wilcoxon est plus puissant quand l’effet est cohérent, mais aussi pourquoi il suppose une loi symétrique.
En code
import numpy as npfrom scipy import stats
d = np.array([-22, -5, -15, 50, -43, -6, -12, -28, 4, -21]) # page 4 : +50 au lieu de +3
# 1. Test du signe : toujours 2 différences positives sur 10print(stats.binomtest((d > 0).sum(), (d != 0).sum()).pvalue) # 0.109
# 2. Wilcoxon : W+ et p-valeurs exacte et approchéerangs = stats.rankdata(np.abs(d))print(rangs[d > 0].sum()) # W+ = 11.0print(stats.wilcoxon(d).pvalue) # exacte : 0.105z = (11 - 27.5) / np.sqrt(96.25)print(z, 2 * stats.norm.cdf(z)) # -1.68 0.093d <- c(-22, -5, -15, 50, -43, -6, -12, -28, 4, -21) # page 4 : +50 au lieu de +3
# 1. Test du signe : toujours 2 différences positives sur 10binom.test(sum(d > 0), sum(d != 0))$p.value # 0.109
# 2. Wilcoxon : W+ et p-valeurs exacte et approchéerangs <- rank(abs(d))sum(rangs[d > 0]) # W+ = 11wilcox.test(d)$p.value # exacte : 0.105z <- (11 - 27.5) / sqrt(96.25)c(z, 2 * pnorm(z)) # -1.68 0.093