Leçon 2 sur 8
Unité · Ce qu'il ne faut pas collecter
Dix-neuf cellules à un
Croisez zone, catégorie, tranche d'âge et sexe et ce jeu de données anonyme produit 200 cellules, dont 85 contiennent quatre cas ou moins et 19 en contiennent exactement un. Dans un jeu de données VBG, une cellule à un est une personne, et le tableau ne peut pas sortir des murs.
Le jeu de données est anonyme et le tableau ne l’est pas
Tout identifiant direct a disparu. Cela protège contre la lecture d’une ligne permettant de savoir de qui il s’agit. Cela ne protège pas contre le comptage.
import pandas as pd
referrals = pd.read_csv("protection-referrals-2024.v1.csv")
cells = referrals.groupby(
["admin2", "case_category", "age_band", "sex"], dropna=False
).size()
print(f"{len(cells)} cells")
print(f" holding 1 case: {(cells == 1).sum()}")
print(f" holding 4 or fewer: {(cells <= 4).sum()}")
library(dplyr)
referrals |>
count(admin2, case_category, age_band, sex) |>
summarise(cells = n(), of_one = sum(n == 1), of_four_or_fewer = sum(n <= 4))
200 cellules, dont 85 avec quatre cas ou moins, et 19 avec exactement un.
Une cellule à un dit : dans ce district, il y a eu cette année un cas de VBG concernant une fille de 0 à 11 ans. Quiconque travaille dans ce district peut savoir précisément de qui il s’agit. Le jeu de données ne l’a jamais nommée ; le tableau l’a fait.
Pourquoi quatre et non un
L’instinct est de ne supprimer que les cellules à un. Cela ne suffit pas, pour deux raisons.
Une cellule à deux identifie les deux personnes l’une à l’autre, et à quiconque en connaît une.
La différenciation récupère les cellules supprimées. Si un total de ligne est publié et que toutes les cellules de cette ligne sauf une le sont aussi, la manquante est une soustraction.
by_area = referrals.groupby(["admin2", "case_category"]).size().unstack()
print(by_area)
print("\nIf one cell is suppressed and the row total is printed,")
print("the suppressed cell is the total minus the others.")
referrals |> count(admin2, case_category) |>
tidyr::pivot_wider(names_from = case_category, values_from = n)
Une règle de suppression exige donc un seuil et une suppression secondaire : supprimer les petites cellules, puis en supprimer assez d’autres pour que les petites ne soient pas récupérables par soustraction. Un seuil de cinq est le plancher courant dans ce secteur ; certaines agences emploient dix.
Une règle, écrite comme du code
THRESHOLD = 5
def suppress(counts, threshold=THRESHOLD):
"""Primary suppression, then a secondary pass so rows cannot be differenced."""
table = counts.copy()
small = table < threshold
table = table.mask(small, other=pd.NA)
# Secondary: if a row has exactly one suppressed cell, the row total gives
# it away. Suppress the next smallest cell in that row as well.
for index, row in table.iterrows():
if row.isna().sum() == 1:
remaining = counts.loc[index][~small.loc[index]]
if len(remaining):
table.loc[index, remaining.idxmin()] = pd.NA
return table
print(suppress(by_area))
suppress <- function(x, threshold = 5) ifelse(x < threshold, NA, x)
# Plus the secondary pass: a row with exactly one NA is not suppressed at all.
Écrivez la règle comme une fonction et appliquez-la à chaque tableau. Une règle appliquée à l’œil est appliquée de façon inégale, et le seul tableau où on l’oublie est celui qui sera transféré.
Que faire au lieu de publier la cellule
La suppression est le dernier recours. Trois meilleures réponses viennent avant, et chacune préserve l’information que le demandeur voulait réellement.
Agréger vers le haut. Retirez une dimension. Zone par catégorie n’a aucune cellule sous cinq ; zone par catégorie par âge par sexe en a quatre-vingt-cinq.
coarse = referrals.groupby(["admin2", "case_category"]).size()
print(f"cells under 5: {(coarse < 5).sum()} of {len(coarse)}")
referrals |> count(admin2, case_category) |> summarise(small = sum(n < 5))
Rapporter le taux, non l’effectif, sur un dénominateur assez grand pour le porter. « L’aboutissement est de 31 % pour les cas avec handicap signalé » est publiable ; « 3 cas sur 11 à Mirebalais » ne l’est pas.
Répondre à la question plutôt que fournir le tableau. Un demandeur qui réclame zone par âge par sexe veut généralement savoir si les services atteignent les enfants. C’est un nombre unique sur un grand dénominateur, et il est publiable.
Le constat d’équité survit à la règle
La crainte est que la suppression détruise l’analyse. Vérifiez-le.
def completion(frame):
consenting = frame[frame["consent_to_refer"]]
reached = consenting[consenting["referral_accepted"] &
consenting["days_to_first_service"].notna()]
return len(reached), len(consenting), len(reached) / len(consenting)
disability = referrals["disability_reported"].isin([True, "true", "Yes"])
print("reported: %d/%d = %.1f%%" % completion(referrals[disability]))
print("not reported: %d/%d = %.1f%%" % completion(referrals[~disability]))
referrals |>
filter(consent_to_refer) |>
summarise(reached = sum(referral_accepted & !is.na(days_to_first_service)),
n = n(), .by = disability_reported) |>
mutate(completion = reached / n)
26,7 % contre 46,2 %, sur des dénominateurs de 202 et 1 436. L’écart d’équité de dix-neuf points — le constat le plus important de ce jeu de données — est calculé sur des dénominateurs très supérieurs à tout seuil de suppression et il est entièrement publiable.
Les analyses qui échouent au test de divulgation ne sont presque jamais celles
qui comptent. Une désagrégation à quatre entrées sur 1 850 cas répond rarement à
une question que quelqu’un a posée ; elle résulte généralement du fait d’avoir
versé toutes les variables catégorielles dans un groupby pour voir ce qui en
sort.
La demande qu’il faut refuser
Certaines doivent être refusées, et il vaut la peine de répéter la phrase.
Request: case counts by commune, month and incident category.
Response: declined.
Commune-by-month cells hold zero to three cases. At that resolution a
count identifies individuals to anyone working in the area, and the
dataset cannot be re-identified back but a person can be.
What I can provide instead:
- the same counts at district by quarter, no cell below five
- the completion rate and time to service for each commune, on
annual denominators
- a written answer to the question the request is for, if you tell me
what decision it feeds
If the requirement is genuinely commune-level operational planning, the
right route is a data sharing agreement with the case management agency,
not an extract.
Proposez trois solutions de remplacement et demandez quelle décision la demande alimente. Un refus sans alternative est remonté puis annulé par quelqu’un qui n’a pas lu cette leçon ; un refus avec trois alternatives se termine généralement par l’acceptation de l’une d’elles.
La suite
Les règles sont posées. L’unité suivante porte sur l’analyse elle-même — en commençant par la porte à l’entrée du circuit qui décide qui figure au dénominateur, et ce n’est pas une décision de qualité des données.