cassionAnalyse de données

Leçon 6 sur 8

Unité · La cohorte

Un taux de survie qu'il ne faut pas publier

Chaînez les taux de passage d'une année et 19,3 % des enfants atteignent la dernière année. Comptez les redoublants comme retenus et cela fait 40,3 %. Les deux viennent du même fichier, et aucun n'est un taux d'achèvement.

PythonR105 minObjectifs de développement durable (ODD)Définitions d'indicateurs de l'UNICEFEnquête par grappes à indicateurs multiples (MICS)

Le calcul vers lequel tout le monde se tourne

Vous avez les taux de passage par année pour une année scolaire. Chaînez-les et vous obtenez la survie jusqu’à la dernière année — la mesure d’achèvement de type ODD — sans attendre six ans.

import pandas as pd

enrolment = pd.read_csv("school-enrolment-2024.v1.csv")
clean = enrolment[enrolment["age_years"] <= 20]
cohort = clean[clean["school_year"] == 2023]

survival, cumulative = {}, 1.0
for grade in range(1, 7):
    grade_rows = cohort[cohort["grade"] == grade]
    promoted = grade_rows["end_of_year_status"].isin(
        ["promoted", "completed-final-grade"]).mean()
    survival[grade] = cumulative
    cumulative *= promoted
    print(f"grade {grade}: promotion {promoted:.1%}, "
          f"survival to here {survival[grade]:.1%}")

print(f"\nsurvival to grade 6 entry: {cumulative:.1%}")
library(dplyr)

cohort |>
  filter(between(grade, 1, 6)) |>
  summarise(promotion = mean(end_of_year_status %in%
              c("promoted", "completed-final-grade")), .by = grade) |>
  arrange(grade) |>
  mutate(survival = cumprod(lag(promotion, default = 1)))
Année Passage Survie cumulée
1 89,3 % 100,0 %
2 83,0 % 89,3 %
3 71,7 % 74,1 %
4 76,2 % 53,1 %
5 70,9 % 40,5 %
6 67,3 % 28,7 %
— — 19,3 %

19,3 % des enfants entrant en première année atteignent la fin du primaire. C’est un nombre frappant, c’est ce que la méthode produit, et il ne doit pas quitter votre écran.

Pourquoi il est faux

C’est une cohorte reconstruite : elle suppose que le taux de passage de cinquième année observé cette année est celui que les enfants aujourd’hui en première année connaîtront dans quatre ans. Quatre choses cassent cette hypothèse, et ici elles la cassent toutes dans le même sens.

Les redoublants sont comptés comme des échecs. Un enfant qui redouble la deuxième année n’est pas déscolarisé — il est de nouveau en deuxième année. La chaîne traite le non-passage comme une sortie, si bien que 10,1 % de redoublement se composent six fois en une perte apparente énorme.

promotion_or_repeat = cohort[cohort["grade"].between(1, 6)].groupby("grade")[
    "end_of_year_status"].apply(
    lambda s: s.isin(["promoted", "completed-final-grade", "repeated"]).mean())

still_enrolled = 1.0
for grade, rate in promotion_or_repeat.items():
    still_enrolled *= rate
print(f"survival counting repeaters as retained: {still_enrolled:.1%}")
# Retention, not promotion, is what a survival rate needs.

Les transferts sont eux aussi comptés comme des échecs. 2,9 % sont partis dans une autre école et la chaîne les lit comme quittant l’éducation.

Une seule année est prise pour six. Le taux de passage de sixième observé en 2023 appartient à des enfants entrés en 2018, dans d’autres conditions.

Les effectifs par année ne forment pas une cohorte. La première année compte 149 élèves et la sixième 110, et une partie de cet écart est une attrition réelle tandis qu’une autre est une population qui change — une cohorte de naissances plus large arrivant en première année fait ressembler la pyramide à de l’abandon.

Que calculer à la place

La rétention, non le passage, s’il faut employer une seule année. Comptez comme retenus les enfants encore scolarisés — passés ou redoublants.

def chained(statuses):
    rate, product = {}, 1.0
    for grade in range(1, 7):
        rows = cohort[cohort["grade"] == grade]["end_of_year_status"]
        product *= rows.isin(statuses).mean()
        rate[grade] = product
    return product

promotion_only = chained(["promoted", "completed-final-grade"])
retained = chained(["promoted", "completed-final-grade", "repeated"])
print(f"chained promotion: {promotion_only:.1%}")
print(f"chained retention: {retained:.1%}")
# Same chain, different numerator. The gap is repetition compounding.

19,3 % contre 40,3 %. Plus de vingt points de la perte apparente étaient du redoublement lu comme une sortie, et le chiffre de rétention est le plus proche de quelque chose de signifiant — sans être pour autant un taux d’achèvement.

Suivez de vrais élèves quand vous le pouvez. Ce registre a deux années et les mêmes identifiants, si bien qu’une transition d’un an est directement observable plutôt que supposée.

years = clean.pivot_table(index="student_id", columns="school_year",
                          values="grade", aggfunc="first")
both = years.dropna()
print(f"students observed in both years: {len(both)}")
print(f"  advanced a grade: {(both[2024] > both[2023]).mean():.1%}")
print(f"  same grade:       {(both[2024] == both[2023]).mean():.1%}")

1 103 élèves figurent dans les deux années : 88,5 % ont avancé d’une année et 11,5 % sont deux fois dans la même. C’est une transition mesurée et non supposée, et c’est le nombre à mettre dans un rapport.

enrolment |>
  select(student_id, school_year, grade) |>
  tidyr::pivot_wider(names_from = school_year, values_from = grade) |>
  filter(!is.na(`2023`), !is.na(`2024`))

Une transition d’un an observée sur de vrais élèves vaut mieux qu’une chaîne de six ans assemblée à partir des taux d’une seule année, même si elle répond à une question plus petite. La question plus petite est défendable.

Qui part

La désagrégation qui, elle, donne quelque chose, contrairement aux ventilations du retard d’âge de la leçon 2.

year23 = cohort.assign(
    over_age=cohort["age_years"] > cohort["grade"] + 5,
    dropped=cohort["end_of_year_status"].eq("dropped-out"),
)
for column in ("over_age", "disability_reported", "displacement_status", "sex"):
    table = year23.groupby(column, dropna=False)["dropped"].agg(["mean", "size"])
    print((table * [100, 1]).round(1), "\n")
cohort |>
  mutate(dropped = end_of_year_status == "dropped-out") |>
  summarise(dropout = mean(dropped), n = n(), .by = displacement_status)
Ventilation Abandon n
En retard d’âge 19,5 % 478
À l’âge attendu 4,8 % 814
Retournés 19,7 % 71
Handicap signalé 17,9 % 112
Déplacés internes 14,7 % 163
Communauté d’accueil 11,6 % 95
Résidents 8,6 % 940
Garçons 10,4 % 634
Filles 10,0 % 658

Le retard d’âge est le prédicteur le plus fort et le groupe le plus grand. Le handicap et le déplacement doublent à peu près le taux sur des dénominateurs plus petits.

Le sexe ne montre rien — 10,4 % contre 10,0 % sur 634 et 658 élèves. C’est un résultat qui mérite d’être énoncé, car un écart de genre dans l’abandon est le constat qu’un programme éducatif s’attend le plus à voir et ce registre n’en contient pas.

Les données manquantes ne le sont pas au hasard

print(year23["displacement_status"].isna().sum(), "students with no status")
print(year23.loc[year23["displacement_status"].isna(), "dropped"].mean())
cohort |> filter(is.na(displacement_status)) |> nrow()

Le statut de déplacement est vide pour 42 élèves du registre, dont 23 dans la cohorte 2023. Les vides proviennent entièrement de ménages déplacés et retournés — c’est le champ qu’un enquêteur saute quand une famille vient d’arriver.

Soyez précis sur ce que cela biaise et ne biaise pas. Les vides sont retirés à peu près au hasard à l’intérieur de ces deux catégories, donc :

  • le taux d’abandon des déplacés internes et des retournés n’est pas biaisé — 14,7 % et 19,7 % sont les bons nombres pour ces groupes ;
  • leurs effectifs et leur part de population sont sous-estimés d’environ un dixième. 234 élèves sont enregistrés comme déplacés ou retournés en 2023 et le vrai chiffre approche 260.

Donc « les enfants déplacés abandonnent à 14,7 % » est défendable et « les enfants déplacés représentent 18 % des effectifs » ne l’est pas. Un motif de valeurs manquantes peut biaiser un effectif sans biaiser un taux, et savoir sur lequel repose votre phrase décide si le motif compte.

L’abandon observé parmi les vides eux-mêmes vaut 8,7 % sur 23 élèves, ce qui est trop peu pour en lire quoi que ce soit et ne doit pas être rapporté comme une catégorie.

Rapportez-le comme un profil de risque

Dropout, 2023 cohort, 1,292 students

  Overall                      10.2%

  Over-age for grade           19.5%   478 students
  In-age                        4.8%   814

  Returnee                     19.7%    71 students
  Disability reported          17.9%   112
  Internally displaced         14.7%   163
  Resident                      8.6%   940

  No meaningful difference by sex: 10.4% boys, 10.0% girls.

  42 students have no displacement status, all drawn from displaced and
  returnee households. The rates above are unbiased for those groups; their
  counts are understated by about a tenth.

  Not reported: survival to the final grade. A reconstructed cohort from a
  single year gives 19.3% on promotion and 40.3% on retention, neither of
  which is a completion rate. A true cohort needs six years of the register
  or a household survey. The measured one-year transition is 88.5%.

« Non rapporté, et pourquoi » est la ligne la plus utile de ce bloc. Quelqu’un demandera un taux d’achèvement, et la réponse en est une vraie : pas à partir de ceci, à partir d’une enquête ménage, et voici ce que ceci peut dire à la place.

La suite

L’inscription, la présence et la rétention disent si les enfants sont à l’école. Aucune ne dit s’ils apprennent, et la dernière unité porte sur l’instrument qui le dit — où deux passations se révèlent non comparables.

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.