---
title: "Couverture et taux d'abandon par formation sanitaire"
subtitle: "Couverture vaccinale de routine, 2024 · R"
author: "Cassion · data-analysis.cassion.dev"
format:
  html:
    toc: true
    code-fold: false
---

## Ce que produit ce document

Le même calcul d'abandon et la même logique de signalement que l'exemple Python,
écrits avec les verbes du tidyverse.

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)

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"))

glimpse(epi)
```

## Un rapport non transmis n'est pas zéro enfant vacciné

```{r}
epi |>
  filter(!report_submitted) |>
  summarise(
    lignes            = n(),
    lignes_a_zero     = sum(doses_administered == 0),
    formations_mois   = n_distinct(paste(facility_id, mois))
  )
```

Chaque ligne non rapportée porte un zéro. Sommez sans filtrer et vous affirmez
qu'aucun enfant de cette aire de responsabilité n'a été vacciné ce mois-là.

## Le dénominateur est annuel, le rapportage est mensuel

`target_population` est la cible annuelle répétée sur chaque ligne. La couverture
mensuelle divise par ce chiffre rapporté à douze ; diviser par le chiffre annuel
complet sous-estime la couverture d'un facteur douze.

```{r}
MOIS_PAR_AN <- 12

epi |>
  filter(antigen == "penta3") |>
  group_by(mois) |>
  summarise(
    doses          = sum(doses_administered),
    cible_annuelle = sum(target_population),
    completude     = round(mean(report_submitted), 3),
    .groups = "drop"
  ) |>
  mutate(
    denominateur_annuel_faux      = round(doses / cible_annuelle, 3),
    denominateur_mensuel_correct  = round(doses / (cible_annuelle / MOIS_PAR_AN), 3)
  )
```

## L'abandon entre penta1 et penta3

L'abandon ne dépend d'aucune estimation de population : les deux termes
proviennent du même registre, ce qui en fait un meilleur signal de programme que
la couverture.

```{r}
serie <- epi |>
  filter(report_submitted, antigen %in% c("penta1", "penta3")) |>
  group_by(facility_id, antigen) |>
  summarise(doses = sum(doses_administered), .groups = "drop") |>
  pivot_wider(names_from = antigen, values_from = doses) |>
  filter(!is.na(penta1), !is.na(penta3)) |>
  mutate(abandon = (penta1 - penta3) / penta1) |>
  arrange(desc(abandon))

cat("abandon median :", round(100 * median(serie$abandon), 1), "%\n")
head(serie, 10)
```

Une formation rapportant un abandon proche de zéro mérite autant de suspicion
qu'une formation en rapportant un très élevé. Un abandon quasi nul signifie
généralement que le penta3 a été reconstitué à partir du penta1 plutôt que compté.

## Le signalement de sur-rapportage, appliqué au bon niveau

```{r}
par_mois <- epi |>
  filter(report_submitted, antigen %in% c("penta1", "penta3")) |>
  select(facility_id, mois, antigen, doses_administered) |>
  pivot_wider(names_from = antigen, values_from = doses_administered) |>
  filter(!is.na(penta1), !is.na(penta3)) |>
  mutate(impossible = penta3 > penta1)

par_mois |>
  group_by(facility_id) |>
  summarise(deja_signalee = any(impossible), .groups = "drop") |>
  summarise(
    signalees  = sum(deja_signalee),
    formations = n()
  )
```

Trente-sept sur trente-huit. Un contrôle qui signale la quasi-totalité du
district n'identifie rien : les deux doses sont administrées à des enfants
différents à des mois différents et les décomptes n'ont aucune raison d'évoluer
de concert, si bien que le bruit ordinaire franchit la ligne en permanence.

```{r}
persistantes <- serie |> filter(abandon < 0)
persistantes
```

```{r}
par_mois |>
  filter(facility_id %in% persistantes$facility_id) |>
  group_by(facility_id) |>
  summarise(
    mois_signales  = sum(impossible),
    mois_rapportes = n(),
    .groups = "drop"
  ) |>
  arrange(desc(mois_signales))
```

Six formations sur le total annuel. Voilà une liste sur laquelle un superviseur
peut agir.

## La couverture par antigène

```{r}
epi |>
  filter(report_submitted) |>
  group_by(antigen) |>
  summarise(
    couverture = round(
      sum(doses_administered) / (sum(target_population) / MOIS_PAR_AN), 3
    ),
    .groups = "drop"
  ) |>
  arrange(desc(couverture))
```

Le dénominateur ne compte que les formations ayant rapporté, ce qui maintient
numérateur et dénominateur sur le même ensemble. C'est l'objet de l'exemple sur
l'ajustement pour la complétude.

## Ce qu'il faut rapporter

La couverture avec son dénominateur énoncé, l'abandon comme signal indépendant de
toute estimation de population, et la liste de sur-rapportage accompagnée du
niveau d'agrégation auquel elle a été calculée.
