Leçon 7 sur 8
Unité · Juger un programme
Le dénominateur qui déplace le taux de guérison de neuf points
Guéris, abandons, décès, non-réponse — au regard des standards minimums Sphere, sur le dénominateur que Sphere spécifie réellement. 82,3 % ou 73,6 %, selon un choix que personne ne documente, et un site hors standard que le chiffre du district masque.
Quatre issues et un standard
Un programme PCIMA est jugé sur ce qu’il est advenu des enfants qu’il a admis, et les standards minimums Sphere donnent les seuils.
| Issue | Minimum Sphere, soins thérapeutiques ambulatoires |
|---|---|
| Guéris | plus de 75 % |
| Décès | moins de 10 % |
| Abandons | moins de 15 % |
| Non-réponse | pas de seuil fixe ; investiguée si élevée |
L’alimentation supplémentaire a son propre jeu — guéris au-dessus de 75 %, décès sous 3 %, abandons sous 15 %. Les nombres diffèrent ; la structure non.
Chacun est une proportion, et la discussion porte toujours sur le dénominateur.
Les trois dénominateurs candidats
import pandas as pd
cmam = pd.read_csv("cmam-admissions-2024.v1.csv")
print(f"admissions: {len(cmam):>5}")
print(f"with an outcome: {cmam['outcome'].notna().sum():>5}")
print(f"excluding transfers: "
f"{(cmam['outcome'].notna() & (cmam['outcome'] != 'transferred')).sum():>5}")
cmam |> summarise(
admissions = n(),
with_outcome = sum(!is.na(outcome)),
excluding_transfers = sum(!is.na(outcome) & outcome != "transferred")
)
| Dénominateur | n | Ce qu’il fait des exclus |
|---|---|---|
| Toutes les admissions | 1 100 | Les enfants encore en traitement comptés comme non guéris |
| Ayant atteint une issue | 1 029 | Les transferts comptés comme une issue |
| Ayant atteint une issue, transferts exclus | 984 | — |
cured = (cmam["outcome"] == "cured").sum()
for label, n in [("all admissions", 1100), ("with an outcome", 1029),
("excluding transfers", 984)]:
print(f"cure rate on {label:22} {cured / n:.1%}")
cured <- sum(cmam$outcome == "cured", na.rm = TRUE)
c(all = cured / 1100, outcome = cured / 1029, sphere = cured / 984)
73,6 %, 78,7 %, 82,3 %. Un numérateur, trois dénominateurs, neuf points.
Et l’un d’eux franchit un seuil : sur toutes les admissions le programme est sous le minimum Sphere de 75 % et sur le dénominateur Sphere il est confortablement au-dessus. La différence n’est pas la performance. C’est un dénominateur que personne n’a écrit.
Celui que Sphere désigne
Les enfants ayant atteint une issue thérapeutique, transferts exclus. 984 ici.
Les deux exclusions ont des motifs différents et les deux sont justes.
Les enfants encore en traitement ne sont pas une issue. Soixante et onze enfants ont été admis assez tard pour ne pas être sortis à la date d’arrêt. Ils ne sont ni guéris ni abandons ; ils sont en traitement. Les compter dans une catégorie quelconque est une affirmation sur un avenir qui n’a pas eu lieu.
Les transferts sont l’issue de quelqu’un d’autre. Quarante-cinq enfants sont passés d’un programme à l’autre — ambulatoire vers hospitalier, ou l’inverse — et leur issue thérapeutique sera enregistrée là où ils aboutissent. Les compter ici reviendrait à les compter deux fois entre les deux programmes, ou à créditer ce programme d’un résultat qu’il n’a pas produit.
performance = cmam[cmam["outcome"].notna() & (cmam["outcome"] != "transferred")]
rates = performance["outcome"].value_counts(normalize=True)
print((rates * 100).round(1))
print(f"n = {len(performance)}")
performance <- cmam |> filter(!is.na(outcome), outcome != "transferred")
performance |> count(outcome) |> mutate(rate = n / sum(n))
| Issue | Taux | Sphere | |
|---|---|---|---|
| Guéris | 82,3 % | plus de 75 % | passe |
| Abandons | 13,7 % | moins de 15 % | passe |
| Décès | 1,0 % | moins de 10 % | passe |
| Non-réponse | 2,9 % | — |
Quatre lignes, toutes dans le standard. C’est là que s’arrêtent la plupart des rapports PCIMA, et c’est là que le constat se cache.
Décomposez par site
by_site = (
performance.assign(**{o: performance["outcome"] == o
for o in ["cured", "defaulted", "died", "non-response"]})
.groupby("site_id")
.agg(n=("outcome", "size"), cured=("cured", "mean"),
defaulted=("defaulted", "mean"), died=("died", "mean"))
)
print((by_site[["cured", "defaulted", "died"]] * 100).round(1))
performance |>
summarise(n = n(),
cured = mean(outcome == "cured"),
defaulted = mean(outcome == "defaulted"),
died = mean(outcome == "died"),
.by = site_id) |>
arrange(desc(defaulted))
| Site | n | Abandons |
|---|---|---|
| SITE-03 | 156 | 20,5 % |
| SITE-02 | 168 | 15,5 % |
| SITE-06 | 125 | 13,6 % |
| SITE-05 | 142 | 11,3 % |
| SITE-01 | 188 | 11,2 % |
| SITE-04 | 205 | 11,2 % |
Le programme abandonne à 13,7 % et un site à 20,5 %. Le chiffre du district est sous le maximum Sphere et ce site ne l’est pas, de cinq points.
C’est toute la raison pour laquelle le cours d’évaluation insistait sur les distributions plutôt que sur les moyennes, et cela arrive ici avec une conséquence clinique — un enfant qui abandonne est un enfant qui a cessé le traitement avant guérison.
flagged = by_site[(by_site["defaulted"] > 0.15) | (by_site["cured"] < 0.75)]
print(f"{len(flagged)} of {len(by_site)} sites outside a Sphere standard")
performance |>
summarise(cured = mean(outcome == "cured"),
defaulted = mean(outcome == "defaulted"), .by = site_id) |>
filter(defaulted > 0.15 | cured < 0.75)
Deux sites, et le second est limite à 15,5 %. Signalez par rapport au standard, non par rapport à la moyenne du district — c’est au standard que le programme est tenu.
La cause est en général la distance, non l’observance
L’abandon a une littérature et elle est constante : le meilleur prédicteur est le temps de trajet jusqu’au site. Un accompagnant qui doit perdre une journée de travail chaque semaine pour marcher deux heures cessera de venir quand l’enfant aura meilleure mine, ce qui est rationnel et n’est pas de l’inobservance.
length_of_stay = (
pd.to_datetime(performance["discharge_date"])
- pd.to_datetime(performance["admission_date"])
).dt.days
print(performance.assign(stay=length_of_stay)
.groupby("outcome")["stay"].median().round(0))
performance |>
mutate(stay = as.integer(as.Date(discharge_date) - as.Date(admission_date))) |>
summarise(median_stay = median(stay), .by = outcome)
Les abandons partent bien plus tôt que les enfants guéris — le séjour médian est d’environ trois semaines contre environ huit. Un abandon n’est pas un échec thérapeutique, c’est un enfant parti avant la fin du traitement, et l’action corrective est la décentralisation ou un appui au transport, non du counseling.
C’est la discipline de cause racine du cours d’évaluation, appliquée à un indicateur clinique.
Le gain de poids, et les saisies qui ne peuvent pas être justes
gain = (
(performance["weight_discharge_kg"] - performance["weight_admission_kg"])
/ performance["weight_admission_kg"]
/ length_of_stay * 1000
)
print(gain.describe().round(1))
print(f"{(gain < 0).sum()} discharges with weight loss")
performance |>
mutate(gain = (weight_discharge_kg - weight_admission_kg) /
weight_admission_kg / as.integer(as.Date(discharge_date) -
as.Date(admission_date)) * 1000) |>
summarise(median = median(gain, na.rm = TRUE), negative = sum(gain < 0, na.rm = TRUE))
Le gain de poids en grammes par kilogramme et par jour est la mesure classique de la réponse au traitement, et neuf sorties présentent une perte de poids. C’est possible chez un enfant décédé ou ayant abandonné tôt, et c’est aussi exactement l’allure d’une saisie inversée. Le registre ne permet pas de les distinguer, ce qui est un constat pour la section qualité des données plutôt qu’un nombre à nettoyer.
Le tableau de performance à publier
CMAM performance, 2024
Admissions 1,100
Still in treatment at cut-off 71 excluded: not an outcome
Transferred 45 excluded: outcome recorded elsewhere
Performance denominator 984
Cured 82.3% Sphere >75% pass
Defaulted 13.7% Sphere <15% pass
Died 1.0% Sphere <10% pass
Non-response 2.9%
One site (SITE-03, n=156) defaults at 20.5%, outside the Sphere maximum.
Median stay for defaulters is 24 days against 58 for cured children,
consistent with distance rather than non-response to treatment.
Les lignes exclues sont montrées, non supprimées. Un lecteur peut reconstituer n’importe lequel des trois dénominateurs à partir de ce bloc, ce qui fait la différence entre un tableau de performance et une affirmation.
La suite
Le programme guérit les enfants qu’il admet. La dernière leçon pose la question plus difficile — quelle part des enfants ayant besoin d’un traitement il a jamais vue — et pourquoi le chiffre que la plupart des programmes rapportent comme couverture n’en est pas une.