{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Aboutissement des référencements et points de rupture du circuit\n",
    "\n",
    "*Référencements de protection, 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/protection-referrals-2024/referral-completion.python.fr.ipynb)"
   ],
   "id": "cell-000"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Avant tout code\n",
    "\n",
    "Ce jeu de données modélise des données de protection et de VBG. Il est\n",
    "synthétique — aucune personne réelle n'y est décrite — et il ne doit jamais\n",
    "servir de modèle pour stocker de vraies données de cas. La version sûre de cela\n",
    "est un système de gestion de cas encadré par le consentement, non un CSV.\n",
    "\n",
    "Remarquez ce qui **n'y figure pas** : ni noms, ni coordonnées, ni texte libre, ni\n",
    "date d'incident, ni localisation en deçà de l'admin2, ni âge exact, ni type\n",
    "d'incident, ni détail sur l'auteur. Rien de tout cela n'est nécessaire pour\n",
    "mesurer si un circuit de référencement fonctionne, et selon les principes de\n",
    "gestion de l'information VBG les champs au niveau de l'incident ne sortent jamais\n",
    "de l'agence de gestion de cas. Collecter moins que ce que l'on pourrait est la\n",
    "discipline ici modélisée.\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",
    "    \"protection-referrals-2024.v1.csv\"\n",
    ")\n",
    "\n",
    "cas = pd.read_csv(URL, dtype={\"case_id\": \"string\"})\n",
    "print(cas.shape)\n",
    "cas.head()"
   ],
   "id": "cell-002"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Le consentement conditionne le dénominateur\n",
    "\n",
    "**Le dénominateur de l'aboutissement, ce sont les cas ayant consenti au\n",
    "référencement, non l'ensemble des cas.** Compter un cas non consentant comme un\n",
    "échec du circuit déforme à la fois la performance et la décision d'une personne :\n",
    "le circuit a fait exactement ce qu'il devait lorsque quelqu'un a refusé."
   ],
   "id": "cell-003"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"cas                        : {len(cas)}\")\n",
    "print(f\"ont consenti au referencement : {cas['consent_to_refer'].sum()} \"\n",
    "      f\"({cas['consent_to_refer'].mean():.1%})\")\n",
    "\n",
    "consentants = cas[cas[\"consent_to_refer\"]].copy()\n",
    "print(f\"ont atteint un service     : {consentants['referral_accepted'].sum()} \"\n",
    "      f\"({consentants['referral_accepted'].mean():.1%})\")"
   ],
   "id": "cell-004"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Deux chiffres, deux significations distinctes. Les 88,5 % mesurent la manière\n",
    "dont le service a été proposé — si les personnes se sont senties assez en\n",
    "sécurité pour accepter un référencement. Les 46 % mesurent le circuit. Ne\n",
    "rapporter que le second, au regard de l'ensemble des cas, donnerait un taux\n",
    "d'aboutissement d'environ 41 % et imputerait au circuit les 11,5 % de refus.\n",
    "\n",
    "## Résoudre les contradictions avant d'accorder foi à quoi que ce soit\n",
    "\n",
    "Onze cas consignent un délai jusqu'au premier service alors que le référencement\n",
    "n'a jamais été accepté. Six d'entre eux ne portent aucun référencement effectué.\n",
    "Ce sont des impossibilités logiques, et un taux d'aboutissement calculé sur elles\n",
    "est calculé sur des enregistrements qui ne peuvent pas tous être exacts."
   ],
   "id": "cell-005"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "delai_consigne = cas[\"days_to_first_service\"].notna()\n",
    "\n",
    "contradictions = pd.DataFrame({\n",
    "    \"delai consigne, non accepte\": [\n",
    "        int((~cas[\"referral_accepted\"] & delai_consigne).sum())\n",
    "    ],\n",
    "    \"delai consigne, aucun referencement\": [\n",
    "        int((~cas[\"referral_made\"] & delai_consigne).sum())\n",
    "    ],\n",
    "    \"delai consigne, sans consentement\": [\n",
    "        int((~cas[\"consent_to_refer\"] & delai_consigne).sum())\n",
    "    ],\n",
    "    \"accepte, aucun delai consigne\": [\n",
    "        int((cas[\"referral_accepted\"] & ~delai_consigne).sum())\n",
    "    ],\n",
    "}).T\n",
    "contradictions.columns = [\"cas\"]\n",
    "contradictions"
   ],
   "id": "cell-006"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La dernière ligne compte autant que les autres : quarante référencements acceptés\n",
    "n'ont aucun délai consigné, donc **le dénominateur de la promptitude est plus\n",
    "petit que celui de l'aboutissement.** Employer l'un pour l'autre fausse les deux."
   ],
   "id": "cell-007"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cas[\"contradictoire\"] = (~cas[\"referral_accepted\"]) & delai_consigne\n",
    "print(f\"signales comme contradictoires : {int(cas['contradictoire'].sum())}\")"
   ],
   "id": "cell-008"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Signalez-les plutôt que de les supprimer. Dans un contexte de gestion de cas, un\n",
    "enregistrement contradictoire est un problème de saisie à renvoyer au\n",
    "travailleur social, et le supprimer détruit la seule trace de l'existence du cas.\n",
    "\n",
    "## Normaliser le champ handicap avant de désagréger dessus\n",
    "\n",
    "Une zone a consigné le handicap en `Yes` et `No` plutôt qu'en `true` et `false`.\n",
    "Laissée telle quelle, la désagrégation se fragmente en quatre catégories, dont\n",
    "deux trop petites pour être interprétées — et ces deux-là proviennent d'une seule\n",
    "zone : ce n'est donc pas un sous-ensemble aléatoire."
   ],
   "id": "cell-009"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(cas[\"disability_reported\"].value_counts(dropna=False))"
   ],
   "id": "cell-010"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "HANDICAP = {\"true\": True, \"yes\": True, \"false\": False, \"no\": False}\n",
    "cas[\"handicap\"] = (\n",
    "    cas[\"disability_reported\"].astype(\"string\").str.strip().str.lower().map(HANDICAP)\n",
    ")\n",
    "consentants = cas[cas[\"consent_to_refer\"]].copy()\n",
    "print(consentants[\"handicap\"].value_counts(dropna=False))"
   ],
   "id": "cell-011"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Où le circuit se rompt"
   ],
   "id": "cell-012"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def aboutissement(df, par):\n",
    "    out = df.groupby(par).agg(\n",
    "        cas=(\"referral_accepted\", \"size\"),\n",
    "        aboutis=(\"referral_accepted\", \"sum\"),\n",
    "    )\n",
    "    out[\"taux\"] = (out[\"aboutis\"] / out[\"cas\"]).round(3)\n",
    "    return out.sort_values(\"taux\")\n",
    "\n",
    "aboutissement(consentants, \"service_requested\")"
   ],
   "id": "cell-013"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le soutien aux moyens d'existence aboutit à environ 23 % contre environ 62 % pour\n",
    "la santé. Ce n'est pas un écart de performance des travailleurs sociaux : c'est\n",
    "un énoncé sur les services qui existent et disposent de capacités. Une analyse de\n",
    "circuit qui s'arrête à 46 % global escamote tout le constat."
   ],
   "id": "cell-014"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "aboutissement(consentants, \"admin2\")"
   ],
   "id": "cell-015"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Entre environ 31 % et 57 % selon les six zones. Croisez les deux ventilations et\n",
    "le point de rupture devient localisable :"
   ],
   "id": "cell-016"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grille = pd.crosstab(\n",
    "    consentants[\"admin2\"],\n",
    "    consentants[\"service_requested\"],\n",
    "    values=consentants[\"referral_accepted\"],\n",
    "    aggfunc=\"mean\",\n",
    ").round(2)\n",
    "\n",
    "effectifs = pd.crosstab(consentants[\"admin2\"], consentants[\"service_requested\"])\n",
    "grille.where(effectifs >= 20)"
   ],
   "id": "cell-017"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les cellules de moins de vingt cas sont laissées vides plutôt qu'affichées. Un\n",
    "taux d'aboutissement sur huit cas n'est pas un constat, et en protection une\n",
    "petite cellule constitue aussi un risque de divulgation — voir plus bas.\n",
    "\n",
    "## Le constat d'équité"
   ],
   "id": "cell-018"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "par_handicap = aboutissement(consentants.dropna(subset=[\"handicap\"]), \"handicap\")\n",
    "par_handicap"
   ],
   "id": "cell-019"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les cas où un handicap est signalé aboutissent à environ 31 % contre 48 % lorsque\n",
    "aucun ne l'est. C'est le constat que ce jeu de données existe pour faire\n",
    "apparaître, et il n'apparaît que si l'on désagrège."
   ],
   "id": "cell-020"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.stats import chi2_contingency\n",
    "\n",
    "table = pd.crosstab(\n",
    "    consentants.dropna(subset=[\"handicap\"])[\"handicap\"],\n",
    "    consentants.dropna(subset=[\"handicap\"])[\"referral_accepted\"],\n",
    ")\n",
    "chi2, p, dof, attendu = chi2_contingency(table)\n",
    "print(f\"ecart d'aboutissement : \"\n",
    "      f\"{par_handicap['taux'].iloc[-1] - par_handicap['taux'].iloc[0]:+.3f}\")\n",
    "print(f\"p du khi-deux         : {p:.2e}\")"
   ],
   "id": "cell-021"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un écart de dix-sept points, peu susceptible d'être dû au hasard. Ce qu'il ne\n",
    "vous dit pas, c'est *pourquoi* — si les services sont physiquement inaccessibles,\n",
    "si les circuits supposent une mobilité que certains clients n'ont pas, ou tout\n",
    "autre chose. À cette question répondent les travailleurs sociaux, non ce tableau.\n",
    "\n",
    "## Les petites cellules sont un risque de protection, pas seulement statistique"
   ],
   "id": "cell-022"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sensibles = pd.crosstab(consentants[\"admin2\"], consentants[\"case_category\"])\n",
    "sensibles.where(sensibles < 20)"
   ],
   "id": "cell-023"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ici, toutes les cellules dépassent vingt : rien n'est supprimé — et ce résultat\n",
    "vide est précisément la raison pour laquelle ce contrôle doit rester dans la\n",
    "chaîne plutôt que d'être exécuté une seule fois. Dans un tableau par district,\n",
    "une cellule de trois cas de VBG peut identifier une survivante pour quiconque\n",
    "connaît la zone. Traitez le seuil de suppression comme une décision de protection\n",
    "prise avec l'agence de gestion de cas, non comme une préférence de mise en forme.\n",
    "\n",
    "## Ce qu'il faut rapporter\n",
    "\n",
    "L'aboutissement au regard du dénominateur conditionné par le consentement,\n",
    "décomposé assez finement pour localiser le point de rupture, avec les\n",
    "enregistrements contradictoires signalés et comptés. L'écart lié au handicap,\n",
    "énoncé clairement. Et rien à une granularité susceptible d'identifier une\n",
    "personne — ce qui, dans ce secteur, est la contrainte qui prime sur toute\n",
    "préférence analytique."
   ],
   "id": "cell-024"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "file_extension": ".py"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
