cassionAnalyse de données

Leçon 3 sur 8

Unité · Comparer deux groupes

Deux écarts, deux réponses

Un écart de 5,5 points avec un intervalle de −0,4 à +11,3, et un écart de 19,4 points avec un intervalle de −26,1 à −12,8. Même test, même code, conclusions opposées — et le module 4 avait laissé les deux ouverts exprès.

PythonR180 minEnquête SMARTDéfinitions d'indicateurs de l'UNICEFCritères d'évaluation du CAD de l'OCDE

Les deux questions que le module 4 a reportées

Le cours d’éducation a trouvé les garçons plus souvent en retard d’âge que les filles et a refusé d’en faire un constat. Le cours de protection a trouvé un écart d’aboutissement selon le handicap et l’a qualifié de résultat le plus important du jeu de données. Les deux disaient « calculez d’abord l’intervalle ». Voici ce calcul.

import pandas as pd
import numpy as np
from statsmodels.stats.proportion import proportions_ztest, confint_proportions_2indep

enrolment = pd.read_csv("school-enrolment-2024.v1.csv")
clean = enrolment[(enrolment["age_years"] <= 20)
                  & (enrolment["school_year"] == 2024)
                  & enrolment["grade"].between(1, 6)]
clean = clean.assign(over_age=clean["age_years"] > clean["grade"] + 5)

boys = clean[clean["sex"] == "m"]["over_age"]
girls = clean[clean["sex"] == "f"]["over_age"]
print(f"boys  {boys.sum()}/{len(boys)} = {boys.mean():.1%}")
print(f"girls {girls.sum()}/{len(girls)} = {girls.mean():.1%}")
library(dplyr)

enrolment |>
  filter(age_years <= 20, school_year == 2024, between(grade, 1, 6)) |>
  mutate(over_age = age_years > grade + 5) |>
  summarise(k = sum(over_age), n = n(), .by = sex)

Écart numéro un : celui qui n’y est pas

Groupe Retard d’âge n Intervalle à 95 %
Garçons 39,2 % 510 35,1–43,5 %
Filles 33,8 % 542 29,9–37,8 %

Les deux intervalles se recouvrent. Un recouvrement d’intervalles est un indice et non un test — ce qu’il faut calculer, c’est un intervalle sur la différence.

count = np.array([boys.sum(), girls.sum()])
nobs = np.array([len(boys), len(girls)])

stat, pvalue = proportions_ztest(count, nobs)
low, high = confint_proportions_2indep(count[0], nobs[0], count[1], nobs[1])
print(f"difference {boys.mean() - girls.mean():+.1%}")
print(f"95% CI [{low:+.1%}, {high:+.1%}]")
print(f"z = {stat:.2f}, p = {pvalue:.3f}")
prop.test(c(200, 183), c(510, 542))

Différence +5,5 points, IC à 95 % −0,4 à +11,3, p = 0,066.

L’intervalle contient zéro. La réponse honnête n’est donc pas « il n’y a pas d’écart » — c’est « cette enquête ne peut pas vous dire s’il y a un écart, et s’il y en a un il se situe quelque part entre des filles légèrement plus mal loties et des garçons plus mal lotis de onze points ».

C’est une phrase réellement utile et ce n’est pas la même chose qu’un résultat nul.

Écart numéro deux : celui qui y est

protection = pd.read_csv("protection-referrals-2024.v1.csv")
consenting = protection[protection["consent_to_refer"]]
reached = (consenting["referral_accepted"]
           & consenting["days_to_first_service"].notna())

disability = consenting["disability_reported"].isin([True, "true", "Yes"])
k = np.array([reached[disability].sum(), reached[~disability].sum()])
n = np.array([disability.sum(), (~disability).sum()])

stat, pvalue = proportions_ztest(k, n)
low, high = confint_proportions_2indep(k[0], n[0], k[1], n[1])
print(f"{k[0]}/{n[0]} = {k[0]/n[0]:.1%}   {k[1]}/{n[1]} = {k[1]/n[1]:.1%}")
print(f"difference {k[0]/n[0] - k[1]/n[1]:+.1%}, CI [{low:+.1%}, {high:+.1%}]")
print(f"z = {stat:.2f}, p = {pvalue:.2e}")
prop.test(c(54, 663), c(202, 1436))
Groupe Aboutissement n
Handicap signalé 26,7 % 202
Non signalé 46,2 % 1 436

Différence −19,4 points, IC à 95 % −26,1 à −12,8, p < 0,001.

Toute valeur de cet intervalle est un écart substantiel. Le plus petit écart compatible avec les données vaut 12,8 points, ce qui reste assez grand pour agir. C’est ce qui en fait un constat plutôt qu’un indice.

Les deux résultats côte à côte

results = pd.DataFrame([
    {"comparison": "over-age, boys vs girls", "diff": 5.5,
     "ci": "-0.4 to +11.3", "n": "510 vs 542", "p": 0.066},
    {"comparison": "completion, disability", "diff": -19.4,
     "ci": "-26.1 to -12.8", "n": "202 vs 1436", "p": 0.0000002},
])
print(results)
# Two rows. The conclusion column is the analyst's, not the test's.

Remarquez que le résultat significatif a le plus petit échantillon dans l’un des bras. 202 cas ont produit une réponse tranchée et 510 élèves non, parce que la taille d’effet est presque quatre fois plus grande. Taille d’échantillon et taille d’effet s’échangent, et c’est leur combinaison qui décide si une comparaison se tranche.

Choisir le test

Trois tests couvrent presque tout ce qu’un rapport de programme compare, et le choix se fait d’après la nature du résultat plutôt que d’après ce qui paraît sophistiqué.

Comparaison Test Dans ce cours
Deux proportions Test z à deux échantillons, ou prop.test Les deux écarts ci-dessus
Deux moyennes Test t de Welch Présence et cantine, leçon 6
Une variable catégorielle contre une autre Khi-deux Motif de fermeture par district
from scipy import stats

points = pd.read_csv("water-point-monitoring-2024.v1.csv")
table = pd.crosstab(points["admin2"], points["functional_status"])
chi2, p, dof, expected = stats.chi2_contingency(table)
print(f"chi-square {chi2:.1f}, dof {dof}, p = {p:.4f}")
print(f"smallest expected cell: {expected.min():.1f}")
chisq.test(table(points$admin2, points$functional_status))

Vérifiez les hypothèses, et dites que vous l’avez fait

Chaque test suppose des choses, et deux d’entre elles se vérifient en une ligne.

Indépendance. Tous les tests ci-dessus supposent que chaque ligne est une observation indépendante. C’est l’hypothèse la plus souvent violée et la plus difficile à voir — la leçon 6 porte entièrement sur un cas où elle tombe.

Effectifs attendus pour le khi-deux. Le test devient peu fiable quand une cellule attendue passe sous cinq environ. Affichez expected.min() à chaque fois ; s’il est petit, regroupez des catégories ou employez le test exact de Fisher.

Égalité des variances pour le test t. Ne la vérifiez pas — employez toujours le test t de Welch, qui ne la suppose pas. scipy.stats.ttest_ind(..., equal_var=False) et le comportement par défaut de t.test en R. La version qui suppose des variances égales n’apporte rien et tombe dès que les groupes diffèrent par leur dispersion.

girls_rate, boys_rate = girls.mean(), boys.mean()
print("assumptions checked:")
print(f"  independence: one row per student, no student in two rows — verified")
print(f"  sample sizes: {len(boys)} and {len(girls)}, both well above 30")
print(f"  expected counts: smallest is {min(len(boys), len(girls)) * min(girls_rate, 1 - boys_rate):.0f}")
# Write the check into the script, not into your memory of having done it.

Rapportez la différence, non les deux nombres

Over-age enrolment by sex, primary, 2024

  Boys    39.2%   n = 510
  Girls   33.8%   n = 542
  Difference +5.5 points, 95% CI -0.4 to +11.3, p = 0.066

  The interval includes zero. This survey cannot establish whether over-age
  enrolment differs by sex; if it does, the gap is between 0 and 11 points
  and boys are the disadvantaged group. Not reported as a finding.

Referral completion by disability status, 1,638 consenting cases

  Disability reported     26.7%   n = 202
  Not reported            46.2%   n = 1,436
  Difference -19.4 points, 95% CI -26.1 to -12.8, p < 0.001

  The smallest gap consistent with the data is 12.8 points. Reported as a
  finding, and the pathway lesson locates it at referral-making and
  acceptance rather than at consent.

Les deux blocs mettent la différence et son intervalle en titre, avec les deux valeurs de groupe en dessous. Cet ordre est délibéré : la différence est l’affirmation, et les deux proportions en sont la preuve.

La suite

L’un de ces écarts est statistiquement significatif. La leçon suivante demande si c’est la même chose qu’être important — et trouve une comparaison où un p inférieur à 0,001 décrit une différence sur laquelle aucun programme n’agirait.

Animer cette leçon

La leçon en diaporama, la prose étant reléguée dans les notes du présentateur plutôt que projetée. Produit à partir de cette page, dont il ne peut donc pas s'écarter.

Lancer le diaporamaLire les diapositives

Le PDF ne requiert aucun logiciel et se projette depuis n'importe quel poste. Le fichier PowerPoint est fait pour être modifié : appliquez la charte de votre organisation, retirez une section pour une séance plus courte, ou fusionnez deux leçons en atelier.