Leçon 4 sur 8
Unité · Comparer deux groupes
p = 0,023, et un tiers d'une journée
Les garçons sont présents à 88,77 % et les filles à 88,21 %. Sur 68 267 pointages, c'est significatif à p = 0,023. C'est aussi une différence d'un tiers de journée scolaire par enfant et par trimestre, sur laquelle aucun programme n'agirait et aucun ne devrait.
Un résultat significatif sur lequel personne ne devrait agir
import pandas as pd
import numpy as np
from statsmodels.stats.proportion import proportions_ztest
attendance = pd.read_csv("school-attendance-2024.v1.csv")
enrolment = pd.read_csv("school-enrolment-2024.v1.csv")
current = enrolment[enrolment["school_year"] == 2024]
MARKS = {"true": True, "Y": True, "false": False, "N": False}
marked = attendance[attendance["present"].isin(MARKS)].copy()
marked["attended"] = marked["present"].map(MARKS)
marked = marked.merge(current[["student_id", "sex"]], on="student_id")
by_sex = marked.groupby("sex")["attended"].agg(["sum", "size"])
print(by_sex)
stat, p = proportions_ztest(by_sex["sum"], by_sex["size"])
print(f"z = {stat:.2f}, p = {p:.4f}")
library(dplyr)
marked |> summarise(k = sum(attended), n = n(), .by = sex)
prop.test(c(29655, 30749), c(33408, 34859))
| Groupe | Présences | Pointages | Taux |
|---|---|---|---|
| Garçons | 29 655 | 33 408 | 88,77 % |
| Filles | 30 749 | 34 859 | 88,21 % |
Différence 0,56 point de pourcentage. z = 2,28, p = 0,023.
Selon la convention qu’emploie tout rapport, c’est une différence significative de présence selon le sexe. Elle passerait une relecture. Elle n’est aussi, opérationnellement, rien du tout.
Convertissez-la dans l’unité de la décision
term_days = marked["attendance_date"].nunique()
gap = 0.0056
print(f"{term_days} school days in the term")
print(f"0.56 points = {gap * term_days:.2f} days per student per term")
# The statistic is in percentage points. The decision is in days.
Soixante jours de classe, donc 0,56 % vaut 0,34 jour — environ un tiers de journée par enfant et par trimestre.
Aucune intervention sur la présence n’est conçue à cette résolution. Aucun directeur d’école ne peut agir dessus. Aucune ligne budgétaire ne bouge. Le résultat est réel, reproductible, statistiquement significatif et opérationnellement vide, et ces quatre choses sont parfaitement compatibles.
Convertissez toujours un effet dans l’unité où se prend la décision. Les points de pourcentage sont l’unité de l’analyste ; les journées, les enfants, les cas et les francs sont celle du programme.
Pourquoi p est devenu petit : c’était le n
Le p répond à « à quel point ces données seraient surprenantes s’il n’y avait aucune différence », et la surprise croît avec la taille d’échantillon à effet constant.
for scale in (0.05, 0.2, 1.0):
k = (by_sex["sum"] * scale).round().astype(int)
n = (by_sex["size"] * scale).round().astype(int)
stat, p = proportions_ztest(k, n)
print(f"n = {n.sum():>6,} difference {k[1]/n[1] - k[0]/n[0]:+.2%} p = {p:.3f}")
# Same proportions, smaller n, and the p-value walks back across 0.05.
| Échantillon | Différence | p |
|---|---|---|
| 5 % des pointages (n=3 413) | +0,62 % | 0,570 |
| 20 % (n=13 654) | +0,55 % | 0,314 |
| 100 % (n=68 267) | +0,56 % | 0,023 |
L’effet n’a jamais changé. Seul l’échantillon a changé. Un p est une affirmation sur la force de la preuve, non sur l’ampleur, et avec un échantillon assez grand toute différence devient significative.
Un p seul ne peut donc jamais vous dire si quelque chose compte. Il vous dit si vous pouvez écarter zéro, et zéro est rarement la comparaison intéressante.
Rapportez une taille d’effet à côté de chaque p
Trois mesures d’effet couvrent presque tout ce dont un rapport de programme a besoin.
| Mesure | Pour | Se lit comme |
|---|---|---|
| Différence en points de pourcentage | Deux proportions | La réponse directe |
| Rapport de risques | Deux proportions, événements rares | Combien de fois plus probable |
| d de Cohen | Deux moyennes | Écarts-types de séparation |
p_boys, p_girls = by_sex["sum"] / by_sex["size"]
print(f"difference: {p_boys - p_girls:+.2%} points")
print(f"risk ratio: {p_boys / p_girls:.3f}")
# Two lines. There is no excuse for a p-value with no effect size beside it.
Rapport de risques 1,006. Les garçons sont présents 0,6 % plus souvent en termes relatifs — une autre façon de dire le même rien.
Comparez à l’écart selon le handicap de la leçon précédente : une différence de 19,4 points et un rapport de risques de 0,58, c’est-à-dire que les cas signalant un handicap aboutissent à un peu plus de la moitié du taux. Les deux mêmes nombres, calculés de la même façon, décrivent un constat dans un cas et du bruit dans l’autre, et seule la taille d’effet les distingue.
Fixez le seuil avant de tester
La défense contre les deux erreurs — courir après un rien significatif, et écarter un quelque chose non significatif — consiste à décider d’avance quelle ampleur de différence changerait ce que vous faites.
MEANINGFUL = 0.03 # 3 points of attendance, about 1.8 days a term
observed = p_boys - p_girls
print(f"observed {observed:+.2%}, threshold {MEANINGFUL:.0%}")
print(f"large enough to act on: {abs(observed) >= MEANINGFUL}")
# Write the threshold in the analysis plan, not after seeing the result.
Trois points de présence font environ 1,8 jour par trimestre, c’est-à-dire l’échelle à laquelle un programme de suivi serait conçu. Les 0,56 observés en valent le cinquième.
Écrire MEANINGFUL avant de lancer le test est ce qui empêche le seuil de se
déplacer là où le résultat est tombé. C’est la même discipline que le tableau des
facteurs de confusion du cours d’épidémiologie : décidez la règle avant de voir
le nombre auquel elle s’appliquera.
Les quatre cas
cases = pd.DataFrame([
("significant, large", "report it", "disability gap, -19.4 pts"),
("significant, small", "report the effect size, say it is small", "attendance by sex, 0.56 pts"),
("not significant, large", "report the interval; underpowered", "over-age by sex, +5.5 pts"),
("not significant, small", "report as no evidence of a difference", "-"),
], columns=["case", "what to do", "example in this course"])
print(cases)
# Four quadrants, and only one of them is "publish the p-value".
La troisième ligne est celle qu’on traite mal. L’écart de retard d’âge selon le sexe valait 5,5 points avec p = 0,066, et l’instinct est de rapporter « pas de différence ». Le rapport correct dit que l’étude n’a pas pu trancher une différence d’une ampleur qui aurait compté — ce qui est une affirmation sur l’échantillon, et un argument pour en prendre un plus grand plutôt que pour fermer la question.
Rapportez-le en entier
Attendance by sex, February to April 2024
Boys 88.77% 29,655 of 33,408 marks
Girls 88.21% 30,749 of 34,859 marks
Difference +0.56 percentage points (p = 0.023, risk ratio 1.006).
Equivalent to 0.34 school days per child per term. Below the 3-point
threshold set in the analysis plan and not reported as a programme
finding. The p-value is small because the analysis has 68,267 marks, not
because the difference is large.
La dernière phrase est ce qui rend le bloc honnête. Un lecteur qui ne voit que « p = 0,023 » supposera que la différence compte, et la phrase qui dit pourquoi elle ne compte pas tient sur une ligne.
La suite
Chaque test jusqu’ici portait sur une comparaison. La leçon suivante en lance vingt-trois d’un coup, sur un effet qui n’existe pas, et compte combien reviennent « significatives ».