cassionData Analysis

Lesson 4 of 8

Unit · The pathway

The gap opens before the referral is made

Cases reporting a disability complete at 26.7% against 46.2%. The gap is not at the service door — consent is identical at 89% and 88.5% — it opens at the gate where a caseworker decides whether to make the referral at all.

PythonR120 minCore Humanitarian Standard (CHS)UNICEF indicator definitionsOECD DAC evaluation criteriaSphere Standards

Find the gate, not the total

An end-to-end completion rate of 43.8% tells you the pathway is leaking. It does not tell you where, and the four gates lose very different amounts.

import pandas as pd

referrals = pd.read_csv("protection-referrals-2024.v1.csv")

def cascade(frame):
    consented = frame[frame["consent_to_refer"]]
    made = consented[consented["referral_made"]]
    accepted = made[made["referral_accepted"]]
    reached = accepted[accepted["days_to_first_service"].notna()]
    return pd.Series({
        "n": len(frame), "consented": len(consented), "made": len(made),
        "accepted": len(accepted), "reached": len(reached),
    })

totals = cascade(referrals)
print(totals)
print("\nloss at each gate:")
print(f"  consent:  {1 - totals['consented'] / totals['n']:.1%}")
print(f"  referral: {1 - totals['made'] / totals['consented']:.1%}")
print(f"  accepted: {1 - totals['accepted'] / totals['made']:.1%}")
print(f"  service:  {1 - totals['reached'] / totals['accepted']:.1%}")
library(dplyr)

referrals |> summarise(
  n = n(),
  consented = sum(consent_to_refer),
  made = sum(consent_to_refer & referral_made),
  accepted = sum(referral_made & referral_accepted),
  reached = sum(referral_accepted & !is.na(days_to_first_service))
)
Gate Lost here
Consent 11.5% — a decision, not a loss
Referral made 30.3%
Referral accepted 33.7%
Reached the service 5.3%

The two middle gates lose almost everything. Once a referral is accepted, 94.7% of cases reach the service — the receiving end works. The failure is upstream: a referral that is never made, and a referral that is made and refused.

Those are two different problems with two different owners. A single completion rate hides which one you have, and a programme reading 43.8% would reasonably invest in the service that is not the bottleneck.

Which service, and which area

by_service = referrals.groupby("service_requested").apply(cascade,
                                                          include_groups=False)
by_service["completion"] = by_service["reached"] / by_service["consented"]
print((by_service["completion"] * 100).round(1).sort_values())
referrals |>
  summarise(consented = sum(consent_to_refer),
            reached = sum(referral_accepted & !is.na(days_to_first_service)),
            .by = service_requested) |>
  mutate(completion = reached / consented) |> arrange(completion)
Service Completion Referral made Accepted
Livelihood support 20.7% 49% 47%
Legal 30.0% 61% 50%
Safety and security 33.8% 61% 58%
Psychosocial 53.2% 77% 72%
Health 57.6% 81% 76%

Livelihood support completes at a third of the rate health does, and it fails at both middle gates equally — half the referrals are never made and half of those made are refused. That is the signature of a service that is known to be oversubscribed: caseworkers stop referring to it because they know the answer.

Areas range from 29.7% to 53.8%, a spread wide enough to act on and narrow enough that no area is fine.

The gap that matters

disability = referrals["disability_reported"].isin([True, "true", "Yes"])
no_disability = referrals["disability_reported"].isin([False, "false", "No"])

for label, mask in [("reported", disability), ("not reported", no_disability)]:
    row = cascade(referrals[mask])
    print(f"{label:14} n={row['n']:>5}  "
          f"consent {row['consented'] / row['n']:.1%}  "
          f"made {row['made'] / row['consented']:.1%}  "
          f"accepted {row['accepted'] / row['made']:.1%}  "
          f"→ {row['reached'] / row['consented']:.1%}")
referrals |>
  mutate(disability = disability_reported %in% c("true", "Yes")) |>
  summarise(n = n(), consented = sum(consent_to_refer),
            made = sum(consent_to_refer & referral_made),
            accepted = sum(referral_made & referral_accepted),
            reached = sum(referral_accepted & !is.na(days_to_first_service)),
            .by = disability)
n Consent Referral made Accepted Completion
Disability reported 227 89.0% 54.5% 56.4% 26.7%
Not reported 1,623 88.5% 71.9% 67.3% 46.2%

Consent is identical — 89.0% against 88.5%. People with disabilities are exactly as willing to be referred.

The gap opens at the two gates the service system controls. A referral is made for 54.5% of them against 71.9%, and accepted for 56.4% against 67.3%. By the service door the difference has compounded to nineteen points.

Locating the gap at a specific gate turns an observation into an action. “Outcomes are worse for people with disabilities” is a finding nobody can act on. “Caseworkers make a referral for 55% of them against 72%, and receiving services accept 56% against 67%” names two conversations with two named parties.

Before publishing it

Three checks, and the first will change your numbers.

The disability column has four values. One area recorded Yes and No instead of true and false, so a naive grouping produces four categories, two of them too small to interpret and one of them silently dropped by a boolean filter.

print(referrals["disability_reported"].value_counts(dropna=False))
referrals |> count(disability_reported)

Disability is self-reported and under-reported. 227 of 1,850 is 12.3%, below most population estimates. The gap is real; the denominator is a group who disclosed, and people who did not disclose are counted in the comparison group. That biases the gap downward — so 19 points is a floor.

Check the cell sizes before disaggregating further. Disability by area by service is exactly the four-way table the last lesson refused to publish.

Report the gates, not the total

Referral pathway, 1,638 consenting cases

  Referral made                   69.7%
  Accepted, of those made         66.3%
  Reached service, of accepted    94.7%
  End to end, of consenting       43.8%

  The pathway fails upstream. Once a referral is accepted 94.7% of cases
  reach a service; the losses are at referral-making and acceptance.

  Cases reporting a disability: 26.7% complete against 46.2%. Consent is
  identical (89.0% and 88.5%); the gap is entirely at referral-making
  (54.5% against 71.9%) and acceptance (56.4% against 67.3%).

  Disability is self-reported by 12.3% of cases. Non-disclosure places some
  people in the comparison group, so the gap is a lower bound.

What comes next

The pathway ends when a case reaches a service. What happens after that is a case that someone has to carry, and the next unit is the register that records who is carrying how many.

Teach this lesson

The lesson as a slide deck, with the prose kept in the speaker notes rather than on the slide. Generated from this page, so it cannot fall out of step with it.

Start the slideshowRead the slides

The PDF needs no software and projects from any machine. The PowerPoint file is there to be edited — add your organisation's branding, cut a section for a shorter session, or merge two lessons into a workshop.