cassionAnalyse de données

Leçon 3 sur 8

Unité · Périodes et complétude

Les périodes, et l'opérateur d'agrégation que personne ne règle

Un fichier, un indicateur, trois taux de couverture annuels — 6,5 %, 60,8 % et 77,5 % — tous issus de phrases qui sonnent défendables. La différence est la manière dont le dénominateur s'agrège dans le temps.

PythonR90 minDéfinitions d'indicateurs de l'UNICEFObjectifs de développement durable (ODD)Gestion axée sur les résultats (GAR)

Types de période, et l’identifiant qui les porte

Toute valeur appartient à une période, et le type de période est une propriété de l’ensemble de données sur lequel elle a été collectée. Les identifiants de période de DHIS2 encodent le type dans la chaîne, ce qu’il vaut la peine d’adopter même hors DHIS2.

202408      monthly
2024W32     weekly
2024Q3      quarterly
2024        yearly
2024April   financial year starting April

Ils se trient correctement, ils ne peuvent pas être ambigus entre conventions, et le type se lit sans schéma. Le cours sur les jointures plaidait pour des clés de période ISO ; c’est le même argument avec le vocabulaire du système.

import pandas as pd

vax = pd.read_csv("vaccination-coverage-2024.v1.csv", parse_dates=["period"])
vax["period_id"] = vax["period"].dt.strftime("%Y%m")
print(sorted(vax["period_id"].unique())[:4])
library(dplyr)
vax <- vax |> mutate(period_id = format(period, "%Y%m"))

À quelle période appartient une valeur ?

La question a une bonne réponse et deux mauvaises d’usage quotidien.

  • Juste — la période où l’activité a eu lieu. Une dose administrée le 28 août est d’août, quelle que soit la date d’arrivée du formulaire.
  • Faux — la période où le formulaire a été transmis. Fréquent là où la saisie se fait par lots, et cela décale l’activité d’un mois partout où le rapportage est tardif.
  • Faux — la période où la valeur a été saisie. Même défaillance, un cran plus loin.

DHIS2 stocke la valeur contre la période du formulaire de saisie ouvert, si bien que le système a raison si la personne a ouvert le bon formulaire. La défaillance est humaine et se manifeste par un motif caractéristique — un mois bas suivi d’un mois haut, le pic-et-creux que le cours d’évaluation vous a appris à chercher.

Les trois chiffres annuels

Voici la leçon. Prenez le penta3 sur l’année et demandez la couverture annuelle.

penta3 = vax[vax["antigen"] == "penta3"]
reported = penta3[penta3["report_submitted"] == True]

doses = reported["doses_administered"].sum()

summed_denominator = reported["target_population"].sum()
annual_target = penta3.groupby("facility_id")["target_population"].first().sum()

print(f"doses: {doses:,}")
print(f"a) doses / summed monthly target : {doses / summed_denominator:.1%}")
print(f"b) doses / annual target, all 38 : {doses / annual_target:.1%}")
penta3 <- vax |> filter(antigen == "penta3")
reported <- penta3 |> filter(report_submitted)

doses <- sum(reported$doses_administered)
summed <- sum(reported$target_population)
annual <- penta3 |> summarise(t = first(target_population), .by = facility_id) |>
  summarise(sum(t)) |> pull()

c(a = doses / summed, b = doses / annual)
Phrase Chiffre
« Doses sur population cible, sommée sur l’année » 6,5 %
« Doses annuelles sur population cible annuelle » 60,8 %
« Doses annuelles sur cible annuelle, parmi les couples formation-mois ayant rapporté » 77,5 %

Trois nombres, un fichier, un indicateur, et chacun sort d’une phrase que quelqu’un prononcerait en réunion sans sourciller.

Pourquoi ils diffèrent

6,5 % est un chiffre mensuel portant une étiquette annuelle. target_population est une cohorte annuelle — les nourrissons survivants du bassin pour l’année — et l’extraction la répète chaque mois. La sommer sur douze mois produit un dénominateur douze fois trop grand. Le résultat est la couverture mensuelle moyenne, et la multiplier par douze ramène à 78 %.

60,8 % compte les formations muettes comme n’ayant vacciné personne. Le dénominateur est la cohorte annuelle complète de chaque formation, et le numérateur ne contient que les doses des couples formation-mois ayant rapporté. 107 des 456 couples penta3 manquent, et ce chiffre leur attribue zéro dose.

77,5 % est le défendable, et il exige un ajustement explicite du dénominateur.

months_reported = reported.groupby("facility_id").size()
targets = penta3.groupby("facility_id")["target_population"].first()
prorated = (targets * months_reported.reindex(targets.index).fillna(0) / 12).sum()

print(f"c) pro-rated denominator: {doses / prorated:.1%}")
months <- reported |> summarise(m = n(), .by = facility_id)
targets <- penta3 |> summarise(t = first(target_population), .by = facility_id)

prorated <- targets |>
  left_join(months, by = "facility_id") |>
  mutate(m = coalesce(m, 0)) |>
  summarise(sum(t * m / 12)) |> pull()

doses / prorated

Chaque formation contribue la part de sa cohorte annuelle correspondant aux mois qu’elle a effectivement rapportés. C’est une couverture parmi les couples formation-mois ayant rapporté, et le libellé doit le dire.

L’opérateur d’agrégation

Sous ces trois chiffres se trouve un réglage de configuration que la plupart des analystes ne voient jamais — la façon dont un élément de données s’agrège entre périodes.

Opérateur Sens Convient à
Somme Additionner entre périodes Décomptes d’événements — doses, admissions, consultations
Moyenne Moyenne entre périodes Niveaux de stock, effectifs, tout ce qui est un état et non un événement
Dernière valeur Prendre la plus récente Populations, cibles, tailles de registre

doses_administered se somme. target_population ne doit pas — c’est un état, et il se somme à douze fois lui-même. Dans une instance réelle, l’élément serait configuré en moyenne ou en dernière valeur, et demander la couverture annuelle fonctionnerait. Ici c’est une colonne répétée et la correction vous revient.

Demandez l’opérateur d’agrégation avec les métadonnées. C’est un champ par élément de données, invisible dans l’export, et c’est la différence entre 6,5 % et 78 %.

Le même réglage existe pour l’agrégation entre unités d’organisation, où « somme » est presque toujours juste et « moyenne » presque toujours fausse — les doses d’un district sont la somme de celles de ses formations, non leur moyenne.

Échéances, expiration et verrouillage

Trois réglages qui déterminent si une valeur peut encore changer.

  • L’échéance est la date à laquelle l’ensemble de données est dû. Elle alimente le chiffre de promptitude que calcule le cours d’évaluation.
  • Les jours d’expiration verrouillent le formulaire un nombre fixe de jours après la fin de la période. Ensuite, la saisie exige un déverrouillage.
  • Une exception de verrouillage déverrouille un ensemble, une unité, une période. Chacune est une décision, et un système en comptant des centaines n’a en pratique aucun verrouillage.

Conséquence pour un analyste — une extraction d’une période récente est provisoire. Tirez août en septembre puis en novembre et les nombres différeront, légitimement, parce que la saisie tardive continue d’arriver. Ce n’est pas un problème de qualité et cela ne doit pas être rapporté comme tel — c’est la raison pour laquelle l’avant-dernière leçon consigne une date d’extraction à chaque tirage.

La complétude est un enregistrement à part

Dernier point structurel, et il explique un nombre qui paraît sinon incohérent.

La complétude dans DHIS2 est un enregistrement, non un calcul. Quand un utilisateur clique « complet » sur un formulaire de saisie, le système stocke un enregistrement de complétude pour cet ensemble, cette unité et cette période. Le taux de rapportage est construit à partir de ces enregistrements.

Ce qui signifie qu’un ensemble peut être complet sans aucune valeur — quelqu’un a cliqué complet sur un formulaire vide — et plein de valeurs sans être complet — données saisies, personne n’a cliqué. Les deux arrivent constamment.

grid = (vax["facility_id"].nunique() * vax["period"].nunique()
        * vax["antigen"].nunique())
print(f"{grid} rows expected, {len(vax)} present, "
      f"{(vax['report_submitted'] == True).sum()} flagged reported")
c(expected = n_distinct(vax$facility_id) * n_distinct(vax$period) * n_distinct(vax$antigen),
  present = nrow(vax),
  reported = sum(vax$report_submitted))

2 736 lignes présentes et 2 094 marquées comme rapportées. La ligne existe dans les deux cas, exactement la forme contre laquelle le cours de nettoyage mettait en garde — un rapport manquant qui arrive sous forme de zéro avec un indicateur à côté.

La suite

L’indicateur de rapportage est la matière première du chiffre de complétude, et la leçon suivante en fait un dénominateur. Le cours d’évaluation a déjà montré pourquoi cela compte ; celui-ci montre comment le système le calcule, et les deux façons dont sa réponse diffère de la vôtre.

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.