---
title: "Ajuster la couverture pour la complétude du rapportage"
subtitle: "Couverture vaccinale de routine, 2024 · R"
author: "Cassion · data-analysis.cassion.dev"
format:
  html:
    toc: true
    code-fold: false
---

## Le problème en une phrase

En août, la couverture penta3 rapportée tombe sous le tiers. Rien n'est arrivé à
la vaccination : 71 % des formations sanitaires n'ont simplement pas transmis de
rapport, et leurs zéros sont entrés au numérateur pendant que leurs populations
cibles restaient au dénominateur.

Tous les jeux de données de cette plateforme sont synthétiques. La couverture
présentée ne décrit aucun district réel.

## Mise en place

```{r}
#| message: false
library(readr)
library(dplyr)
library(tidyr)
library(ggplot2)

URL <- paste0(
  "https://data-analysis.cassion.dev/datasets/files/",
  "vaccination-coverage-2024.v1.csv"
)

epi <- read_csv(URL, col_types = cols(
  facility_id = col_character(),
  period      = col_date(),
  .default    = col_guess()
)) |>
  mutate(mois = format(period, "%Y-%m"))

MOIS_PAR_AN <- 12
```

## La complétude est un indicateur à part entière

Rapportez-la avant tout ce qui se calcule à partir des données qu'elle
conditionne. Un chiffre de couverture placé à côté d'une complétude de 29 % se
lit très différemment du même chiffre présenté seul.

```{r}
completude <- epi |>
  distinct(facility_id, mois, report_submitted) |>
  group_by(mois) |>
  summarise(
    formations = n(),
    rapportees = sum(report_submitted),
    completude = round(mean(report_submitted), 3),
    .groups = "drop"
  )

completude
```

Août à 29 % et septembre à 45 %. Tout ce qui suit porte sur ce que ces deux mois
font à une série de couverture.

## Trois dénominateurs, trois récits différents

```{r}
penta3 <- epi |> filter(antigen == "penta3")

serie <- penta3 |>
  group_by(mois) |>
  summarise(
    doses            = sum(doses_administered),
    cible_totale     = sum(target_population),
    cible_rapportee  = sum(target_population[report_submitted]),
    completude       = mean(report_submitted),
    .groups = "drop"
  ) |>
  mutate(
    non_corrigee       = doses / (cible_totale / MOIS_PAR_AN),
    declarants_seuls   = doses / (cible_rapportee / MOIS_PAR_AN),
    mise_a_l_echelle   = non_corrigee / completude
  )

serie |>
  select(mois, completude, non_corrigee, declarants_seuls, mise_a_l_echelle) |>
  mutate(across(where(is.numeric), ~ round(.x, 3)))
```

**Non corrigée** compte la population cible de toutes les formations au
dénominateur et les seules doses des formations déclarantes au numérateur. Les
deux ne décrivent pas la même population, et le résultat suit le rapportage
plutôt que la vaccination.

**Déclarants seuls** restreint le dénominateur aux formations ayant rapporté.
Numérateur et dénominateur décrivent désormais le même ensemble, et la série
reste stable en août.

**Mise à l'échelle** divise le chiffre non corrigé par la complétude. C'est
l'ajustement vers lequel on se tourne en premier, et c'est celui dont il faut se
méfier.

## Pourquoi la mise à l'échelle sur-corrige

```{r}
serie |>
  select(mois, completude, declarants_seuls, mise_a_l_echelle) |>
  mutate(
    ecart = round(mise_a_l_echelle - declarants_seuls, 3),
    across(where(is.numeric), ~ round(.x, 3))
  ) |>
  arrange(desc(abs(ecart)))
```

En août, la mise à l'échelle produit une couverture proche de 99 % — supérieure à
celle de tous les mois où presque tout le monde avait rapporté. Diviser par la
complétude suppose que les formations silencieuses auraient obtenu exactement les
mêmes résultats que les déclarantes. Ce n'est généralement pas le cas : une
formation qui cesse de rapporter est souvent celle qui connaît une rupture de
stock, un déficit de personnel ou un problème d'accès — autrement dit, celle qui
obtient les moins bons résultats.

L'hypothèse n'est pas toujours fausse, mais elle doit être énoncée, et elle ne
doit pas être posée en silence par un analyste qui saisit la formule la plus
simple.

## Le graphique qui l'établit

```{r}
#| fig-width: 8
#| fig-height: 5
serie |>
  select(mois, non_corrigee, declarants_seuls, mise_a_l_echelle) |>
  pivot_longer(-mois, names_to = "methode", values_to = "couverture") |>
  ggplot(aes(x = mois, y = 100 * couverture, group = methode, colour = methode)) +
  geom_line(linewidth = 0.8) +
  geom_point(size = 1.8) +
  scale_colour_manual(values = c(
    non_corrigee     = "#B5533C",
    declarants_seuls = "#2F5D50",
    mise_a_l_echelle = "#9AA8A3"
  )) +
  labs(
    x = NULL, y = "couverture penta3 (%)", colour = NULL,
    title = "La chute d'aout est un artefact de rapportage, non un effondrement"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    panel.grid.minor = element_blank()
  )
```

La courbe non corrigée présente une falaise en août. Celle restreinte aux
déclarants, non. La falaise est une propriété de qui a transmis un formulaire, et
un responsable de programme à qui l'on montrerait le premier graphique passerait
un mois à enquêter sur un effondrement qui n'a pas eu lieu.

## Quelles formations se sont tues

```{r}
silencieuses <- epi |>
  distinct(facility_id, mois, report_submitted, facility_type) |>
  filter(mois %in% c("2024-08", "2024-09")) |>
  group_by(facility_type) |>
  summarise(
    formations_mois = n(),
    rapportees      = sum(report_submitted),
    completude      = round(mean(report_submitted), 3),
    .groups = "drop"
  )

silencieuses
```

Si la complétude diffère selon le type de formation, le dénominateur restreint
aux déclarants reste biaisé : il décrit désormais les formations qui rapportent,
lesquelles ne constituent pas un échantillon aléatoire de l'ensemble. Vérifiez-le
avant de présenter la série corrigée comme si elle était la vérité.

## Ce qu'il faut rapporter

La complétude à côté de la couverture, systématiquement, dans le même tableau. Le
dénominateur employé, nommé. Et si vous avez mis à l'échelle par la complétude,
l'hypothèse que les formations silencieuses obtiennent les mêmes résultats que
les déclarantes, écrite noir sur blanc — car c'est cette hypothèse qui fait le
plus gros du travail, et non l'arithmétique.
