cassionAnalyse de données

Leçon 6 sur 8

Unité · La charge de dossiers

Les dossiers non clos sont les longs

Le délai médian de clôture vaut quatre mois sur les 636 dossiers clos. 472 autres sont encore ouverts, dont 140 dépassent déjà quatre mois, et la protection de l'enfance est censurée à 58 % contre 32 % pour la protection générale — la même statistique signifie donc des choses différentes selon la colonne.

PythonR120 minNorme humanitaire fondamentale (CHS)Définitions d'indicateurs de l'UNICEFCritères d'évaluation du CAD de l'OCDE

Le calcul évident est biaisé

import pandas as pd

cases = pd.read_csv("protection-case-management-2024.v1.csv")

opened = cases["opened_month"].str[5:].astype(int)
closed = cases["closed_month"].str[5:].astype("Int64")
duration = closed - opened

print(f"closed cases: {duration.notna().sum()}")
print(f"median months to closure: {duration.median()}")
print(f"still open: {duration.isna().sum()} ({duration.isna().mean():.1%})")
library(dplyr)

cases |>
  mutate(opened = as.integer(substr(opened_month, 6, 7)),
         closed = as.integer(substr(closed_month, 6, 7)),
         duration = closed - opened) |>
  summarise(closed = sum(!is.na(duration)),
            median = median(duration, na.rm = TRUE),
            open = sum(is.na(duration)))

Quatre mois, sur 636 dossiers sur 1 108. Les 472 autres sont encore ouverts à la clôture de décembre, et ils ne sont pas manquants — ils sont censurés.

Le biais a un sens et il se voit directement.

still_open = 12 - opened[duration.isna()] + 1
print(f"open cases already past 4 months: {(still_open > 4).sum()}")
print(f"longest open: {still_open.max()} months and counting")
cases |> filter(is.na(closed_month)) |>
  mutate(so_far = 12 - as.integer(substr(opened_month, 6, 7)) + 1) |>
  summarise(past_four = sum(so_far > 4), longest = max(so_far))

140 des dossiers ouverts le sont déjà depuis plus longtemps que le dossier clos médian, et l’un l’est depuis onze mois. Chacun d’eux se clôturera à une durée supérieure à quatre mois, ou jamais. Les supprimer rend la réponse plus petite que la vérité, systématiquement.

Corrigez d’abord les durées impossibles

impossible = duration.notna() & (duration <= 0)
print(f"cases closing on or before the month they opened: {impossible.sum()}")
print(cases.loc[impossible, ["opened_month", "closed_month"]])
cases |> filter(!is.na(closed_month),
                closed_month <= opened_month) |> nrow()

Sept dossiers se clôturent un mois antérieur à leur ouverture. La contradiction est invisible dans chaque colonne prise seule et produit des durées négatives qu’un median() absorbe sans broncher.

Décidez et consignez : les exclure, ou traiter le mois de clôture comme une faute de saisie et le fixer au mois d’ouverture. Sept dossiers ne déplaceront pas la médiane ; c’est la discipline de les trouver et de les déclarer qui manquera la prochaine fois si vous la sautez.

Trois façons honnêtes de le rapporter

Rapporter la médiane avec la censure énoncée. L’option correcte la moins coûteuse.

Median time to closure: 4 months (636 closed cases).
472 cases (42.6%) were still open at the cut-off and are excluded; 140 of
them have already been open longer than 4 months, so the true median is
higher.

Rapporter une courbe d’aboutissement par mois. Quelle part des dossiers ouverts au mois m était close dans les k mois, calculée uniquement sur les dossiers disposant d’au moins k mois de suivi. C’est l’approche par cohorte et elle emploie correctement les dossiers censurés tant qu’ils ont été observés.

def closed_within(k):
    eligible = cases[opened <= 12 - k]
    dur = (eligible["closed_month"].str[5:].astype("Int64")
           - eligible["opened_month"].str[5:].astype(int))
    return (dur <= k).sum() / len(eligible), len(eligible)

for k in (3, 6, 9):
    share, n = closed_within(k)
    print(f"closed within {k} months: {share:.1%} (n={n})")
# For each k, restrict to cases with k months of possible follow-up.

Rapporter le délai de clôture pour une seule cohorte à suivi complet. Les dossiers ouverts en janvier ont onze mois d’observation ; ceux de novembre en ont un. Se restreindre à la cohorte précoce répond honnêtement à une question plus étroite.

Les trois sont défendables et la première ne suffit pas seule. Une médiane sans énoncé de censure à côté est la version qui sera citée.

La censure n’est pas répartie uniformément

by_category = pd.DataFrame({
    "closed": duration.notna().groupby(cases["case_category"]).sum(),
    "open": duration.isna().groupby(cases["case_category"]).sum(),
})
by_category["censored"] = by_category["open"] / by_category.sum(axis=1)
print(by_category.round(3))
cases |> summarise(closed = sum(!is.na(closed_month)),
                   open = sum(is.na(closed_month)), .by = case_category) |>
  mutate(censored = open / (open + closed))
Catégorie Clos Ouverts Censure Médiane des clos
Protection de l’enfance 131 179 58 % 5 mois
VBG 255 173 40 % 4 mois
Protection générale 250 120 32 % 4 mois

La protection de l’enfance est censurée à 58 % et la protection générale à 32 %. Les deux médianes de ce tableau sont donc calculées sur des fractions très différentes de leurs cohortes, et l’écart entre cinq mois et quatre mois sous-estime l’écart réel.

Une censure inégale rend une comparaison de médianes trompeuse dans un sens précis : le groupe le plus censuré est celui dont la médiane est la plus sous-estimée, c’est-à-dire le groupe qui paraît déjà le plus mal loti.

Le motif de clôture est un jugement

print(cases["closure_reason"].value_counts(normalize=True).round(3))
cases |> filter(!is.na(closure_reason)) |> count(closure_reason) |>
  mutate(share = n / sum(n))
Motif Part des clôtures
Objectifs du plan atteints 29,1 %
Perte de contact 27,2 %
Retrait de la survivante 13,1 %
Clôture administrative 11,3 %
Déménagement 9,7 %
Transfert à une autre agence 9,6 %

Seules 29,1 % des clôtures correspondent à un plan mené à terme. C’est le chiffre principal, et c’est celui par lequel commence une conversation de supervision.

Mais regardez comment les motifs se répartissent par zone avant d’y croire.

by_area = pd.crosstab(cases["admin2"], cases["closure_reason"], normalize="index")
print((by_area[["closed-administratively", "lost-contact"]] * 100).round(1))
cases |> filter(!is.na(closure_reason)) |>
  count(admin2, closure_reason) |> mutate(share = n / sum(n), .by = admin2)

Hinche classe 36,2 % de ses clôtures en closed-administratively contre 3,8 % à 9,8 % partout ailleurs, et son taux de perte de contact affiche 20,2 % contre jusqu’à 38,9 %. Le fourre-tout absorbe le motif dont un superviseur a besoin.

Un motif de clôture est le jugement d’un travailleur social, non une observation. Une distribution qui diffère fortement d’un bureau à l’autre est donc une question sur les bureaux avant d’être une question sur les dossiers — et le remède est une conversation sur le codage, non un ajustement statistique.

Rapportez-le en entier

Case closure, 1,108 cases

  Closed by the cut-off              636    57.4%
  Still open                         472    42.6%    censored, not missing
  Median months to closure             4              of closed cases only
    open cases already past 4 months  140              so the true median is higher

  Censoring by category: child protection 58%, GBV 40%, general 32%.
  Medians are not comparable across those columns.

  Closure reasons (of 636 closures)
    Case plan objectives met       29.1%
    Lost contact                   27.2%
    Survivor withdrew              13.1%
    Closed administratively        11.3%    36.2% in Hinche, 3.8-9.8% elsewhere
    Relocated                       9.7%
    Transferred                     9.6%

  7 cases record a closing month before their opening month and are excluded.

La suite

Tout dans cette unité porte sur des dossiers déjà dans le système. La dernière unité demande ce que signifie le nombre de dossiers lui-même — et pourquoi une zone dont le compte de dossiers a doublé est généralement celle où quelque chose s’est bien passé.

Animer cette leçon

La leçon en diaporama, la prose étant reléguée dans les notes du présentateur plutôt que projetée. Produit à partir de cette page, dont il ne peut donc pas s'écarter.

Lancer le diaporamaLire les diapositives

Le PDF ne requiert aucun logiciel et se projette depuis n'importe quel poste. Le fichier PowerPoint est fait pour être modifié : appliquez la charte de votre organisation, retirez une section pour une séance plus courte, ou fusionnez deux leçons en atelier.