cassionAnalyse de données

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.

PythonR150 minStandards SphèreDéfinitions d'indicateurs de l'UNICEFNorme humanitaire fondamentale (CHS)

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.

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.