{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Évolution du dépistage sur l'année de campagne\n",
    "\n",
    "*Dépistage du périmètre brachial — Artibonite, 2024*\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/coverage-trend.python.fr.ipynb)"
   ],
   "id": "cell-000"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ce que produit ce document\n",
    "\n",
    "Le nombre de dépistages par mois et par commune sur la campagne 2024, et le\n",
    "problème de qualité des données que dissimule le volume. C'est le pendant de\n",
    "l'exemple sur la prévalence : celui-là demande à quel point les enfants étaient\n",
    "malnutris, celui-ci demande si vous en avez dépisté assez, aux bons endroits, et\n",
    "avec assez de régularité pour croire la réponse.\n",
    "\n",
    "Tous les jeux de données de cette plateforme sont synthétiques. Rien ici ne\n",
    "décrit un enfant réel.\n",
    "\n",
    "## Une mise en garde sur le mot « couverture »\n",
    "\n",
    "Ce qui suit n'est **pas** la couverture du programme. La couverture est le\n",
    "rapport des cas atteints aux cas existants, et ce registre ne dispose d'aucun\n",
    "dénominateur d'enfants dans la population — seulement des enfants qui se sont\n",
    "présentés. Appeler « couverture » un volume de dépistage est l'une des manières\n",
    "les plus courantes pour un rapport nutritionnel de surestimer ce qu'il sait.\n",
    "\n",
    "Ce que le volume peut vous dire, en revanche, c'est où la campagne a été\n",
    "interrompue : une question réelle et utile, à laquelle il existe une réponse\n",
    "honnête.\n",
    "\n",
    "## Mise en place"
   ],
   "id": "cell-001"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "\n",
    "URL = (\n",
    "    \"https://data-analysis.cassion.dev/datasets/files/\"\n",
    "    \"muac-screening-artibonite-2024.v1.csv\"\n",
    ")\n",
    "\n",
    "muac = pd.read_csv(\n",
    "    URL,\n",
    "    dtype={\"child_id\": \"string\", \"commune\": \"string\", \"sex\": \"string\"},\n",
    "    na_values={\"muac_mm\": [\"-99\"]},\n",
    ")\n",
    "\n",
    "muac[\"screening_date\"] = pd.to_datetime(muac[\"screening_date\"], format=\"%Y-%m-%d\")\n",
    "muac[\"mois\"] = muac[\"screening_date\"].dt.to_period(\"M\")\n",
    "\n",
    "print(f\"{len(muac):,} depistages, {muac['commune'].nunique()} communes\")\n",
    "print(f\"{muac['mois'].min()} a {muac['mois'].max()}\")"
   ],
   "id": "cell-002"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Le volume sur l'année"
   ],
   "id": "cell-003"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "mensuel = muac.groupby(\"mois\").size()\n",
    "mensuel"
   ],
   "id": "cell-004"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Janvier et décembre sont visiblement plus maigres que les mois intermédiaires.\n",
    "Avant d'y lire une campagne au démarrage lent et à l'extinction progressive,\n",
    "vérifiez l'explication évidente : un registre qui ne couvre qu'une partie du mois\n",
    "à chaque extrémité. Ici le premier dépistage a lieu à la mi-janvier et le dernier\n",
    "à la mi-décembre : les deux mois faibles sont donc un artefact de la fenêtre de\n",
    "rapportage, et non une baisse d'activité.\n",
    "\n",
    "C'est la vérification à mener chaque fois qu'une première ou une dernière période\n",
    "paraît faible."
   ],
   "id": "cell-005"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"premier depistage :\", muac[\"screening_date\"].min().date())\n",
    "print(\"dernier depistage :\", muac[\"screening_date\"].max().date())"
   ],
   "id": "cell-006"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Le volume par commune"
   ],
   "id": "cell-007"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "par_commune = (\n",
    "    muac.groupby(\"commune\")\n",
    "    .size()\n",
    "    .sort_values(ascending=False)\n",
    "    .rename(\"depistages\")\n",
    "    .to_frame()\n",
    ")\n",
    "par_commune[\"part\"] = (par_commune[\"depistages\"] / len(muac)).round(3)\n",
    "par_commune"
   ],
   "id": "cell-008"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Gonaïves et Saint-Marc représentent une large part du registre. Ce n'est pas en\n",
    "soi un résultat — ce sont les communes les plus peuplées — mais cela signifie\n",
    "qu'un taux départemental non pondéré est dominé par elles, et qu'une commune à\n",
    "189 dépistages porte un intervalle assez large pour rendre son rang à peu près\n",
    "dépourvu de sens.\n",
    "\n",
    "## Où la campagne a été interrompue\n",
    "\n",
    "Le motif utile n'est pas le total : c'est le mois où l'activité d'une commune\n",
    "s'écarte de sa propre norme."
   ],
   "id": "cell-009"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grille = (\n",
    "    muac.pivot_table(\n",
    "        index=\"commune\", columns=\"mois\", values=\"child_id\", aggfunc=\"count\"\n",
    "    )\n",
    "    .fillna(0)\n",
    "    .astype(int)\n",
    ")\n",
    "\n",
    "# Chaque commune rapportée à sa propre médiane mensuelle, pour qu'une grande et\n",
    "# une petite commune restent comparables.\n",
    "mediane = grille.median(axis=1)\n",
    "relatif = grille.div(mediane, axis=0).round(2)\n",
    "relatif"
   ],
   "id": "cell-010"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Empiler d'abord, filtrer ensuite. Filtrer le tableau puis empiler laisse les\n",
    "# cellules NaN produites par le masque, que pandas conserve désormais : vous\n",
    "# obtenez un résultat contenant tous les couples commune-mois, sans erreur pour\n",
    "# vous prevenir.\n",
    "plat = relatif.stack()\n",
    "creux = plat[plat < 0.5].sort_values()\n",
    "\n",
    "print(f\"{len(creux)} couples commune-mois sous la moitie de la mediane\")\n",
    "creux"
   ],
   "id": "cell-011"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La quasi-totalité correspond à décembre et à janvier, soit de nouveau l'artefact\n",
    "de fenêtre de rapportage et non une interruption. Le seul qui n'en relève pas —\n",
    "Gros-Morne en janvier — correspond à une campagne démarrée tardivement dans cette\n",
    "commune, et c'est la seule entrée sur laquelle il vaut la peine de poser une\n",
    "question.\n",
    "\n",
    "## Le problème qui ne relève pas du volume\n",
    "\n",
    "Le volume de dépistage s'est maintenu en juin. Ce qui ne s'est pas maintenu,\n",
    "c'est la complétude — et un décompte de lignes ne vous le montrera jamais,\n",
    "puisque les lignes sont bien là."
   ],
   "id": "cell-012"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "juin = muac[muac[\"mois\"] == pd.Period(\"2024-06\")]\n",
    "\n",
    "completude = (\n",
    "    juin.assign(age_manquant=juin[\"age_months\"].isna())\n",
    "    .groupby(\"commune\")[\"age_manquant\"]\n",
    "    .agg(lignes=\"size\", taux_age_manquant=\"mean\")\n",
    "    .sort_values(\"taux_age_manquant\", ascending=False)\n",
    "    .round(3)\n",
    ")\n",
    "completude"
   ],
   "id": "cell-013"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le mois de juin d'une commune est bien plus dégradé que celui des autres.\n",
    "Resserrez sur la semaine et la cause devient évidente :"
   ],
   "id": "cell-014"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pire = completude.index[0]\n",
    "\n",
    "hebdo = (\n",
    "    muac[muac[\"commune\"] == pire]\n",
    "    .assign(semaine=lambda d: d[\"screening_date\"].dt.to_period(\"W\"))\n",
    "    .groupby(\"semaine\")[\"age_months\"]\n",
    "    .agg(lignes=\"size\", taux_age_manquant=lambda s: s.isna().mean())\n",
    "    .sort_values(\"taux_age_manquant\", ascending=False)\n",
    "    .round(3)\n",
    ")\n",
    "hebdo.head()"
   ],
   "id": "cell-015"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Une équipe, une semaine, un formulaire de tablette dont le champ âge était mal\n",
    "configuré. Les dépistages ont bien eu lieu et les enfants ont bien été mesurés ;\n",
    "seul l'âge manque.\n",
    "\n",
    "## Pourquoi cela compte pour le tableau de prévalence\n",
    "\n",
    "Si vous supprimez les lignes incomplètes avant de calculer la prévalence, vous\n",
    "retirez cette commune bien plus que toute autre — puis vous classez les communes\n",
    "en partie selon le formulaire de quelle équipe était défectueux."
   ],
   "id": "cell-016"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "MAG_MM = 125\n",
    "\n",
    "erreur_unite = muac[\"muac_mm\"].notna() & (muac[\"muac_mm\"] < 40)\n",
    "muac.loc[erreur_unite, \"muac_mm\"] = muac.loc[erreur_unite, \"muac_mm\"] * 10\n",
    "\n",
    "mesures = muac[muac[\"muac_mm\"].notna()]\n",
    "complet = mesures[mesures[\"age_months\"].notna()]\n",
    "\n",
    "comparaison = pd.DataFrame({\n",
    "    \"en_conservant\": mesures.groupby(\"commune\")[\"muac_mm\"].apply(\n",
    "        lambda s: (s < MAG_MM).mean()\n",
    "    ),\n",
    "    \"en_supprimant\": complet.groupby(\"commune\")[\"muac_mm\"].apply(\n",
    "        lambda s: (s < MAG_MM).mean()\n",
    "    ),\n",
    "})\n",
    "comparaison[\"ecart\"] = (\n",
    "    comparaison[\"en_supprimant\"] - comparaison[\"en_conservant\"]\n",
    ")\n",
    "comparaison.sort_values(\"ecart\", key=abs, ascending=False).round(4)"
   ],
   "id": "cell-017"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les seuils de PB pour les 6 à 59 mois forment une bande unique et n'exigent pas\n",
    "l'âge : la bonne décision est donc de conserver ces lignes pour l'indicateur PB\n",
    "et de ne les exclure que des analyses qui réclament réellement l'âge. Cette\n",
    "décision n'est disponible que parce que vous avez regardé.\n",
    "\n",
    "## La représentation"
   ],
   "id": "cell-018"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(9, 5))\n",
    "\n",
    "for commune in grille.index:\n",
    "    ax.plot(\n",
    "        range(len(grille.columns)),\n",
    "        grille.loc[commune],\n",
    "        marker=\"o\",\n",
    "        markersize=3,\n",
    "        linewidth=1,\n",
    "        color=\"#9AA8A3\",\n",
    "        alpha=0.7,\n",
    "    )\n",
    "\n",
    "ax.plot(\n",
    "    range(len(grille.columns)),\n",
    "    grille.loc[pire],\n",
    "    marker=\"o\",\n",
    "    markersize=4,\n",
    "    linewidth=2,\n",
    "    color=\"#2F5D50\",\n",
    "    label=pire,\n",
    ")\n",
    "\n",
    "ax.set_xticks(range(len(grille.columns)))\n",
    "ax.set_xticklabels([str(m) for m in grille.columns], rotation=45, ha=\"right\")\n",
    "ax.set_ylabel(\"Depistages\")\n",
    "ax.set_title(\"Depistages par commune et par mois, 2024\")\n",
    "ax.legend(frameon=False)\n",
    "ax.spines[[\"top\", \"right\"]].set_visible(False)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ],
   "id": "cell-019"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ce qu'il faut rapporter\n",
    "\n",
    "Énoncez le volume, énoncez la complétude séparément, et ne laissez jamais l'un\n",
    "tenir lieu de l'autre. Une campagne ayant dépisté le nombre d'enfants visé avec\n",
    "un champ âge défectueux présente un problème de volume nul et un problème de\n",
    "données qui modifie le classement — et un seul des deux est visible dans un\n",
    "décompte de lignes."
   ],
   "id": "cell-020"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "file_extension": ".py"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
