Leçon 6 sur 8
Unité · Le service est une année, non une journée
Tari n'est pas en panne
Les puits protégés tournent à 74,7 % en saison humide et à 36,4 % en saison sèche. Les forages à pompe manuelle ne bougent presque pas. Le champ d'état ne distingue pas ces deux défaillances, la séquence des visites le peut.
Un seul état, plusieurs défaillances différentes
functional_status dit qu’un point ne fonctionne pas. Il ne dit pas si la pompe
est cassée, si la nappe a baissé, si le comité a cessé de percevoir les
redevances, ou si la communauté est partie. Ces cas appellent quatre réponses
différentes et l’une d’elles n’est pas une réparation.
La séquence des visites peut les séparer, parce qu’ils ont des formes différentes dans le temps.
| Forme sur l’année | Ce que c’est | Que faire |
|---|---|---|
| À l’arrêt les mêmes mois chaque année, en service entre-temps | Saisonnier — la source se tarit | Approfondir, ou prévoir une alternative de saison sèche |
| À l’arrêt une fois, puis en service après un intervalle | Panne — réparée | Regarder la longueur de l’intervalle |
| À l’arrêt et le reste jusqu’à la fin | Abandonné ou en attente de pièces | Déterminer lequel ; ce n’est pas la même chose |
| En service et à l’arrêt à répétition | Chronique — sous-entretenu ou surchargé | Problème de gestion, non d’ouvrage |
Le signal saisonnier, et là où il n’est pas
import pandas as pd
points = pd.read_csv("water-point-monitoring-2024.v1.csv", parse_dates=["visit_date"])
WORKING = {"functional", "partially-functional"}
DRY_MONTHS = {1, 2, 3, 11, 12}
points["dry_season"] = points["visit_date"].dt.month.isin(DRY_MONTHS)
points["ok"] = points["functional_status"].isin(WORKING)
print(points.groupby("dry_season")["ok"].agg(["mean", "size"]).round(3))
library(dplyr)
points |>
mutate(dry = lubridate::month(visit_date) %in% c(1, 2, 3, 11, 12),
ok = functional_status %in% c("functional", "partially-functional")) |>
summarise(functionality = mean(ok), n = n(), .by = dry)
67,8 % en saison sèche contre 79,4 % en saison humide. Onze points d’écart, qu’un rapport comparant une évaluation de mars à une de juillet lirait comme un effondrement ou comme un redressement selon le sens de la soustraction.
Ventilez maintenant par type de source, où se trouve réellement le constat.
seasonal = (
points.groupby(["source_type", "dry_season"])["ok"].mean().unstack()
)
seasonal.columns = ["wet", "dry"]
seasonal["swing"] = seasonal["wet"] - seasonal["dry"]
print((seasonal * 100).round(1).sort_values("swing", ascending=False))
points |>
summarise(functionality = mean(ok), .by = c(source_type, dry)) |>
tidyr::pivot_wider(names_from = dry, values_from = functionality)
| Type de source | Humide | Sèche | Écart |
|---|---|---|---|
| Puits protégé | 74,7 % | 36,4 % | 38,3 |
| Source protégée | 84,8 % | 41,0 % | 43,8 |
| Forage à pompe manuelle | 75,0 % | 76,1 % | −1,1 |
| Borne de réseau | 95,9 % | 98,8 % | −2,9 |
Deux types de source portent tout l’effet saisonnier et deux ne bougent pas du tout. Les puits peu profonds et les sources puisent dans une nappe qui baisse ; un forage descend en dessous et un réseau dispose d’un réservoir. Les onze points de moyenne étaient un mélange d’un effet de quarante points et d’aucun effet.
C’est là le constat. Les forages de ce parc n’ont pas de problème saisonnier et les puits en ont un, si bien qu’un programme de saison sèche qui réhabilite des forages réhabilite le mauvais ouvrage.
Classer chaque point à partir de sa propre séquence
def classify(group):
group = group.sort_values("visit_date")
failures = group.loc[~group["ok"]]
if failures.empty:
return "never failed"
if not group["ok"].tail(3).any() and len(group) >= 3:
return "down at year end"
if failures["dry_season"].all():
return "seasonal"
return "intermittent"
shape = points.groupby("water_point_id").apply(classify, include_groups=False)
print(shape.value_counts())
points |>
arrange(visit_date) |>
summarise(
shape = case_when(
all(ok) ~ "never failed",
!any(tail(ok, 3)) & n() >= 3 ~ "down at year end",
all(dry[!ok]) ~ "seasonal",
TRUE ~ "intermittent"
), .by = water_point_id) |>
count(shape)
| Forme | Points |
|---|---|
| Jamais défailli | 82 |
| Intermittent | 80 |
| Saisonnier | 46 |
| À l’arrêt en fin d’année | 34 |
L’ordre des tests compte et c’est un jugement, non un détail. Un point qui ne défaille qu’en mois secs et qui est encore à l’arrêt en décembre satisfait les deux règles. Tester la permanence d’abord l’appelle abandonné ; tester la saisonnalité d’abord l’appelle saisonnier. Ce code teste la permanence d’abord, parce qu’un point qui n’a pas tourné depuis trois visites est un problème opérationnel maintenant quelle qu’en soit la cause — et parce que la lecture de décembre est celle sur laquelle un plan de janvier s’écrit.
Notez l’ordre employé. Deux analystes avec le même fichier et des ordres différents produiront des effectifs différents et auront tous deux raison.
La valeur d’état qui ressemble à la réponse sans l’être
Le registre comporte un état abandoned, et il est tentant de l’employer
directement.
ever_abandoned = points.loc[points["functional_status"].eq("abandoned"),
"water_point_id"].nunique()
print(f"points ever recorded as abandoned: {ever_abandoned}")
print(f"points down at the end of the year: 34")
points |> filter(functional_status == "abandoned") |>
summarise(points = n_distinct(water_point_id))
Dix-sept points sont enregistrés comme abandonnés lors d’une visite ; trente-quatre sont encore à l’arrêt en fin d’année. L’état est le jugement d’un enquêteur porté à une visite, et la séquence est une preuve. La moitié des points qui ne reviennent jamais n’ont jamais été étiquetés abandonnés, parce que l’enquêteur qui les a vus en juillet n’avait aucun moyen de savoir qu’ils seraient encore à l’arrêt en décembre.
Dérivez l’état de l’historique là où vous le pouvez, et employez l’état enregistré comme recoupement. Un champ qu’un enquêteur remplit à partir d’une observation unique ne peut pas porter une information qui n’existe qu’à travers plusieurs observations.
La durée d’arrêt est une statistique de gestion
down = points.loc[~points["ok"] & points["days_since_breakdown"].notna()]
print(down.groupby("management")["days_since_breakdown"].agg(
median="median", visits="size"
).sort_values("median"))
points |>
filter(!ok, !is.na(days_since_breakdown)) |>
summarise(median_days = median(days_since_breakdown), n = n(), .by = management)
| Gestion | Jours d’arrêt médians à la visite |
|---|---|
| Opérateur privé | 29 |
| Service public | 32 |
| Géré par une ONG | 48 |
| Comité communautaire | 68 |
Un point en panne sous comité communautaire est à l’arrêt depuis plus de deux fois plus longtemps qu’un point sous opérateur privé. C’est le seul nombre du registre qui désigne une action plutôt qu’un ouvrage — la différence tient à une chaîne d’approvisionnement en pièces et à un fonds d’entretien, non à la pompe.
Méfiez-vous des trente-sept lignes où un point est enregistré comme fonctionnel et
porte malgré tout un days_since_breakdown : c’est la réponse du mois précédent
reportée. Filtrez sur l’état, non sur la présence du champ.
Rapportez les formes, non le seul taux
Water point failure, 2024, 242 points
Never failed 82 33.9%
Intermittent 80 33.1%
Seasonal (dry months only) 46 19.0%
Down at year end 34 14.0%
Seasonality is concentrated in protected wells (74.7% wet, 36.4% dry) and
protected springs (84.8% wet, 41.0% dry). Handpump boreholes and piped
schemes show no seasonal effect.
Points classified as down at year end if not working at any of the last
three visits; seasonal if every recorded failure fell in a dry month.
Permanence tested before seasonality.
Quatre effectifs et la règle qui les a produits. Un pourcentage de fonctionnalité unique enverrait un budget de réhabilitation vers les mauvais quarante-six points, car ils n’ont pas besoin d’être réhabilités — il leur faut une alternative de saison sèche, et leurs pompes vont bien.
La suite
Chaque nombre de cette leçon est calculé sur les visites qui ont été faites. Un district a manqué un tiers de ses tournées en saison des pluies, et les points manqués n’étaient pas un tiers au hasard — c’est la leçon suivante, et la raison pour laquelle sa fonctionnalité semble s’améliorer quand les routes se ferment.