Back to the lesson·Lesson 5 of 8·Counting people
Reach, coverage, and the people counted twelve times
The same deck as the downloads, rendered as a page. Start the slideshow to present it full screen — arrow keys or a click advance one slide, Escape leaves.
What this lesson covers
- Three questions that sound like one
- The cumulative sum that manufactures people
- Deduplicating across months, when you can
- Reach is not coverage, and the difference is the denominator
- Cumulative counts in proposals
- Which one belongs in your LogFrame
- What comes next
Speaker notes
Three indicators that all sound like "how many people did we help", and the monthly series that turns 4,200 children into 50,000 the moment somebody sums the column.Three questions that sound like one
- Reach — how many distinct people received something, over a stated period.
- Service volume — how many services were delivered. Larger than reach whenever anyone comes twice.
- Coverage — what share of those who needed it got it. Requires a denominator and is the only one that says whether…
Speaker notes
"How many people did the programme help?" has three answers, and a proposal, a donor report and a coverage figure each need a different one. The names are used interchangeably in practice, which is how a programme reports 50,000 children reached in a district with 4,200 of them.The cumulative sum that manufactures people — In Python
monthly = ( muac.assign(month=muac["screening_date"].dt.strftime("%Y-%m")) .groupby("month") .agg(screenings=("child_id", "size"), distinct_children=("child_id", "nunique")) ) monthly["cumulative_naive"] = monthly["screenings"].cumsum() print(monthly) print("sum of monthly counts: ", monthly["screenings"].sum()) print("distinct children, year:", muac["child_id"].nunique())Speaker notes
Here is the mechanism, and it is the single most common way this sector overstates its work.The cumulative sum that manufactures people — In R
monthly <- muac |> mutate(month = format(screening_date, "%Y-%m")) |> summarise(screenings = n(), distinct_children = n_distinct(child_id), .by = month) |> arrange(month) |> mutate(cumulative_naive = cumsum(screenings)) c(sum_of_months = sum(monthly$screenings), distinct_year = n_distinct(muac$child_id))The cumulative sum that manufactures people
Correct Wrong Monthly reach, January distinct people in January — Annual reach distinct people across the year sum of twelve monthly counts Cumulative reach to date distinct people since programme start running total of monthly counts Speaker notes
On this register the two happen to be close, because it is a one-round campaign and few children were screened twice. That is the exception. In a monthly programme — outpatient therapeutic care, a cash transfer, a health facility — a beneficiary appears in every month they receive something, and twelve monthly counts summed is twelve times a stable caseload. The arithmetic that goes wrong:The cumulative sum that manufactures people
- A cumulative reach figure can only be computed from person-level data — If your reporting is monthly aggregates, you…
Speaker notes
A cumulative reach figure can only be computed from person-level data. If your reporting is monthly aggregates, you cannot deduplicate across months, and the honest annual figure is not available — you must either say so or report the maximum month rather than the sum.The cumulative sum that manufactures people — In Python
annual_reach = muac["child_id"].nunique() service_volume = len(muac) print(f"reach {annual_reach:,} children; {service_volume:,} screening events")The cumulative sum that manufactures people — In R
c(reach = n_distinct(muac$child_id), volume = nrow(muac))Deduplicating across months, when you can — In Python
first_contact = ( muac.sort_values("screening_date") .drop_duplicates(subset=["child_id"], keep="first") .assign(month=lambda d: d["screening_date"].dt.strftime("%Y-%m")) ) new_by_month = first_contact.groupby("month").size() print(new_by_month.cumsum())Deduplicating across months, when you can — In R
first_contact <- muac |> arrange(screening_date) |> distinct(child_id, .keep_all = TRUE) |> mutate(month = format(screening_date, "%Y-%m")) first_contact |> count(month) |> arrange(month) |> mutate(cumulative = cumsum(n))Speaker notes
Counting each person in the month of their first contact gives a cumulative series that rises to the true reach and never exceeds it. It also produces a genuinely useful management number — new beneficiaries per month, which distinguishes a programme that is growing from one that is serving the same people repeatedly. Report both, always: new this month and total served this month. They answer different questions and either alone is misleading.Reach is not coverage, and the difference is the denominator — In Python
reached = muac["child_id"].nunique() under5_population = 21500 # from the administrative frame, projected print(f"screening coverage: {reached / under5_population:.1%}")Speaker notes
Reach counts who you served. Coverage says what fraction of those who needed the service got it, and it is the only one of the three that can say a programme is insufficient.Reach is not coverage, and the difference is the denominator — In R
c(coverage = n_distinct(muac$child_id) / 21500)Reach is not coverage, and the difference is the denominator
- Admissions over expected caseload is not coverage — Dividing the children admitted to treatment by the caseload you…
- A reach figure with a population denominator is not coverage either — unless the population is the population in need
Speaker notes
Two failures to avoid, and both are common enough to have names. Admissions over expected caseload is not coverage. Dividing the children admitted to treatment by the caseload you expected tells you how good your expectation was, not what share of malnourished children you reached. Real coverage needs the number of malnourished children, which needs a survey — which is why coverage surveys exist as a separate methodology. A reach figure with a population denominator is not coverage either unless the population is the population in need. Screening 4,200 of 21,500 under-fives is screening coverage. It says nothing about what share of the malnourished children were found.Cumulative counts in proposals
- State the deduplication level. "Unique individuals, deduplicated within year, not across years" is a defensible and…
- Never sum reach across sectors. A household receiving water, food and a protection service is one household, and…
- Say when you cannot. "Aggregate figures are not deduplicated across partners; the true unique reach is lower" is a…
Speaker notes
Multi-year proposals ask for "people reached over the life of the project", and this is where the double-counting becomes a compliance issue rather than a methods one. Three rules that keep it honest:Which one belongs in your LogFrame
If the row is about Use Programme effort and cost Service volume People served Reach, with the deduplication level stated Whether the response is sufficient Coverage, with a needs-based denominator Whether people who start finish A completion or dropout rate Which one belongs in your LogFrame
Reach tells you what you did. Coverage tells you whether it was enough. A report with only the first is a description of activity, and every reader knows it.
Speaker notes
The last row is the one most often missing, and it is frequently the most informative — the Theory of Change lesson made that case with the 13.8% penta1 to penta3 dropout, which no reach or coverage figure would have revealed.What comes next
- Almost every indicator in this lesson already has an official definition published by somebody — UNICEF, WHO, the cluster, the SDG framework.
Speaker notes
Almost every indicator in this lesson already has an official definition published by somebody — UNICEF, WHO, the cluster, the SDG framework. The next lesson is about finding it, adopting it, and saying precisely where yours departs from it.