Retour à la leçon·Leçon 5 sur 8·Où les comparaisons cèdent
Vingt-quatre tests, deux constats, aucun effet
Le même diaporama que les téléchargements, rendu sous forme de page. Lancez le diaporama pour le présenter en plein écran — les flèches ou un clic avancent d'une diapositive, Échap quitte.
Ce que couvre cette leçon
- Une comparaison dont on connaît la réponse
- Deux, c'est ce que le hasard vous devait
- Bonferroni : divisez le seuil par le nombre de tests
- Benjamini-Hochberg, quand vingt-quatre devient deux cents
- La correction ne détruit pas les constats réels
- Comptez les tests, y compris ceux que vous n'avez pas rapportés
- Rapportez-le en entier
- La suite
Notes du présentateur
Le registre d'inscription tire le sexe indépendamment de tout le reste, si bien que l'écart de retard d'âge selon le sexe vaut exactement zéro par construction. Testez-le école par école et deux des vingt-quatre ressortent significatives, ce qui est à peu près ce que le hasard vous devait.Une comparaison dont on connaît la réponse — En Python (suite)
import pandas as pd import numpy as np from statsmodels.stats.proportion import proportions_ztest enrolment = pd.read_csv("school-enrolment-2024.v1.csv") clean = enrolment[(enrolment["school_year"] == 2024) & (enrolment["age_years"] <= 20) & enrolment["grade"].between(1, 6)] clean = clean.assign(over_age=clean["age_years"] > clean["grade"] + 5) results = [] for school, group in clean.groupby("school_id"): counts = group.groupby("sex")["over_age"].agg(["sum", "size"]) if counts["size"].min() < 5: continue stat, p = proportions_ztest(counts["sum"], counts["size"])Notes du présentateur
Les deux leçons précédentes testaient une comparaison à la fois. Une vraie analyse s'arrête rarement à une seule : un relecteur demande le même indicateur par district, par année, par école, par statut de déplacement, et l'analyste lance chacun. Cette leçon en lance vingt-quatre sur une comparaison dont la vraie réponse est connue, parce que le jeu de données a été produit avec le sexe tiré indépendamment de l'âge, de l'année, du redoublement et de l'école. La vraie différence de retard d'âge selon le sexe vaut exactement zéro, et tout ce qu'un test y trouve est un artefact d'échantillonnage.Une comparaison dont on connaît la réponse — En Python (suite)
results.append({"school": school, "p": p, "boys": counts.loc["m", "sum"] / counts.loc["m", "size"], "girls": counts.loc["f", "sum"] / counts.loc["f", "size"]}) tests = pd.DataFrame(results).sort_values("p") print(f"{len(tests)} schools tested, {(tests['p'] < 0.05).sum()} significant")Une comparaison dont on connaît la réponse — En R
library(dplyr) clean |> summarise(k = sum(over_age), n = n(), .by = c(school_id, sex)) |> tidyr::pivot_wider(names_from = sex, values_from = c(k, n)) |> filter(n_m >= 5, n_f >= 5) |> rowwise() |> mutate(p = prop.test(c(k_m, k_f), c(n_m, n_f))$p.value)Une comparaison dont on connaît la réponse
École Garçons Filles p SCH23 62,5 % (10/16) 25,9 % (7/27) 0,018 SCH06 39,1 % (9/23) 12,5 % (3/24) 0,036 SCH21 43,5 % (10/23) 25,8 % (8/31) 0,173 SCH03 50,0 % (11/22) 30,8 % (8/26) 0,175 SCH04 27,3 % (3/11) 55,6 % (5/9) 0,199 Une comparaison dont on connaît la réponse
- Vingt-quatre écoles testées, deux significatives à p < 0,05 — Les deux seraient écrites
- Aucune des deux n'est réelle — Le générateur n'a jamais laissé le sexe toucher quoi que ce soit
Notes du présentateur
Vingt-quatre écoles testées, deux significatives à p < 0,05. Les deux seraient écrites. SCH23 en particulier paraît frappante — des garçons en retard d'âge à plus du double du taux des filles, dans une école de 43 enfants. Aucune des deux n'est réelle. Le générateur n'a jamais laissé le sexe toucher quoi que ce soit.Deux, c'est ce que le hasard vous devait — En Python
n_tests = len(tests) print(f"tests run: {n_tests}") print(f"expected significant if nothing is real: {0.05 * n_tests:.1f}") print(f"chance of at least one: {1 - 0.95 ** n_tests:.1%}")Deux, c'est ce que le hasard vous devait — En R
# 1 - 0.95^24. The arithmetic is one line and it is the whole lesson.Deux, c'est ce que le hasard vous devait
- Faux positifs attendus : 1,2. Observés : 2 — La probabilité d'obtenir au moins un résultat significatif à partir de…
- Un seuil de 0,05 est une promesse portant sur un test — Il dit que si rien ne se passe, vous serez trompé une fois sur…
- C'est pourquoi « nous avons regardé par école et deux écoles se détachent » n'est pas un constat — C'est une…
Notes du présentateur
Faux positifs attendus : 1,2. Observés : 2. La probabilité d'obtenir au moins un résultat significatif à partir de vingt-quatre hypothèses nulles vraies vaut 70,8 % — le résultat surprenant aurait donc été de n'en trouver aucun. Un seuil de 0,05 est une promesse portant sur un test. Il dit que si rien ne se passe, vous serez trompé une fois sur vingt. Lancez vingt tests et être trompé une fois est le cas attendu, non l'accident. C'est pourquoi « nous avons regardé par école et deux écoles se détachent » n'est pas un constat. C'est une description de ce que font vingt-quatre tests.Bonferroni : divisez le seuil par le nombre de tests — En Python
BONFERRONI = 0.05 / n_tests print(f"corrected threshold: {BONFERRONI:.5f}") print(f"surviving: {(tests['p'] < BONFERRONI).sum()}")Bonferroni : divisez le seuil par le nombre de tests — En R
p.adjust(tests$p, method = "bonferroni") < 0.05Bonferroni : divisez le seuil par le nombre de tests
- Seuil 0,05/24 = 0,00208. Survivants : aucun — Le plus petit p des vingt-quatre vaut 0,018, un ordre de grandeur plus…
Notes du présentateur
Seuil 0,05/24 = 0,00208. Survivants : aucun. Le plus petit p des vingt-quatre vaut 0,018, un ordre de grandeur plus loin. La correction est grossière — elle suppose le pire cas, que tous les tests sont indépendants et toutes les hypothèses nulles vraies — et elle est grossière du bon côté. C'est le bon choix par défaut quand vous n'avez pas de raison d'en préférer un autre, et elle tient sur une ligne.Benjamini-Hochberg, quand vingt-quatre devient deux cents — En Python
from statsmodels.stats.multitest import multipletests reject, adjusted, _, _ = multipletests(tests["p"], alpha=0.05, method="fdr_bh") print(f"BH survivors: {reject.sum()}")Notes du présentateur
Bonferroni contrôle la probabilité d'un seul faux positif, ce qui est strict. Quand une analyse de dépistage lance des centaines de tests et s'attend à quelques effets réels parmi eux, contrôler la proportion de fausses découvertes est la garantie la plus utile.Benjamini-Hochberg, quand vingt-quatre devient deux cents — En R
p.adjust(tests$p, method = "BH") < 0.05Benjamini-Hochberg, quand vingt-quatre devient deux cents
- Benjamini-Hochberg ne retient lui non plus aucune des vingt-quatre — ce qui est la bonne réponse ici — il n'y a rien à…
- Employez Bonferroni pour une petite famille confirmatoire ; BH pour un large dépistage exploratoire — Les deux tiennent…
Notes du présentateur
Benjamini-Hochberg ne retient lui non plus aucune des vingt-quatre, ce qui est la bonne réponse ici — il n'y a rien à trouver. Son avantage apparaît quand il y a quelque chose : sur deux cents tests dont vingt effets réels, Bonferroni en manquera la plupart et BH non. Employez Bonferroni pour une petite famille confirmatoire ; BH pour un large dépistage exploratoire. Les deux tiennent sur une ligne, et le choix importe beaucoup moins que le fait d'en faire un.La correction ne détruit pas les constats réels — En Python
previous = enrolment[enrolment["school_year"] == 2023].set_index("student_id") clean = clean.assign(last_year=clean["student_id"].map(previous["end_of_year_status"])) returning = clean[clean["last_year"].isin(["repeated", "promoted"])] survivors = [] for school, group in returning.groupby("school_id"): counts = group.groupby("last_year")["over_age"].agg(["sum", "size"]) if len(counts) < 2 or counts["size"].min() < 5: continue stat, p = proportions_ztest(counts["sum"], counts["size"]) survivors.append(p) survivors = np.array(survivors) print(f"{len(survivors)} schools, {(survivors < 0.05).sum()} significant, " f"{(survivors < 0.05 / len(survivors)).sum()} survive Bonferroni")Notes du présentateur
La crainte qui empêche de corriger est que cela enterre quelque chose de vrai. Testez un effet réel de la même façon et regardez ce qui se produit.La correction ne détruit pas les constats réels — En R
# Same loop, different comparison. The generator made this one real.La correction ne détruit pas les constats réels
Comparaison Tests Significatifs à 0,05 Survivent à Bonferroni Retard d'âge selon le sexe (aucun effet réel) 24 2 0 Retard d'âge selon le redoublement de 2023 (réel) 14 14 9 La correction ne détruit pas les constats réels
- L'écart de 94,5 % contre 30,6 % du cours d'éducation survit à la correction dans neuf des quatorze écoles où il peut…
Notes du présentateur
L'écart de 94,5 % contre 30,6 % du cours d'éducation survit à la correction dans neuf des quatorze écoles où il peut être testé. Un écart qui n'existe pas ne survit nulle part. La correction n'est pas un impôt sur les constats ; c'est ce qui les sépare des deux écoles qui allaient de toute façon paraître intéressantes.Comptez les tests, y compris ceux que vous n'avez pas rapportés
- Les sous-groupes regardés puis abandonnés — Découper par district, ne rien voir, et découper par année ensuite fait…
- Les résultats essayés tour à tour — Tester le retard d'âge, puis la présence, puis l'achèvement contre le même…
- Les seuils déplacés — « Retard d'âge » à année + 2 plutôt qu'à année + 1 est un second test de la même idée
- L'analyse que quelqu'un d'autre a lancée sur les mêmes données — Rarement connue, et à demander quand un relecteur…
Notes du présentateur
Le nombre qui entre dans la correction est le nombre de tests que vous avez lancés, non celui que vous avez écrit. Quatre sortes de tests échappent régulièrement au compte : Les sous-groupes regardés puis abandonnés. Découper par district, ne rien voir, et découper par année ensuite fait deux familles de tests, non une. Les résultats essayés tour à tour. Tester le retard d'âge, puis la présence, puis l'achèvement contre le même regroupement fait trois tests. Les seuils déplacés. « Retard d'âge » à année + 2 plutôt qu'à année + 1 est un second test de la même idée. L'analyse que quelqu'un d'autre a lancée sur les mêmes données. Rarement connue, et à demander quand un relecteur arrive avec un résultat frappant sur un sous-groupe.Comptez les tests, y compris ceux que vous n'avez pas rapportés — En Python
plan = { "outcomes": ["over_age"], "groupings": ["sex"], "subgroups": ["school_id"], } n_planned = 1 * 1 * 24 print(f"tests declared in the analysis plan: {n_planned}")Comptez les tests, y compris ceux que vous n'avez pas rapportés — En R
# Write the number down before running anything. It cannot be recovered after.Comptez les tests, y compris ceux que vous n'avez pas rapportés
- Déclarez la famille avant de la lancer — Après coup, le nombre de tests est affaire de mémoire et d'intérêt personnel,…
Notes du présentateur
Déclarez la famille avant de la lancer. Après coup, le nombre de tests est affaire de mémoire et d'intérêt personnel, et les deux le réduisent.Rapportez-le en entier — Exemple
Over-age enrolment by sex, school-level analysis 24 schools tested (both sexes with at least 5 students in grades 1-6). 2 schools significant at p < 0.05: SCH23 (p = 0.018), SCH06 (p = 0.036). With 24 tests, 1.2 significant results are expected by chance alone and the probability of at least one is 71%. Neither school survives the Bonferroni threshold of 0.0021, and neither is reported as a finding. For comparison, over-age by 2023 repetition is significant in all 14 schools testable and survives Bonferroni in 9 of them.Rapportez-le en entier
- La ligne de comparaison est ce qui rend le bloc convaincant plutôt que défensif — Elle montre la même procédure…
Notes du présentateur
La ligne de comparaison est ce qui rend le bloc convaincant plutôt que défensif. Elle montre la même procédure trouvant quelque chose quand il y a quelque chose à trouver, ce qu'un responsable de programme a besoin de voir avant d'accepter que deux écoles frappantes soient du bruit.La suite
- Tous les tests jusqu'ici ont supposé qu'une ligne était une observation indépendante.
Notes du présentateur
Tous les tests jusqu'ici ont supposé qu'une ligne était une observation indépendante. La leçon suivante prend une comparaison où cette hypothèse tombe — une cantine scolaire attribuée par école, testée sur des élèves — et trouve une statistique t presque deux fois plus grande que l'honnête.