{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Insécurité alimentaire par statut de déplacement et moyen d'existence\n",
    "\n",
    "*Enquête de sécurité alimentaire, 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/food-security-survey-2024/disaggregation.python.fr.ipynb)"
   ],
   "id": "cell-000"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ce que produit ce document\n",
    "\n",
    "Les groupes de consommation alimentaire ventilés par statut de déplacement,\n",
    "moyen d'existence et sexe du chef de ménage — et, plus utile encore, un compte\n",
    "rendu honnête des **ventilations assez robustes pour fonder une décision**. La\n",
    "désagrégation est le moment où une enquête cesse d'être un chiffre de tête pour\n",
    "devenir une décision de ciblage, et c'est aussi le moment où un petit échantillon\n",
    "s'épuise discrètement.\n",
    "\n",
    "Tous les jeux de données de cette plateforme sont synthétiques. Aucun ménage réel\n",
    "n'est décrit.\n",
    "\n",
    "## Mise en place et calcul du score\n",
    "\n",
    "Le calcul reprend l'exemple précédent en condensé : contrôle de plage, exclusion\n",
    "des incomplets, pondération."
   ],
   "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",
    "    \"food-security-survey-2024.v1.csv\"\n",
    ")\n",
    "\n",
    "fs = pd.read_csv(URL, dtype={\"household_id\": \"string\"})\n",
    "\n",
    "PONDERATIONS_SCA = {\n",
    "    \"fcs_cereals_tubers\": 2, \"fcs_pulses\": 3, \"fcs_vegetables\": 1,\n",
    "    \"fcs_fruit\": 1, \"fcs_meat_fish_eggs\": 4, \"fcs_dairy\": 4,\n",
    "    \"fcs_oils_fats\": 0.5, \"fcs_sugar\": 0.5,\n",
    "}\n",
    "composantes = list(PONDERATIONS_SCA)\n",
    "\n",
    "for colonne in composantes:\n",
    "    fs[colonne] = fs[colonne].where(fs[colonne].between(0, 7))\n",
    "\n",
    "fs[\"sca\"] = sum(fs[c] * w for c, w in PONDERATIONS_SCA.items())\n",
    "fs[\"groupe_consommation\"] = pd.cut(\n",
    "    fs[\"sca\"], [-np.inf, 21, 35, np.inf],\n",
    "    labels=[\"pauvre\", \"limite\", \"acceptable\"],\n",
    ")\n",
    "\n",
    "valides = fs[fs[\"sca\"].notna()].copy()\n",
    "print(f\"{len(valides)} menages au SCA complet, sur {len(fs)} enquetes\")"
   ],
   "id": "cell-002"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nettoyer d'abord la variable de désagrégation\n",
    "\n",
    "Une équipe du Sud a consigné le sexe du chef de ménage en `Female` et `Male`\n",
    "plutôt qu'en `f` et `m`. Un tableau désagrégé par sexe construit sans\n",
    "normalisation se fragmente en quatre catégories, deux d'entre elles assez petites\n",
    "pour passer pour du bruit — et ces deux petites proviennent d'une seule équipe\n",
    "dans une seule zone : ce n'est donc pas un sous-ensemble aléatoire de quoi que ce\n",
    "soit."
   ],
   "id": "cell-003"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(fs[\"sex_head_of_household\"].value_counts())"
   ],
   "id": "cell-004"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "valides[\"sexe_chef\"] = (\n",
    "    valides[\"sex_head_of_household\"]\n",
    "    .str.strip()\n",
    "    .str.lower()\n",
    "    .str[0]\n",
    "    .map({\"f\": \"femme\", \"m\": \"homme\"})\n",
    ")\n",
    "print(valides[\"sexe_chef\"].value_counts(dropna=False))"
   ],
   "id": "cell-005"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Prendre la première lettre après passage en minuscules traite `f`, `F`, `female`\n",
    "et `Female` en une seule opération. Le mappage explicite qui suit fait qu'une\n",
    "valeur inattendue devient `NaN` et se retrouve comptée, au lieu de constituer\n",
    "silencieusement une cinquième catégorie.\n",
    "\n",
    "## La ventilation qui compte : le statut de déplacement"
   ],
   "id": "cell-006"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def parts_groupes(df, par):\n",
    "    effectifs = pd.crosstab(df[par], df[\"groupe_consommation\"])\n",
    "    parts = (effectifs.div(effectifs.sum(axis=1), axis=0) * 100).round(1)\n",
    "    parts[\"menages\"] = effectifs.sum(axis=1)\n",
    "    return parts.sort_values(\"pauvre\", ascending=False)\n",
    "\n",
    "parts_groupes(valides, \"displacement_status\")"
   ],
   "id": "cell-007"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le gradient est le constat : les ménages déplacés et retournés sont en moins bonne\n",
    "situation que les résidents et les hôtes. Mais regardez les effectifs avant de\n",
    "croire à l'ordre — le plus petit groupe compte ici moins d'une centaine de\n",
    "ménages, et un pourcentage calculé sur une telle base bouge d'un point entier\n",
    "lorsque trois ménages changent de catégorie.\n",
    "\n",
    "## Quelle part de cet ordre est réelle ?\n",
    "\n",
    "Attachez un intervalle avant de classer quoi que ce soit. Sans lui, un écart de\n",
    "quatre points entre deux groupes se lit comme un constat alors qu'il peut relever\n",
    "du tirage au sort."
   ],
   "id": "cell-008"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.stats import beta\n",
    "\n",
    "def intervalle(succes, n, confiance=0.95):\n",
    "    if n == 0:\n",
    "        return (np.nan, np.nan)\n",
    "    alpha = 1 - confiance\n",
    "    bas = beta.ppf(alpha / 2, succes, n - succes + 1) if succes > 0 else 0.0\n",
    "    haut = beta.ppf(1 - alpha / 2, succes + 1, n - succes) if succes < n else 1.0\n",
    "    return (bas, haut)\n",
    "\n",
    "def taux_inadequat(df, par):\n",
    "    inadequat = df[\"groupe_consommation\"].isin([\"pauvre\", \"limite\"])\n",
    "    out = df.assign(inadequat=inadequat).groupby(par).agg(\n",
    "        menages=(\"inadequat\", \"size\"),\n",
    "        cas=(\"inadequat\", \"sum\"),\n",
    "    )\n",
    "    out[\"taux\"] = out[\"cas\"] / out[\"menages\"]\n",
    "    bornes = out.apply(lambda r: intervalle(r[\"cas\"], r[\"menages\"]), axis=1)\n",
    "    out[\"bas\"] = [b[0] for b in bornes]\n",
    "    out[\"haut\"] = [b[1] for b in bornes]\n",
    "    return out.sort_values(\"taux\", ascending=False).round(3)\n",
    "\n",
    "taux_inadequat(valides, \"displacement_status\")"
   ],
   "id": "cell-009"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les consommations pauvre et limite sont regroupées ici parce que c'est la\n",
    "population dans laquelle se constitue une charge de cas d'assistance alimentaire,\n",
    "et parce qu'éclater un petit groupe en trois catégories ne laisse plus rien à\n",
    "estimer.\n",
    "\n",
    "Lisez les intervalles les uns contre les autres. Là où ils se recouvrent, l'ordre\n",
    "entre ces deux groupes n'est pas soutenu : vous pouvez dire que les ménages\n",
    "déplacés sont en moins bonne situation que les résidents, sans nécessairement\n",
    "pouvoir dire que les retournés le sont davantage que les déplacés.\n",
    "\n",
    "## Le moyen d'existence : là où l'échantillon s'épuise"
   ],
   "id": "cell-010"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "taux_inadequat(valides, \"main_livelihood\")"
   ],
   "id": "cell-011"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le bas de ce tableau est l'endroit où l'enquête cesse de pouvoir répondre à la\n",
    "question. Un groupe de moyens d'existence comptant une vingtaine de ménages\n",
    "produit un taux dont l'intervalle s'étend sur vingt points ou davantage — ce\n",
    "n'est pas un constat, et le placer dans un tableau classé invite quelqu'un à agir\n",
    "dessus."
   ],
   "id": "cell-012"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "par_moyen = taux_inadequat(valides, \"main_livelihood\")\n",
    "par_moyen[\"largeur intervalle\"] = (\n",
    "    par_moyen[\"haut\"] - par_moyen[\"bas\"]\n",
    ").round(3)\n",
    "par_moyen[[\"menages\", \"taux\", \"largeur intervalle\"]]"
   ],
   "id": "cell-013"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Une règle pratique :** fixez un effectif minimal de cellule avant de regarder\n",
    "les résultats, inscrivez-le dans le plan d'analyse, et rapportez les groupes en\n",
    "deçà en une seule ligne « autres » avec leur taux combiné. Le fixer après coup est\n",
    "la manière dont un seuil finit par être choisi pour faire mal paraître un groupe\n",
    "en particulier.\n",
    "\n",
    "## Les ventilations croisées s'épuisent plus vite"
   ],
   "id": "cell-014"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "croise = pd.crosstab(\n",
    "    valides[\"displacement_status\"], valides[\"sexe_chef\"],\n",
    "    values=valides[\"groupe_consommation\"].isin([\"pauvre\", \"limite\"]),\n",
    "    aggfunc=\"mean\",\n",
    ").round(3)\n",
    "\n",
    "effectifs = pd.crosstab(valides[\"displacement_status\"], valides[\"sexe_chef\"])\n",
    "\n",
    "pd.concat({\"taux\": croise, \"menages\": effectifs}, axis=1)"
   ],
   "id": "cell-015"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Chaque dimension supplémentaire divise à nouveau l'échantillon. Une ventilation à\n",
    "trois entrées de cette enquête — déplacement par sexe par moyen d'existence —\n",
    "placerait des effectifs à un chiffre dans la plupart des cellules, et un tableau\n",
    "de pourcentages calculé sur quatre ménages n'est pas une donnée probante, aussi\n",
    "proprement s'affiche-t-il.\n",
    "\n",
    "## Ce qu'il faut rapporter\n",
    "\n",
    "Les ventilations que l'échantillon soutient, avec leurs intervalles, et un énoncé\n",
    "explicite de celles qu'il ne soutient pas. « Nous n'avons pas pu estimer\n",
    "séparément l'insécurité alimentaire des ménages de pêcheurs, à peine une centaine\n",
    "ayant été enquêtés » est une phrase utile. Son absence est ce qui conduit un\n",
    "lecteur à supposer que toutes les lignes de votre tableau se valent."
   ],
   "id": "cell-016"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "file_extension": ".py"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
