Leçon 3 sur 8
Unité · Inscrit n'est pas présent
88 % de présence, 62 % des élèves
L'assiduité moyenne quotidienne vaut 88,4 %. La part des élèves présents au moins 90 % de leurs jours pointés vaut 62,1 %. Les deux viennent des mêmes 70 245 lignes, et les vingt-six points d'écart sont les enfants qu'une moyenne ne voit pas.
Un registre, deux questions
import pandas as pd
attendance = pd.read_csv("school-attendance-2024.v1.csv")
print(f"{len(attendance):,} rows, {attendance['student_id'].nunique():,} students")
print(attendance["present"].value_counts(dropna=False))
library(dplyr)
attendance |> count(present)
La colonne present a cinq valeurs, non deux. SCH09 a saisi Y et N au lieu
de true et false sur 873 de ses lignes, et 271 jours n’ont jamais été pointés
dans un sens ni dans l’autre. Une conversion booléenne transforme ces 873
observations réelles en valeurs manquantes et retire discrètement une école du
dénominateur.
MARKS = {"true": True, "Y": True, "false": False, "N": False}
marked = attendance[attendance["present"].isin(MARKS)].copy()
marked["attended"] = marked["present"].map(MARKS)
print(f"usable marks: {len(marked):,} of {len(attendance):,}")
attendance |>
mutate(attended = case_when(present %in% c("true", "Y") ~ TRUE,
present %in% c("false", "N") ~ FALSE)) |>
filter(!is.na(attended))
Le même fichier répond maintenant à deux questions différentes.
L’assiduité moyenne quotidienne
ada = marked["attended"].mean()
print(f"average daily attendance: {ada:.1%}")
marked |> summarise(ada = mean(attended))
88,4 %. Chaque pointage compte une fois, si bien qu’un élève présent 60 jours sur 60 et un élève présent 30 sur 60 contribuent à proportion de leur nombre de pointages.
C’est le nombre qu’un ministère rapporte et ce qu’un indicateur de système doit être. Il répond à à quel point la classe est remplie un jour moyen, ce qui est la bonne question pour les effectifs, la cantine et les manuels.
La proportion d’élèves assidus
by_student = marked.groupby("student_id")["attended"].agg(["mean", "size"])
eligible = by_student[by_student["size"] >= 20]
for threshold in (0.80, 0.85, 0.90):
share = (eligible["mean"] >= threshold).mean()
print(f"attending at least {threshold:.0%} of marked days: {share:.1%}")
marked |>
summarise(rate = mean(attended), days = n(), .by = student_id) |>
filter(days >= 20) |>
summarise(across(everything(), ~ mean(rate >= 0.9)))
| Seuil | Élèves l’atteignant |
|---|---|
| Au moins 80 % des jours | 87,3 % |
| Au moins 85 % | 78,9 % |
| Au moins 90 % | 62,1 % |
62,1 % au seuil de 90 %, contre une assiduité moyenne quotidienne de 88,4 %. Les deux nombres sont écartés de vingt-six points et aucun n’est faux.
Une moyenne sur des pointages décrit le système ; une proportion sur des élèves décrit des enfants. Une école où chaque enfant manque un jour sur huit et une école où sept enfants sur huit sont présents parfaitement tandis qu’un ne vient jamais ont la même assiduité moyenne quotidienne et des problèmes entièrement différents.
Sur lequel un programme est jugé
Rapportez les deux, et mettez en tête celui qui correspond à la décision.
| La décision | Le nombre |
|---|---|
| Combien de repas, de pupitres, de manuels | Assiduité moyenne quotidienne |
| Quels enfants ont besoin d’une visite de suivi | Proportion sous le seuil |
| Si une intervention a marché | Les deux, car ils peuvent bouger en sens opposés |
Cette dernière ligne est celle qu’il faut intérioriser. Une intervention qui fait passer les plus mauvais assidus de 40 % à 60 % ne déplace presque pas l’assiduité moyenne quotidienne et ne déplace pas du tout la proportion au-dessus de 90 % — et serait enregistrée comme un échec par l’un ou l’autre nombre pris seul.
struggling = eligible[eligible["mean"] < 0.75]
print(f"students below 75%: {len(struggling)} "
f"({len(struggling) / len(eligible):.1%})")
print(f"their marks as a share of all marks: "
f"{struggling['size'].sum() / eligible['size'].sum():.1%}")
# The students furthest behind are a small share of the rows and the whole
# of the problem.
Énoncez le seuil, et d’où il vient
Le seuil de 90 % est une convention, non un standard, et il fait l’essentiel du travail dans ces 62,1 %. Portez-le à 85 % et le chiffre devient 78,9 %.
sensitivity = {f"{t:.0%}": f"{(eligible['mean'] >= t).mean():.1%}"
for t in (0.75, 0.80, 0.85, 0.90, 0.95)}
print(sensitivity)
# Print the curve, not the point.
À 95 % il vaut 39,8 % et à 75 % il vaut 90,5 %. Publiez le seuil à côté du nombre, à chaque fois. Deux rapports citant « la proportion d’élèves assidus » à des seuils différents produisent des chiffres incomparables qui ont tous deux l’air officiel — le même défaut que les deux jeux de seuils du score de consommation alimentaire, dans un autre secteur.
La décision de dénominateur que personne ne prend explicitement
by_student ci-dessus a été filtré aux élèves ayant au moins vingt jours pointés.
C’est un choix et il doit être déclaré.
Sans lui, un élève inscrit la dernière semaine du trimestre et présent trois jours sur trois apparaît comme assidu à 100 %, et un élève pointé deux fois apparaît dans la queue de distribution.
Avec lui, vous avez exclu précisément les élèves arrivés tard et partis tôt, qui sont ceux qu’un programme d’assiduité veut le plus voir.
print(f"students with any marks: {len(by_student)}")
print(f"students with 20 or more: {len(eligible)}")
# Two denominators, both defensible, and the report says which.
Ici les deux sont presque identiques, si bien que le choix ne déplace pas la réponse. Dans un registre couvrant une année entière il la déplacerait beaucoup, et l’habitude de le déclarer est ce qui rend le chiffre transportable.
Rapportez la paire
Attendance, February to April 2024
Average daily attendance 88.4% 69,974 usable marks
Students attending 90%+ of days 62.1% 1,200 students with 20+ marks
Students attending 85%+ 78.9%
Students below 75% 9.5% 114 students, the follow-up list
SCH09 recorded 873 marks as Y/N rather than true/false; recoded, not
dropped. 271 days were never marked and are excluded from both figures.
La suite
Les deux nombres de cette leçon divisent par des jours qui ont été pointés. La leçon suivante porte sur les jours qui ne sont pas du tout dans le fichier — quinze, dans deux écoles, qui transforment une grève en urgence d’abandon si on les laisse faire.