{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Calculer le SCA, l'ÉFM et l'ICSR à partir de leurs composantes\n",
    "\n",
    "*Enquête de sécurité alimentaire, 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/food-security-survey-2024/composite-indicators.r.fr.ipynb)"
   ],
   "id": "cell-000"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ce que produit ce document\n",
    "\n",
    "Les trois mêmes indicateurs composites que l'exemple Python, construits avec\n",
    "dplyr — y compris la règle d'exclusion des réponses incomplètes à l'échelle de la\n",
    "faim dans le ménage, l'étape que la plupart des implémentations manquent.\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"
   ],
   "id": "cell-001"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| message: false\n",
    "library(readr)\n",
    "library(dplyr)\n",
    "library(tidyr)\n",
    "\n",
    "URL <- paste0(\n",
    "  \"https://data-analysis.cassion.dev/datasets/files/\",\n",
    "  \"food-security-survey-2024.v1.csv\"\n",
    ")\n",
    "\n",
    "fs <- read_csv(URL, col_types = cols(\n",
    "  household_id = col_character(),\n",
    "  .default     = col_guess()\n",
    "))\n",
    "\n",
    "glimpse(fs)"
   ],
   "id": "cell-002"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Contrôler les plages avant de scorer\n",
    "\n",
    "Vingt-trois enregistrements portent une valeur de consommation supérieure à sept\n",
    "jours, impossible pour un rappel de sept jours."
   ],
   "id": "cell-003"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "composantes <- c(\n",
    "  \"fcs_cereals_tubers\", \"fcs_pulses\", \"fcs_vegetables\", \"fcs_fruit\",\n",
    "  \"fcs_meat_fish_eggs\", \"fcs_dairy\", \"fcs_oils_fats\", \"fcs_sugar\"\n",
    ")\n",
    "\n",
    "fs |>\n",
    "  summarise(across(all_of(composantes), list(min = ~min(.x, na.rm = TRUE),\n",
    "                                             max = ~max(.x, na.rm = TRUE)))) |>\n",
    "  pivot_longer(everything())"
   ],
   "id": "cell-004"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Hors plage n'est pas une mesure. NA plutot que troncature a 7 : tronquer\n",
    "# invente une valeur que l'enqueteur n'a jamais relevee.\n",
    "fs <- fs |>\n",
    "  mutate(across(all_of(composantes), ~ if_else(.x >= 0 & .x <= 7, .x, NA_real_)))\n",
    "\n",
    "cat(\"cellules vides ou hors plage :\", sum(is.na(fs[composantes])), \"\\n\")"
   ],
   "id": "cell-005"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La case vide qui n'est pas un zéro\n",
    "\n",
    "`rowSums(..., na.rm = TRUE)` est le piège. Il traite une case vide comme zéro\n",
    "jour, score le ménage comme mangeant moins qu'il n'a mangé, et n'émet aucun\n",
    "avertissement. Les composantes le plus souvent vides portent les pondérations les\n",
    "plus lourdes — produits laitiers et viande valent 4 chacune.\n",
    "\n",
    "La multiplication matricielle est employée ici plutôt que `rowSums`, précisément\n",
    "parce qu'elle propage `NA` par défaut : un ménage incomplet ne reçoit aucun score,\n",
    "ce qui est le comportement correct."
   ],
   "id": "cell-006"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ponderations <- c(\n",
    "  fcs_cereals_tubers = 2, fcs_pulses = 3, fcs_vegetables = 1, fcs_fruit = 1,\n",
    "  fcs_meat_fish_eggs = 4, fcs_dairy = 4, fcs_oils_fats = 0.5, fcs_sugar = 0.5\n",
    ")\n",
    "\n",
    "observe <- as.matrix(fs[, names(ponderations)])\n",
    "zeros   <- observe\n",
    "zeros[is.na(zeros)] <- 0\n",
    "\n",
    "fs <- fs |>\n",
    "  mutate(\n",
    "    sca_complet = if_all(all_of(composantes), ~ !is.na(.x)),\n",
    "    sca         = as.vector(observe %*% ponderations),\n",
    "    sca_zeros   = as.vector(zeros   %*% ponderations)\n",
    "  )\n",
    "\n",
    "fs |>\n",
    "  summarise(\n",
    "    incomplets       = sum(!sca_complet),\n",
    "    moyenne_complets = round(mean(sca[sca_complet]), 1),\n",
    "    moyenne_zeros    = round(mean(sca_zeros[!sca_complet]), 1)\n",
    "  )"
   ],
   "id": "cell-007"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les ménages remplis de zéros se situent en moyenne six points sous ceux qui ont\n",
    "répondu intégralement. Cet écart correspond aux cases vides comptées comme\n",
    "journées sans manger, et non à un constat sur leur alimentation."
   ],
   "id": "cell-008"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "part <- function(scores, pauvre, limite) {\n",
    "  s <- scores[!is.na(scores)]\n",
    "  tibble(\n",
    "    menages     = length(s),\n",
    "    pauvre_pct  = round(100 * mean(s <= pauvre), 2),\n",
    "    limite_pct  = round(100 * mean(s > pauvre & s <= limite), 2)\n",
    "  )\n",
    "}\n",
    "\n",
    "bind_rows(\n",
    "  part(fs$sca, 21, 35)       |> mutate(traitement = \"exclure les incomplets\", seuils = \"21/35\"),\n",
    "  part(fs$sca_zeros, 21, 35) |> mutate(traitement = \"remplir de zeros\",       seuils = \"21/35\"),\n",
    "  part(fs$sca, 28, 42)       |> mutate(traitement = \"exclure les incomplets\", seuils = \"28/42\"),\n",
    "  part(fs$sca_zeros, 28, 42) |> mutate(traitement = \"remplir de zeros\",       seuils = \"28/42\")\n",
    ") |>\n",
    "  select(seuils, traitement, menages, pauvre_pct, limite_pct)"
   ],
   "id": "cell-009"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aux seuils 21/35 la distorsion est faible. Aux seuils 28/42, elle ne l'est plus :\n",
    "trente-deux des ménages incomplets sont classés en consommation alimentaire\n",
    "pauvre sur des scores artificiellement bas, et chacun d'eux serait comptabilisé\n",
    "dans une charge de cas.\n",
    "\n",
    "## Les deux jeux de seuils, côte à côte"
   ],
   "id": "cell-010"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "groupe_consommation <- function(score, pauvre, limite) {\n",
    "  case_when(\n",
    "    is.na(score)     ~ NA_character_,\n",
    "    score <= pauvre  ~ \"pauvre\",\n",
    "    score <= limite  ~ \"limite\",\n",
    "    TRUE             ~ \"acceptable\"\n",
    "  )\n",
    "}\n",
    "\n",
    "valides <- fs |> filter(sca_complet)\n",
    "\n",
    "bind_rows(\n",
    "  valides |> count(groupe = groupe_consommation(sca, 21, 35)) |>\n",
    "    mutate(seuils = \"21/35\", pct = round(100 * n / sum(n), 1)),\n",
    "  valides |> count(groupe = groupe_consommation(sca, 28, 42)) |>\n",
    "    mutate(seuils = \"28/42\", pct = round(100 * n / sum(n), 1))\n",
    ") |>\n",
    "  select(seuils, groupe, n, pct) |>\n",
    "  arrange(seuils, groupe)"
   ],
   "id": "cell-011"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Retenir le jeu 28/42 relève d'un jugement sur le système alimentaire — il\n",
    "s'emploie là où l'huile et le sucre sont consommés de manière quasi universelle —\n",
    "et il fait passer le chiffre de tête d'environ 1 % à environ 7 % de consommation\n",
    "pauvre. Énoncez le jeu de seuils employé, dans la même phrase que le chiffre.\n",
    "\n",
    "## L'échelle de la faim dans le ménage, et sa règle d'exclusion"
   ],
   "id": "cell-012"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "items_efm <- c(\n",
    "  \"hhs_no_food_in_house\", \"hhs_sleep_hungry\",\n",
    "  \"hhs_day_and_night_without_eating\"\n",
    ")\n",
    "\n",
    "fs <- fs |>\n",
    "  mutate(\n",
    "    efm_complet = if_all(all_of(items_efm), ~ !is.na(.x)),\n",
    "    efm = if_else(\n",
    "      efm_complet,\n",
    "      rowSums(pick(all_of(items_efm)), na.rm = FALSE),\n",
    "      NA_real_\n",
    "    ),\n",
    "    categorie_efm = cut(\n",
    "      efm, c(-1, 1, 3, 6),\n",
    "      labels = c(\"faible ou nulle\", \"moderee\", \"severe\")\n",
    "    )\n",
    "  )\n",
    "\n",
    "cat(\"reponses partielles exclues :\", sum(!fs$efm_complet), \"\\n\")\n",
    "\n",
    "fs |>\n",
    "  filter(efm_complet) |>\n",
    "  count(categorie_efm) |>\n",
    "  mutate(pct = round(100 * n / sum(n), 1))"
   ],
   "id": "cell-013"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C'est `na.rm = FALSE` qui fait le travail, et le `if_else` sur `efm_complet` qui\n",
    "rend l'exclusion explicite plutôt qu'implicite. Passez à `na.rm = TRUE` et un\n",
    "ménage ayant répondu à deux questions sur trois est scoré comme s'il avait\n",
    "répondu zéro à la troisième — autrement dit, comme étant en sécurité alimentaire.\n",
    "\n",
    "## L'indice réduit des stratégies de survie"
   ],
   "id": "cell-014"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ponderations_icsr <- c(\n",
    "  rcsi_less_preferred_food = 1, rcsi_borrowed_food = 2,\n",
    "  rcsi_limit_portion_size = 1, rcsi_restrict_adult_consumption = 3,\n",
    "  rcsi_reduce_meal_numbers = 1\n",
    ")\n",
    "\n",
    "fs <- fs |>\n",
    "  mutate(\n",
    "    icsr = as.vector(as.matrix(pick(all_of(names(ponderations_icsr)))) %*% ponderations_icsr)\n",
    "  )\n",
    "\n",
    "summary(fs$icsr)"
   ],
   "id": "cell-015"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ce ne sont pas des approximations l'un de l'autre"
   ],
   "id": "cell-016"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "valides <- fs |> filter(sca_complet)\n",
    "cor(valides$sca, valides$icsr) |> round(3)"
   ],
   "id": "cell-017"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Environ -0,45. Un ménage peut manger de façon monotone sans avoir encore recours\n",
    "à des stratégies d'adaptation, et un autre peut s'adapter fortement tout en\n",
    "conservant un régime varié grâce à des aliments empruntés. Rapporter l'un à la\n",
    "place de l'autre perd une information réelle.\n",
    "\n",
    "## Ce que cela produit, et ce que cela ne produit pas\n",
    "\n",
    "Des données probantes de consommation alimentaire utilisées **dans** une analyse\n",
    "IPC — non une phase IPC. Une phase est attribuée par un groupe de travail\n",
    "technique qui fait converger plusieurs indicateurs de résultat avec les facteurs\n",
    "contributifs, et afficher « phase 3 » à partir d'une distribution de SCA escamote\n",
    "tout le processus que la classification existe pour représenter."
   ],
   "id": "cell-018"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "R",
   "language": "R",
   "name": "ir"
  },
  "language_info": {
   "name": "R",
   "file_extension": ".r"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
