Lesson 3 of 8
Unit · What Sphere asks that a ladder does not
Fifteen litres, thirty minutes, five hundred metres
13.4% of these households fall below the Sphere minimum for quantity. Two of the columns that produce that number carry unit errors, and one of them moves a household up a ladder rung rather than off the end of a chart.
What Sphere adds to the ladder
The JMP ladders classify a service. The Sphere minimum standards put numbers on it, and the three that a water programme is judged on are:
| Standard | Threshold | In this file |
|---|---|---|
| Quantity | At least 15 litres per person per day | litres_per_person_day |
| Queueing time | No more than 30 minutes | Inside round_trip_minutes |
| Distance | No more than 500 m to the nearest point | Not recorded; time is the proxy |
The thresholds are minimums for survival with dignity, not targets. A programme at 15.2 litres has met the standard and is not doing well, and a report that prints only the share above the threshold hides where the distribution actually sits.
import pandas as pd
households = pd.read_csv("wash-household-survey-2024.v1.csv")
litres = households["litres_per_person_day"]
print(f"median {litres.median():.1f} L/p/d")
print(f"below Sphere minimum: {(litres < 15).mean():.1%}")
print(litres.describe().round(1))
library(dplyr)
households |> summarise(
median = median(litres_per_person_day),
below_15 = mean(litres_per_person_day < 15),
n = n()
)
13.4% below 15 litres, with a median of 23.7. Both numbers belong in the report: the first is the standard, the second says the median household has about half again what it needs and the problem is distribution rather than total supply.
Two unit errors, and only one of them is visible
print(litres.nlargest(8).round(1).tolist())
households |> slice_max(litres_per_person_day, n = 8) |>
select(household_id, household_size, litres_per_person_day)
Eleven households are above 80 litres per person per day, up to 277.6. That is implausible in this setting and it is not a keying error — it is the household’s total consumption entered in a per-person column. Divide by household size and every one of them lands in a normal range.
suspect = households[litres > 80]
recovered = suspect["litres_per_person_day"] / suspect["household_size"]
print(recovered.round(1).describe())
households |>
filter(litres_per_person_day > 80) |>
mutate(recovered = litres_per_person_day / household_size) |>
summarise(min = min(recovered), max = max(recovered))
The second error is the dangerous one, because it makes a value smaller rather than larger. Eleven households have collection time entered in hours, so a 90-minute round trip is recorded as 2. There is no outlier to catch: 2 minutes is a perfectly ordinary value, and 85 households legitimately report under 8 minutes.
short = households[households["round_trip_minutes"].between(1, 8)]
print(f"{len(short)} households report a round trip of 1 to 8 minutes")
print(short["water_source"].value_counts())
households |> filter(between(round_trip_minutes, 1, 8)) |> count(water_source)
An error that moves a value into the normal range cannot be found by looking for outliers. It has to be found by a rule that ties two fields together — a round trip under ten minutes to a source that is not on premises and not a public tap in the same community deserves a query — or it has to be prevented at entry with a unit label on the form.
Its consequence here is not a wrong mean. It is a wrong rung: each of those households is classified as basic service when it is limited.
Queueing is inside the round trip, and that is a problem
Sphere sets queueing separately from collection time because they have different causes and different fixes. A long walk needs a new water point; a long queue needs more taps at the point that exists, or a longer opening time.
This survey records only the round trip, which contains both. So the honest report says what it can and cannot separate:
print("round_trip_minutes includes walking, queueing and return.")
print(f"over 30 minutes: {(households['round_trip_minutes'] > 30).mean():.1%}")
print("Queue time alone: not collected. Recommend adding it to the next round.")
# One line in the limitations section beats a queue statistic that is a walk.
39.3% exceed thirty minutes, and none of that number can be attributed to queueing or to distance without the split. Say which of the two you cannot see, because the corrective action differs and a programme that guesses will build the wrong thing.
Distance is not in the file at all
The 500-metre standard needs a coordinate or a measured distance, and this survey has neither. Time is a proxy for it and a poor one: thirty minutes is a kilometre on a road and three hundred metres up a ravine.
Where a standard names a unit you do not have, report the proxy under the proxy’s own name. Reporting round-trip time as though it were the distance standard is how an assessment concludes that a community is within 500 metres of water when it is nowhere near it.
Report the distribution against the threshold
Water quantity, 2,403 households
Median 23.7 L/person/day
Below Sphere minimum (15 L) 13.4% 323 households
Below 7.5 L 2.2% 54 households
Collection time
Over 30 minutes 39.3% 944 households
Queue time not separated from walking time in this round.
Distance not recorded; time is the proxy and is not equivalent.
11 records held household total litres in the per-person column and were
corrected; 11 held collection time in hours and were corrected.
The threshold, the distribution, and the corrections stated with counts. A cleaning log is not a confession — it is what lets a reader see that 13.4% is after two known errors were fixed rather than before.
What comes next
Quantity and time say whether water arrived and how hard it was to get. They say nothing about whether it was safe to drink, and the next lesson is the one where the denominator changes under you.