Leçon 7 sur 8
Unité · Les apprentissages
Huit points de progrès, dont deux que personne n'a gagnés
La lecture passe de 53,1 % à 61,8 % des items entre les deux passations. Sur les 585 élèves ayant passé les deux, elle passe de 56,8 % à 63,7 %. L'écart tient à qui s'est présenté, et les scores bruts n'auraient pu donner ni l'un ni l'autre.
La comparaison qui ne peut pas être faite
import pandas as pd
assessment = pd.read_csv("learning-assessment-2024.v1.csv")
literacy = assessment[assessment["domain"] == "literacy"]
print(literacy.groupby("assessment_round").agg(
students=("student_id", "nunique"),
items=("items", "first"),
mean_raw=("raw_score", "mean"),
).round(1))
library(dplyr)
assessment |>
filter(domain == "literacy") |>
summarise(students = n_distinct(student_id), items = first(items),
mean_raw = mean(raw_score), .by = assessment_round)
| Passation | Élèves | Items | Score brut moyen |
|---|---|---|---|
| Initiale | 741 | 40 | 21,2 |
| Finale | 658 | 50 | 30,9 |
Un score brut est passé de 21,2 à 30,9 et cette comparaison ne signifie rien, car les instruments n’ont pas la même longueur. Neuf points de la hausse sont dix questions de plus.
C’est l’erreur la plus courante du rapportage des évaluations éducatives, et elle
est invisible tant qu’on ne regarde pas la colonne items — que la plupart des
extraits ne portent pas et que la plupart des analystes ne demandent pas.
Trois choses comparables, et une qui ne l’est pas
Non comparable : le score brut. Dénominateurs différents.
Comparable avec prudence : la proportion d’items réussis. Elle met les deux passations sur une échelle de 0 à 1, ce qui est nécessaire et non suffisant — les items de la passation finale peuvent être plus ou moins difficiles, et une proportion suppose qu’ils sont équivalents.
literacy = literacy.assign(proportion=literacy["raw_score"] / literacy["items"])
print(literacy.groupby("assessment_round")["proportion"].mean().round(3))
assessment |> filter(domain == "literacy") |>
summarise(proportion = mean(raw_score / items), .by = assessment_round)
53,1 % au départ et 61,8 % à la fin. Mieux, et reposant encore sur une hypothèse que personne n’a vérifiée.
Comparable par construction : les bandes de compétence. Elles ont été équatées lors de la conception des instruments, ce qui est l’objet même de l’équatage — une procédure psychométrique qui place deux tests différents sur une seule échelle, et c’est la raison pour laquelle les bandes existent comme colonne distincte plutôt que dérivées du score.
bands = pd.crosstab(assessment["assessment_round"],
assessment["proficiency_band"], normalize="index")
order = ["below-minimum", "minimum", "proficient", "advanced"]
print((bands[order] * 100).round(1))
assessment |> count(assessment_round, proficiency_band) |>
mutate(share = n / sum(n), .by = assessment_round)
| Bande | Initiale | Finale |
|---|---|---|
| Sous le minimum | 19,3 % | 11,1 % |
| Minimum | 30,6 % | 23,9 % |
| Compétent | 38,2 % | 38,9 % |
| Avancé | 11,9 % | 26,1 % |
La bande sous le minimum passe de 19,3 % à 11,1 % et la bande avancée de 11,9 % à 26,1 %. C’est la comparaison à rapporter, car c’est la seule dont l’échelle a été conçue pour survivre à un changement d’instrument.
Qui a passé l’épreuve
sat = assessment.groupby("assessment_round")["student_id"].apply(set)
baseline, endline = sat["baseline"], sat["endline"]
print(f"baseline only: {len(baseline - endline)}")
print(f"endline only: {len(endline - baseline)}")
print(f"both rounds: {len(baseline & endline)}")
# Three groups, and only one of them supports a change estimate.
741 ont passé l’initiale, 658 la finale, et 585 les deux. 156 élèves ayant passé le premier test n’ont pas passé le second.
Ce ne sont pas 156 élèves au hasard. Le jour de l’évaluation attrape qui se trouve en classe, et les enfants qui n’y sont pas sont ceux qui fréquentent le moins — qui sont aussi, d’après la leçon 3, ceux qui obtiennent les scores les plus bas.
La moyenne finale est donc calculée sur un groupe dont les plus faibles ont été retirés de façon disproportionnée, et elle montera pour cette seule raison.
Mesurez la sélection au lieu de l’écarter par hypothèse
Le registre permet de faire ce qui tranche : calculer l’évolution sur les élèves présents aux deux passations.
panel = literacy.pivot_table(index="student_id", columns="assessment_round",
values="proportion")
panel = panel.dropna()
print(f"panel of {len(panel)} students")
print(f" baseline {panel['baseline'].mean():.1%} → endline {panel['endline'].mean():.1%}")
all_comers = literacy.groupby("assessment_round")["proportion"].mean()
print(f"all comers")
print(f" baseline {all_comers['baseline']:.1%} → endline {all_comers['endline']:.1%}")
assessment |> filter(domain == "literacy") |>
mutate(proportion = raw_score / items) |>
select(student_id, assessment_round, proportion) |>
tidyr::pivot_wider(names_from = assessment_round, values_from = proportion) |>
filter(!is.na(baseline), !is.na(endline)) |>
summarise(n = n(), baseline = mean(baseline), endline = mean(endline))
| Initiale | Finale | Évolution | |
|---|---|---|---|
| Tous présents | 53,1 % | 61,8 % | +8,7 points |
| Panel de 585 | 56,8 % | 63,7 % | +6,9 points |
Deux des 8,7 points tiennent à qui s’est présenté. La valeur initiale du panel dépasse de 3,7 points celle de tous les présents, ce qui est la mesure directe de la sélection : les élèves revenus étaient déjà plus forts.
Rapportez l’estimation du panel et montrez le chiffre tous-présents à côté. Le panel est le nombre défendable ; l’écart entre les deux est la preuve que la sélection était réelle et son ampleur.
Ce que coûte le panel
Il n’est pas gratuit, et l’arbitrage doit être énoncé.
Le panel n’est pas la population. 585 élèves sur 741 à l’initiale, et ce sont les plus assidus. Les +6,9 points sont donc une estimation non biaisée de l’évolution pour les enfants restés scolarisés et présents deux fois, ce qui est une affirmation plus étroite que celle que l’on attend.
Il ne dit rien des 156 partis. Leurs apprentissages ont pu aller n’importe où, et le rapport honnête dit que l’estimation ne les couvre pas.
left = literacy[literacy["student_id"].isin(baseline - endline)
& (literacy["assessment_round"] == "baseline")]
print(f"the 156 who did not return scored "
f"{left['proportion'].mean():.1%} at baseline")
print(f"the 585 who did scored "
f"{panel['baseline'].mean():.1%}")
# Quantify the difference rather than asserting it.
Les 156 qui ne sont pas revenus obtenaient 39,3 % à l’initiale contre 56,8 % pour le panel — dix-sept points en dessous. Ce n’est pas un effet de sélection subtil ; c’est le sixième le plus faible de la cohorte qui sort de la moyenne finale.
Chiffrer la sélection est le livrable, non l’éliminer. Une analyse qui dit « l’échantillon final était plus assidu, et voici de combien » a fait son travail ; une analyse qui rapporte +8,7 points ne l’a pas fait.
Rapportez les trois nombres
Literacy, baseline to endline
Proficiency bands, all who sat each round
Below minimum 19.3% → 11.1%
Advanced 11.9% → 26.1%
Proportion of items correct
All comers 53.1% → 61.8% +8.7 points n=741, n=658
Panel of both rounds 56.8% → 63.7% +6.9 points n=585
Instruments differed: 40 items at baseline, 50 at endline. Raw scores are
not comparable and are not reported. Proficiency bands were equated at
design; the proportion of items assumes equivalent difficulty.
156 baseline students did not sit the endline. They scored 39.3% at
baseline against 56.8% for those who returned, so the all-comers change
overstates learning by about 1.8 points. The panel estimate covers
children who sat both rounds and does not cover those 156.
La suite
Vous disposez désormais de tous les indicateurs éducatifs que ce cours peut produire — inscription, présence, rétention et apprentissages — chacun avec son dénominateur et sa réserve. La dernière leçon porte sur le rapport où ils vont, et sur les quatre nombres sur lesquels un directeur d’école peut réellement agir.