---
title: "Calculer le SCA, l'ÉFM et l'ICSR à partir de leurs composantes"
subtitle: "Enquête de sécurité alimentaire, 2024 · R"
author: "Cassion · data-analysis.cassion.dev"
format:
  html:
    toc: true
    code-fold: false
---

## Ce que produit ce document

Les trois mêmes indicateurs composites que l'exemple Python, construits avec
dplyr — y compris la règle d'exclusion des réponses incomplètes à l'échelle de la
faim dans le ménage, l'étape que la plupart des implémentations manquent.

Tous les jeux de données de cette plateforme sont synthétiques. Aucun ménage réel
n'est décrit.

## Mise en place

```{r}
#| message: false
library(readr)
library(dplyr)
library(tidyr)

URL <- paste0(
  "https://data-analysis.cassion.dev/datasets/files/",
  "food-security-survey-2024.v1.csv"
)

fs <- read_csv(URL, col_types = cols(
  household_id = col_character(),
  .default     = col_guess()
))

glimpse(fs)
```

## Contrôler les plages avant de scorer

Vingt-trois enregistrements portent une valeur de consommation supérieure à sept
jours, impossible pour un rappel de sept jours.

```{r}
composantes <- c(
  "fcs_cereals_tubers", "fcs_pulses", "fcs_vegetables", "fcs_fruit",
  "fcs_meat_fish_eggs", "fcs_dairy", "fcs_oils_fats", "fcs_sugar"
)

fs |>
  summarise(across(all_of(composantes), list(min = ~min(.x, na.rm = TRUE),
                                             max = ~max(.x, na.rm = TRUE)))) |>
  pivot_longer(everything())
```

```{r}
# Hors plage n'est pas une mesure. NA plutot que troncature a 7 : tronquer
# invente une valeur que l'enqueteur n'a jamais relevee.
fs <- fs |>
  mutate(across(all_of(composantes), ~ if_else(.x >= 0 & .x <= 7, .x, NA_real_)))

cat("cellules vides ou hors plage :", sum(is.na(fs[composantes])), "\n")
```

## La case vide qui n'est pas un zéro

`rowSums(..., na.rm = TRUE)` est le piège. Il traite une case vide comme zéro
jour, score le ménage comme mangeant moins qu'il n'a mangé, et n'émet aucun
avertissement. Les composantes le plus souvent vides portent les pondérations les
plus lourdes — produits laitiers et viande valent 4 chacune.

La multiplication matricielle est employée ici plutôt que `rowSums`, précisément
parce qu'elle propage `NA` par défaut : un ménage incomplet ne reçoit aucun score,
ce qui est le comportement correct.

```{r}
ponderations <- c(
  fcs_cereals_tubers = 2, fcs_pulses = 3, fcs_vegetables = 1, fcs_fruit = 1,
  fcs_meat_fish_eggs = 4, fcs_dairy = 4, fcs_oils_fats = 0.5, fcs_sugar = 0.5
)

observe <- as.matrix(fs[, names(ponderations)])
zeros   <- observe
zeros[is.na(zeros)] <- 0

fs <- fs |>
  mutate(
    sca_complet = if_all(all_of(composantes), ~ !is.na(.x)),
    sca         = as.vector(observe %*% ponderations),
    sca_zeros   = as.vector(zeros   %*% ponderations)
  )

fs |>
  summarise(
    incomplets       = sum(!sca_complet),
    moyenne_complets = round(mean(sca[sca_complet]), 1),
    moyenne_zeros    = round(mean(sca_zeros[!sca_complet]), 1)
  )
```

Les ménages remplis de zéros se situent en moyenne six points sous ceux qui ont
répondu intégralement. Cet écart correspond aux cases vides comptées comme
journées sans manger, et non à un constat sur leur alimentation.

```{r}
part <- function(scores, pauvre, limite) {
  s <- scores[!is.na(scores)]
  tibble(
    menages     = length(s),
    pauvre_pct  = round(100 * mean(s <= pauvre), 2),
    limite_pct  = round(100 * mean(s > pauvre & s <= limite), 2)
  )
}

bind_rows(
  part(fs$sca, 21, 35)       |> mutate(traitement = "exclure les incomplets", seuils = "21/35"),
  part(fs$sca_zeros, 21, 35) |> mutate(traitement = "remplir de zeros",       seuils = "21/35"),
  part(fs$sca, 28, 42)       |> mutate(traitement = "exclure les incomplets", seuils = "28/42"),
  part(fs$sca_zeros, 28, 42) |> mutate(traitement = "remplir de zeros",       seuils = "28/42")
) |>
  select(seuils, traitement, menages, pauvre_pct, limite_pct)
```

Aux seuils 21/35 la distorsion est faible. Aux seuils 28/42, elle ne l'est plus :
trente-deux des ménages incomplets sont classés en consommation alimentaire
pauvre sur des scores artificiellement bas, et chacun d'eux serait comptabilisé
dans une charge de cas.

## Les deux jeux de seuils, côte à côte

```{r}
groupe_consommation <- function(score, pauvre, limite) {
  case_when(
    is.na(score)     ~ NA_character_,
    score <= pauvre  ~ "pauvre",
    score <= limite  ~ "limite",
    TRUE             ~ "acceptable"
  )
}

valides <- fs |> filter(sca_complet)

bind_rows(
  valides |> count(groupe = groupe_consommation(sca, 21, 35)) |>
    mutate(seuils = "21/35", pct = round(100 * n / sum(n), 1)),
  valides |> count(groupe = groupe_consommation(sca, 28, 42)) |>
    mutate(seuils = "28/42", pct = round(100 * n / sum(n), 1))
) |>
  select(seuils, groupe, n, pct) |>
  arrange(seuils, groupe)
```

Retenir le jeu 28/42 relève d'un jugement sur le système alimentaire — il
s'emploie là où l'huile et le sucre sont consommés de manière quasi universelle —
et il fait passer le chiffre de tête d'environ 1 % à environ 7 % de consommation
pauvre. Énoncez le jeu de seuils employé, dans la même phrase que le chiffre.

## L'échelle de la faim dans le ménage, et sa règle d'exclusion

```{r}
items_efm <- c(
  "hhs_no_food_in_house", "hhs_sleep_hungry",
  "hhs_day_and_night_without_eating"
)

fs <- fs |>
  mutate(
    efm_complet = if_all(all_of(items_efm), ~ !is.na(.x)),
    efm = if_else(
      efm_complet,
      rowSums(pick(all_of(items_efm)), na.rm = FALSE),
      NA_real_
    ),
    categorie_efm = cut(
      efm, c(-1, 1, 3, 6),
      labels = c("faible ou nulle", "moderee", "severe")
    )
  )

cat("reponses partielles exclues :", sum(!fs$efm_complet), "\n")

fs |>
  filter(efm_complet) |>
  count(categorie_efm) |>
  mutate(pct = round(100 * n / sum(n), 1))
```

C'est `na.rm = FALSE` qui fait le travail, et le `if_else` sur `efm_complet` qui
rend l'exclusion explicite plutôt qu'implicite. Passez à `na.rm = TRUE` et un
ménage ayant répondu à deux questions sur trois est scoré comme s'il avait
répondu zéro à la troisième — autrement dit, comme étant en sécurité alimentaire.

## L'indice réduit des stratégies de survie

```{r}
ponderations_icsr <- c(
  rcsi_less_preferred_food = 1, rcsi_borrowed_food = 2,
  rcsi_limit_portion_size = 1, rcsi_restrict_adult_consumption = 3,
  rcsi_reduce_meal_numbers = 1
)

fs <- fs |>
  mutate(
    icsr = as.vector(as.matrix(pick(all_of(names(ponderations_icsr)))) %*% ponderations_icsr)
  )

summary(fs$icsr)
```

## Ce ne sont pas des approximations l'un de l'autre

```{r}
valides <- fs |> filter(sca_complet)
cor(valides$sca, valides$icsr) |> round(3)
```

Environ -0,45. Un ménage peut manger de façon monotone sans avoir encore recours
à des stratégies d'adaptation, et un autre peut s'adapter fortement tout en
conservant un régime varié grâce à des aliments empruntés. Rapporter l'un à la
place de l'autre perd une information réelle.

## Ce que cela produit, et ce que cela ne produit pas

Des données probantes de consommation alimentaire utilisées **dans** une analyse
IPC — non une phase IPC. Une phase est attribuée par un groupe de travail
technique qui fait converger plusieurs indicateurs de résultat avec les facteurs
contributifs, et afficher « phase 3 » à partir d'une distribution de SCA escamote
tout le processus que la classification existe pour représenter.
