{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Malnutrition aiguë globale par commune\n",
    "\n",
    "*Dépistage du périmètre brachial — Artibonite, 2024 · R*\n",
    "\n",
    "Cassion · data-analysis.cassion.dev\n",
    "\n",
    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/thecassion/cassion-learning-platform/blob/main/apps/data-analysis/public/datasets/examples/muac-screening-artibonite-2024/gam-by-commune.r.fr.ipynb)"
   ],
   "id": "cell-000"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ce que produit ce document\n",
    "\n",
    "La même analyse que l'exemple Python, mais en R. Python et R sont sur un pied\n",
    "d'égalité sur cette plateforme, et une technique qui n'existe que dans l'un des\n",
    "deux est incomplète : vous hériterez de celui qu'utilisait la personne en poste\n",
    "avant vous.\n",
    "\n",
    "Les prévalences de MAG et de MAS par commune, avec intervalles de confiance à\n",
    "95 %. Les valeurs de référence figurent dans les notes de qualité du jeu de\n",
    "données : une MAG globale proche de 8,6 % et une MAS proche de 2,2 %, avec une\n",
    "amplitude d'environ 5 % à 15 % selon les communes. Si vos chiffres s'en écartent\n",
    "nettement, vous avez un bogue et non un résultat.\n",
    "\n",
    "Tous les jeux de données de cette plateforme sont synthétiques. Rien ici ne\n",
    "décrit un enfant réel, et ces chiffres ne doivent jamais être cités comme des\n",
    "prévalences réelles.\n",
    "\n",
    "## Mise en place"
   ],
   "id": "cell-001"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| message: false\n",
    "library(readr)\n",
    "library(dplyr)\n",
    "library(ggplot2)\n",
    "\n",
    "URL <- paste0(\n",
    "  \"https://data-analysis.cassion.dev/datasets/files/\",\n",
    "  \"muac-screening-artibonite-2024.v1.csv\"\n",
    ")\n",
    "\n",
    "muac <- read_csv(\n",
    "  URL,\n",
    "  col_types = cols(\n",
    "    child_id       = col_character(),\n",
    "    commune        = col_character(),\n",
    "    screening_date = col_date(format = \"%Y-%m-%d\"),\n",
    "    age_months     = col_integer(),\n",
    "    sex            = col_character(),\n",
    "    muac_mm        = col_integer(),\n",
    "    oedema         = col_character(),\n",
    "    outcome        = col_character()\n",
    "  ),\n",
    "  na = c(\"\", \"NA\", \"-99\")\n",
    ")\n",
    "\n",
    "dim(muac)\n",
    "glimpse(muac)"
   ],
   "id": "cell-002"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notez la différence avec pandas. L'argument `na` de `read_csv` s'applique à tout\n",
    "le fichier et non à une seule colonne : si une autre colonne portait un jour un\n",
    "`-99` légitime, il faudrait restreindre le traitement après coup. Déclarer\n",
    "explicitement `col_types` est ce qui empêche `child_id` d'être lu comme un\n",
    "nombre et de perdre son zéro initial.\n",
    "\n",
    "## Corriger ce qui fausserait l'indicateur\n",
    "\n",
    "Sept enregistrements sont restés en centimètres sans jamais être convertis. Les\n",
    "plages plausibles en millimètres et en centimètres ne se chevauchant pas, la\n",
    "correction est sans ambiguïté. Deux communes ont consigné les œdèmes en `Y`/`N`\n",
    "au premier trimestre."
   ],
   "id": "cell-003"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_erreur_unite <- sum(!is.na(muac$muac_mm) & muac$muac_mm < 40)\n",
    "\n",
    "muac <- muac |>\n",
    "  mutate(\n",
    "    muac_mm = if_else(!is.na(muac_mm) & muac_mm < 40, muac_mm * 10L, muac_mm),\n",
    "    muac_mm = if_else(\n",
    "      !is.na(muac_mm) & (muac_mm < 80 | muac_mm > 220),\n",
    "      NA_integer_,\n",
    "      muac_mm\n",
    "    ),\n",
    "    oedema = case_when(\n",
    "      tolower(trimws(oedema)) %in% c(\"true\", \"y\", \"yes\") ~ TRUE,\n",
    "      tolower(trimws(oedema)) %in% c(\"false\", \"n\", \"no\") ~ FALSE,\n",
    "      TRUE ~ NA\n",
    "    )\n",
    "  )\n",
    "\n",
    "cat(\"erreurs d'unite corrigees :\", n_erreur_unite, \"\\n\")\n",
    "cat(\"oedemes encore manquants  :\", sum(is.na(muac$oedema)), \"\\n\")"
   ],
   "id": "cell-004"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Définir l'indicateur avant de le calculer\n",
    "\n",
    "- **Numérateur** — PB inférieur à 125 mm, ou œdèmes bilatéraux prenant le godet.\n",
    "- **Dénominateur** — une mesure de PB valide **ou** une évaluation des œdèmes\n",
    "  consignée.\n",
    "- **Désagrégation** — commune.\n",
    "\n",
    "Les œdèmes signent une malnutrition aiguë sévère quelle que soit la mesure :\n",
    "filtrer sur le seul `muac_mm` sous-estime donc la charge de cas, et la\n",
    "sous-estime précisément parmi les cas les plus graves.\n",
    "\n",
    "`oedema %in% TRUE` plutôt qu'un simple test sur `oedema` : un œdème non évalué\n",
    "n'est pas un œdème absent, et `NA | FALSE` vaut `NA` en R, ce qui retirerait\n",
    "silencieusement la ligne du numérateur."
   ],
   "id": "cell-005"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "MAS_MM <- 115\n",
    "MAG_MM <- 125\n",
    "\n",
    "muac <- muac |>\n",
    "  mutate(\n",
    "    evalue = !is.na(muac_mm) | !is.na(oedema),\n",
    "    mas = if_else(evalue, (muac_mm < MAS_MM) | oedema %in% TRUE, NA),\n",
    "    mag = if_else(evalue, (muac_mm < MAG_MM) | oedema %in% TRUE, NA)\n",
    "  )"
   ],
   "id": "cell-006"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Le tableau d'indicateurs, avec intervalles exacts\n",
    "\n",
    "Les colonnes `depistes` et `denominateur` sont distinctes à dessein : elles\n",
    "diffèrent des lignes sans aucune évaluation, et un lecteur qui voit les deux peut\n",
    "juger sur quelle part du registre repose le taux.\n",
    "\n",
    "`binom.test` fournit l'intervalle de Clopper-Pearson, exact pour une proportion\n",
    "et stable sur les petits effectifs que produisent les communes les moins\n",
    "peuplées."
   ],
   "id": "cell-007"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "intervalle <- function(cas, n) {\n",
    "  if (n == 0 || is.na(cas)) return(c(NA_real_, NA_real_))\n",
    "  binom.test(cas, n)$conf.int\n",
    "}\n",
    "\n",
    "tableau <- muac |>\n",
    "  group_by(commune) |>\n",
    "  summarise(\n",
    "    depistes     = n(),\n",
    "    denominateur = sum(evalue),\n",
    "    cas_mas      = sum(mas, na.rm = TRUE),\n",
    "    cas_mag      = sum(mag, na.rm = TRUE),\n",
    "    .groups = \"drop\"\n",
    "  ) |>\n",
    "  mutate(\n",
    "    taux_mag = cas_mag / denominateur,\n",
    "    taux_mas = cas_mas / denominateur\n",
    "  ) |>\n",
    "  rowwise() |>\n",
    "  mutate(\n",
    "    mag_bas  = intervalle(cas_mag, denominateur)[1],\n",
    "    mag_haut = intervalle(cas_mag, denominateur)[2]\n",
    "  ) |>\n",
    "  ungroup() |>\n",
    "  arrange(desc(taux_mag))\n",
    "\n",
    "tableau"
   ],
   "id": "cell-008"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Représenter le classement avec son incertitude"
   ],
   "id": "cell-009"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| fig-width: 8\n",
    "#| fig-height: 5\n",
    "SEUIL_URGENCE <- 0.15\n",
    "\n",
    "ggplot(tableau, aes(x = taux_mag * 100, y = reorder(commune, taux_mag))) +\n",
    "  geom_vline(\n",
    "    xintercept = SEUIL_URGENCE * 100,\n",
    "    colour = \"#B5533C\", linetype = \"dashed\"\n",
    "  ) +\n",
    "  geom_errorbarh(\n",
    "    aes(xmin = mag_bas * 100, xmax = mag_haut * 100),\n",
    "    height = 0, colour = \"#9AA8A3\"\n",
    "  ) +\n",
    "  geom_point(colour = \"#2F5D50\", size = 2) +\n",
    "  annotate(\n",
    "    \"text\", x = SEUIL_URGENCE * 100 + 0.3, y = 1,\n",
    "    label = \"seuil d'urgence (15%)\", colour = \"#B5533C\",\n",
    "    hjust = 0, size = 3\n",
    "  ) +\n",
    "  labs(\n",
    "    x = \"Prevalence de la MAG par PB (%)\",\n",
    "    y = NULL,\n",
    "    title = \"Malnutrition aigue globale par commune, avec IC 95%\"\n",
    "  ) +\n",
    "  theme_minimal(base_size = 11) +\n",
    "  theme(panel.grid.minor = element_blank())"
   ],
   "id": "cell-010"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lire le résultat\n",
    "\n",
    "Plusieurs intervalles se recouvrent : l'ordre entre ces communes n'est donc pas\n",
    "soutenu par les données. Une décision qui attribuerait un site PCIMA\n",
    "supplémentaire au seul rang lirait une précision que le dépistage ne possède\n",
    "pas.\n",
    "\n",
    "L'intervalle ne décrit par ailleurs que la variation d'échantillonnage. Il\n",
    "s'agit d'un recensement des enfants venus au dépistage et non d'un échantillon\n",
    "probabiliste : savoir si ces enfants ressemblent à ceux qui ne sont pas venus\n",
    "constitue une source d'erreur distincte, et généralement plus grande, qui relève\n",
    "de la section des limites de tout rapport construit sur ces données."
   ],
   "id": "cell-011"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "R",
   "language": "R",
   "name": "ir"
  },
  "language_info": {
   "name": "R",
   "file_extension": ".r"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
