cassionData Analysis

Lesson 7 of 8

Unit · Learning

Eight points of improvement, two of them nobody earned

Literacy rises from 53.1% to 61.8% of items across the two rounds. On the 585 students who sat both, it rises from 56.8% to 63.7%. The difference is who turned up, and the raw scores could not have told you either number.

PythonR105 minSustainable Development Goals (SDG)Multiple Indicator Cluster Survey (MICS)UNICEF indicator definitions

The comparison that cannot be made

import pandas as pd

assessment = pd.read_csv("learning-assessment-2024.v1.csv")
literacy = assessment[assessment["domain"] == "literacy"]

print(literacy.groupby("assessment_round").agg(
    students=("student_id", "nunique"),
    items=("items", "first"),
    mean_raw=("raw_score", "mean"),
).round(1))
library(dplyr)

assessment |>
  filter(domain == "literacy") |>
  summarise(students = n_distinct(student_id), items = first(items),
            mean_raw = mean(raw_score), .by = assessment_round)
Round Students Items Mean raw score
Baseline 741 40 21.2
Endline 658 50 30.9

A raw score went from 21.2 to 30.9 and that comparison is meaningless, because the instruments are not the same length. Nine points of the increase is ten extra questions.

This is the single most common error in education assessment reporting, and it is invisible unless you look at the items column — which most extracts do not carry and most analysts do not ask for.

Three things that are comparable, and one that is not

Not comparable: the raw score. Different denominators.

Comparable with care: the proportion of items correct. It puts both rounds on a 0–1 scale, which is necessary and not sufficient — the endline items may be harder or easier, and a proportion assumes they are equivalent.

literacy = literacy.assign(proportion=literacy["raw_score"] / literacy["items"])
print(literacy.groupby("assessment_round")["proportion"].mean().round(3))
assessment |> filter(domain == "literacy") |>
  summarise(proportion = mean(raw_score / items), .by = assessment_round)

53.1% at baseline and 61.8% at endline. Better, and still resting on an assumption nobody has checked.

Comparable by construction: the proficiency bands. These were equated when the instruments were designed, which is what equating is for — it is a psychometric procedure that puts two different tests on one scale, and it is the reason the bands exist as a separate column rather than being derived from the score.

bands = pd.crosstab(assessment["assessment_round"],
                    assessment["proficiency_band"], normalize="index")
order = ["below-minimum", "minimum", "proficient", "advanced"]
print((bands[order] * 100).round(1))
assessment |> count(assessment_round, proficiency_band) |>
  mutate(share = n / sum(n), .by = assessment_round)
Band Baseline Endline
Below minimum 19.3% 11.1%
Minimum 30.6% 23.9%
Proficient 38.2% 38.9%
Advanced 11.9% 26.1%

Below-minimum falls from 19.3% to 11.1% and advanced rises from 11.9% to 26.1%. That is the comparison to report, because it is the only one whose scale was designed to survive a change of instrument.

Who sat the test

sat = assessment.groupby("assessment_round")["student_id"].apply(set)
baseline, endline = sat["baseline"], sat["endline"]

print(f"baseline only: {len(baseline - endline)}")
print(f"endline only:  {len(endline - baseline)}")
print(f"both rounds:   {len(baseline & endline)}")
# Three groups, and only one of them supports a change estimate.

741 sat the baseline, 658 the endline, and 585 sat both. 156 students who sat the first test did not sit the second.

That is not a random 156. Assessment day catches whoever is in the classroom, and the children who are not in the classroom are the ones attending least — who are also, from lesson 3, the ones scoring lowest.

So the endline mean is computed on a group from which the weakest have been disproportionately removed, and it will rise for that reason alone.

Measure the selection instead of assuming it away

The register lets you do the thing that settles it: compute the change on the students who appear in both rounds.

panel = literacy.pivot_table(index="student_id", columns="assessment_round",
                             values="proportion")
panel = panel.dropna()

print(f"panel of {len(panel)} students")
print(f"  baseline {panel['baseline'].mean():.1%} → endline {panel['endline'].mean():.1%}")

all_comers = literacy.groupby("assessment_round")["proportion"].mean()
print(f"all comers")
print(f"  baseline {all_comers['baseline']:.1%} → endline {all_comers['endline']:.1%}")
assessment |> filter(domain == "literacy") |>
  mutate(proportion = raw_score / items) |>
  select(student_id, assessment_round, proportion) |>
  tidyr::pivot_wider(names_from = assessment_round, values_from = proportion) |>
  filter(!is.na(baseline), !is.na(endline)) |>
  summarise(n = n(), baseline = mean(baseline), endline = mean(endline))
Baseline Endline Change
All comers 53.1% 61.8% +8.7 points
Panel of 585 56.8% 63.7% +6.9 points

Two of the 8.7 points are who turned up. The panel baseline is 3.7 points above the all-comers baseline, which is the direct measurement of the selection: the students who came back were already stronger.

Report the panel estimate and show the all-comers figure beside it. The panel is the defensible number; the gap between them is the evidence that the selection was real and the size of it.

What the panel costs

It is not free, and the trade has to be stated.

The panel is not the population. 585 of 741 baseline students, and they are the better-attending ones. So +6.9 points is an unbiased estimate of the change for children who stayed in school and turned up twice, which is a narrower claim than the one people want.

It cannot tell you about the 156 who left. Their learning may have gone anywhere, and the honest report says the estimate does not cover them.

left = literacy[literacy["student_id"].isin(baseline - endline)
                & (literacy["assessment_round"] == "baseline")]
print(f"the 156 who did not return scored "
      f"{left['proportion'].mean():.1%} at baseline")
print(f"the 585 who did scored "
      f"{panel['baseline'].mean():.1%}")
# Quantify the difference rather than asserting it.

The 156 who did not return scored 39.3% at baseline against the panel’s 56.8% — seventeen points below. That is not a subtle selection effect; it is the weakest sixth of the cohort walking out of the endline mean.

Quantifying the selection is the deliverable, not eliminating it. An analysis that says “the endline sample was better-attending, and here is by how much” has done its job; one that reports +8.7 points has not.

Report all three numbers

Literacy, baseline to endline

  Proficiency bands, all who sat each round
    Below minimum        19.3% → 11.1%
    Advanced             11.9% → 26.1%

  Proportion of items correct
    All comers           53.1% → 61.8%   +8.7 points   n=741, n=658
    Panel of both rounds 56.8% → 63.7%   +6.9 points   n=585

  Instruments differed: 40 items at baseline, 50 at endline. Raw scores are
  not comparable and are not reported. Proficiency bands were equated at
  design; the proportion of items assumes equivalent difficulty.

  156 baseline students did not sit the endline. They scored 39.3% at
  baseline against 56.8% for those who returned, so the all-comers change
  overstates learning by about 1.8 points. The panel estimate covers
  children who sat both rounds and does not cover those 156.

What comes next

You now have every education indicator this course can produce — enrolment, attendance, retention and learning — each with a denominator and a caveat. The last lesson is the report they go into, and the four numbers a head teacher can actually act on.

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.