cassionAnalyse de données

Leçon 5 sur 8

Unité · Repérer les sites qui méritent une visite

Comparer une formation sanitaire à son propre passé

Rapport à la médiane glissante, le problème des petits effectifs qui rend les plus grandes aberrations insignifiantes, et la série si stable qu'elle mérite un second regard.

PythonR75 minDéfinitions d'indicateurs de l'UNICEF

La cohérence est une comparaison, et vous en choisissez le terme

La dimension de cohérence demande si les nombres se tiennent. En pratique cela revient à comparer chaque valeur rapportée à quelque chose, et il y a deux candidats.

À ses pairs. La formation A a rapporté 40 doses ; la médiane du district est de 25. Tentant et généralement faux — les formations diffèrent énormément par la taille de leur bassin, et une grande formation sera signalée chaque mois pour cause de grandeur.

À son propre passé. La formation A a rapporté 40 doses ce mois-ci, contre sa propre médiane annuelle de 25. Voilà une comparaison qui vaut la peine, car une formation est un témoin raisonnable d’elle-même.

Employez la seconde. Tous les contrôles de cette leçon confrontent une formation à son propre historique.

Rapport à la médiane

La statistique utile la plus simple, et elle résiste à la brièveté de ces séries.

import pandas as pd

reported = vax[vax["reported"]].copy()

reported["own_median"] = reported.groupby(["facility_id", "antigen"])[
    "doses_administered"
].transform("median")
reported["ratio"] = reported["doses_administered"] / reported["own_median"]

print(reported.nlargest(5, "ratio")[
    ["facility_id", "antigen", "period", "doses_administered", "own_median", "ratio"]
])
library(dplyr)

reported <- vax |>
  filter(report_submitted) |>
  mutate(own_median = median(doses_administered),
         ratio = doses_administered / own_median,
         .by = c(facility_id, antigen))

reported |> slice_max(ratio, n = 5)

La médiane, pas la moyenne. La moyenne est tirée par l’aberration même que vous cherchez, si bien qu’une valeur peut gonfler sa propre base de comparaison et se dissimuler. C’est la raison pour laquelle les contrôles de plausibilité SMART emploient des statistiques robustes.

Là où la série est assez longue, préférez une médiane glissante, pour qu’un véritable changement de niveau saisonnier ne signale pas tous les mois suivants.

reported = reported.sort_values(["facility_id", "antigen", "period"])
reported["rolling_median"] = (
    reported.groupby(["facility_id", "antigen"])["doses_administered"]
    .transform(lambda s: s.rolling(6, min_periods=3, center=True).median())
)
reported <- reported |>
  arrange(facility_id, antigen, period) |>
  mutate(rolling_median = zoo::rollapply(doses_administered, 6, median,
                                         partial = TRUE, align = "center"),
         .by = c(facility_id, antigen))

Le problème des petits effectifs

Exécutez le contrôle de rapport sur cette extraction et voici le haut de la liste.

Formation Antigène Mois Valeur Médiane propre Rapport
FAC013 mcv2 décembre 5 3 1,67
FAC037 opv3 septembre 13 8 1,62
FAC030 mcv2 octobre 8 5 1,60

La plus grande aberration de tout le district est une formation qui a administré cinq secondes doses de vaccin antirougeoleux au lieu de trois. Deux doses. Personne n’ira enquêter là-dessus, et si votre rapport d’évaluation ouvre là-dessus, personne ne lira la suite.

C’est la défaillance caractéristique du signalement par rapport, et elle est garantie plutôt que malchanceuse — un rapport a un petit dénominateur en bas de la plage, donc les rapports les plus extrêmes proviennent toujours des plus petits effectifs. Le contrôle fait exactement ce qu’on lui a demandé et c’est la question qui était mauvaise.

Adjoignez un plancher absolu au rapport

RATIO_HIGH, RATIO_LOW, MIN_ABSOLUTE = 2.0, 0.5, 10

reported["difference"] = reported["doses_administered"] - reported["own_median"]
reported["flag"] = (
    ((reported["ratio"] > RATIO_HIGH) | (reported["ratio"] < RATIO_LOW))
    & (reported["difference"].abs() >= MIN_ABSOLUTE)
)
print(f"{reported['flag'].sum()} flags from {len(reported)} facility-antigen-months")
reported <- reported |>
  mutate(
    difference = doses_administered - own_median,
    flag = (ratio > 2 | ratio < 0.5) & abs(difference) >= 10
  )

Appliqué ici, rien du tout ne dépasse le double de sa propre médiane, et onze couples formation-antigène-mois tombent sous la moitié de la leur. Le mouvement réel le plus important est le penta3 de FAC005 en octobre — 13 doses contre une médiane de 43, une baisse réelle de trente.

Voilà le constat, et il tient en une ligne. Un contrôle qui produit un seul élément à investiguer sur 2 094 observations fonctionne ; un contrôle qui en produit deux cents est un contrôle que personne n’exécutera deux fois.

Le sens n’est pas symétrique

Une baisse et un pic ne veulent pas dire la même chose, et une évaluation qui les traite pareillement manque l’essentiel de ce qui se passe.

  • Une baisse est généralement réelle ou liée au rapportage. Rupture de stock, absence de personnel, grève, formation qui s’est mise à rapporter à un autre système. Interrogez le mois, pas le nombre.
  • Un pic est généralement une campagne, une sortie de proximité ou un rattrapage après rupture — toutes légitimes — ou un double comptage, qui ne l’est pas. Vérifiez si les mois encadrants baissent en conséquence, signature d’un problème de bornes de période plutôt que d’une activité supplémentaire.
reported["previous"] = reported.groupby(["facility_id", "antigen"])[
    "doses_administered"].shift(1)
reported["next"] = reported.groupby(["facility_id", "antigen"])[
    "doses_administered"].shift(-1)

spike_and_dip = reported[
    (reported["ratio"] > 1.5)
    & (reported["previous"] < reported["own_median"] * 0.7)
]
reported <- reported |>
  mutate(previous = lag(doses_administered),
         next_value = lead(doses_administered),
         .by = c(facility_id, antigen))

Un mois élevé précédé d’un mois faible est très souvent le travail d’un mois consigné dans la remontée du suivant. C’est un constat d’actualité déguisé en constat de cohérence, et le remède est une échéance, pas un recomptage.

Des séries trop stables

Le contrôle inverse, celui qu’on oublie. La prestation de service réelle est bruitée. Une formation rapportant un chiffre presque identique chaque mois mérite un coup d’œil.

stability = (
    reported.groupby(["facility_id", "antigen"])["doses_administered"]
    .agg(["mean", "std", "size"])
)
stability["cv"] = stability["std"] / stability["mean"]
print(stability[stability["size"] >= 8].nsmallest(5, "cv"))
reported |>
  summarise(mean = mean(doses_administered),
            cv = sd(doses_administered) / mean(doses_administered),
            n = n(),
            .by = c(facility_id, antigen)) |>
  filter(n >= 8) |>
  slice_min(cv, n = 5)

La série la plus stable de cette extraction a un coefficient de variation d’environ 0,06 — six pour cent d’un mois à l’autre. C’est faible, et ce n’est la preuve de rien. Une grande formation à bassin stable et à jour de consultation régulier peut aisément produire cela.

Énoncez la limite franchement, car ce contrôle est le plus facilement détourné : un faible coefficient de variation met une formation sur la liste des visites. Il ne figure pas au rapport comme un constat, et surtout pas dans une phrase contenant le mot « fabriqué ».

Classez les formations, ne signalez pas des lignes

Le produit de cette leçon n’est pas une liste de mois signalés. C’est un classement d’unités de rapportage, ce dont la leçon d’échantillonnage avait besoin.

risk = (
    reported.groupby("facility_id")
    .agg(flags=("flag", "sum"), months=("flag", "size"))
    .assign(flag_rate=lambda d: d["flags"] / d["months"])
    .sort_values("flag_rate", ascending=False)
)
print(risk.head(6))
risk <- reported |>
  summarise(flags = sum(flag), months = n(), .by = facility_id) |>
  mutate(flag_rate = flags / months) |>
  arrange(desc(flag_rate))

Une formation avec quatre signalements sur douze mois est candidate à une visite. Un mois signalé isolé dans une formation par ailleurs régulière est une question pour un appel téléphonique. L’unité d’action est la formation, parce que c’est là que va le véhicule.

La suite

Les contrôles de tendance comparent un nombre à son propre passé. La leçon suivante regarde à l’intérieur du nombre lui-même — le dernier chiffre, l’attraction des multiples de cinq, et les limites honnêtes de ce que cela peut dire de la façon dont une mesure a été produite.

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.