cassionData Analysis

Back to the lessonLesson 5 of 8The cohort

Three outcomes that have to sum to one

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.

Slides · PDFSlides · PowerPoint

  1. Slide 1 / 24

    What this lesson covers

    • The year, as a set of exits
    • The denominator is not the enrolment count
    • Repetition is a decision, not an event
    • Dropout by grade, and the grade-6 spike
    • Report the exits, then the rates
    • What comes next
    Speaker notes
    70.4% promoted, 9.8% repeated, 10.2% dropped out, 5.7% finished and 2.9% transferred. Every one of those needs the same denominator, and the transfers are the reason it is not the enrolment count.
  2. Slide 2 / 24

    The year, as a set of exits — In Python

    import pandas as pd
    
    enrolment = pd.read_csv("school-enrolment-2024.v1.csv")
    clean = enrolment[enrolment["age_years"] <= 20]
    cohort = clean[clean["school_year"] == 2023]
    
    outcomes = cohort["end_of_year_status"].value_counts(normalize=True)
    print((outcomes * 100).round(1))
    print(f"\ncohort: {len(cohort)} students")
  3. Slide 3 / 24

    The year, as a set of exits — In R

    library(dplyr)
    
    enrolment |>
      filter(age_years <= 20, school_year == 2023) |>
      count(end_of_year_status) |>
      mutate(share = n / sum(n))
  4. Slide 4 / 24

    The year, as a set of exits

    OutcomeStudentsShare
    Promoted91070.4%
    Dropped out13210.2%
    Repeated1279.8%
    Completed the final grade745.7%
    Transferred out372.9%
    Still enrolled at the cut-off120.9%
  5. Slide 5 / 24

    The year, as a set of exits

    • The three headline rates are promotion, repetition and dropout, and they only mean anything if they share a denominator…
    Speaker notes
    The three headline rates are promotion, repetition and dropout, and they only mean anything if they share a denominator. That sounds obvious and it is the step most often skipped.
  6. Slide 6 / 24

    The denominator is not the enrolment count

    • Transfers are not dropouts — A child who moved to another school is still in school
    • Transfers are also not successes — They cannot be counted as promoted, because this register does not know whether they…
    Speaker notes
    Two categories complicate it, and they complicate it in opposite directions. Transfers are not dropouts. A child who moved to another school is still in school. Counting them as dropouts overstates dropout by nearly three points and — worse — attributes a system success to a school failure. Transfers are also not successes. They cannot be counted as promoted, because this register does not know whether they enrolled anywhere.
  7. Slide 7 / 24

    The denominator is not the enrolment count — In Python (cont.)

    def rates(frame, exclude_transfers):
        base = frame[frame["end_of_year_status"] != "transferred-out"] \
            if exclude_transfers else frame
        return {
            "n": len(base),
            "promotion": (base["end_of_year_status"]
                          .isin(["promoted", "completed-final-grade"]).mean()),
            "repetition": base["end_of_year_status"].eq("repeated").mean(),
            "dropout": base["end_of_year_status"].eq("dropped-out").mean(),
        }
    
    for exclude in (False, True):
        result = rates(cohort, exclude)
        label = "excluding transfers" if exclude else "all students"
        print(f"{label:20} n={result['n']}  "
              f"promotion {result['promotion']:.1%}  "
  8. Slide 8 / 24

    The denominator is not the enrolment count — In Python (cont.)

              f"repetition {result['repetition']:.1%}  "
              f"dropout {result['dropout']:.1%}")
  9. Slide 9 / 24

    The denominator is not the enrolment count — In R

    cohort |>
      filter(end_of_year_status != "transferred-out") |>
      summarise(n = n(),
                promotion = mean(end_of_year_status %in% c("promoted", "completed-final-grade")),
                repetition = mean(end_of_year_status == "repeated"),
                dropout = mean(end_of_year_status == "dropped-out"))
  10. Slide 10 / 24

    The denominator is not the enrolment count

    • This is the CMAM cure-rate denominator from earlier in this module, in a different sector — The convention there was to…
    Speaker notes
    This is the CMAM cure-rate denominator from earlier in this module, in a different sector. The convention there was to exclude transfers because the programme cannot claim their outcome; the convention here is the same, for the same reason. Say which you used, and the three rates will sum to something you can defend.
  11. Slide 11 / 24

    Repetition is a decision, not an event — In Python

    by_district = clean.groupby("admin2")["repeating"].agg(["mean", "size"])
    print((by_district * [100, 1]).round(1))
  12. Slide 12 / 24

    Repetition is a decision, not an event — In R

    enrolment |> filter(age_years <= 20) |>
      summarise(repeating = mean(repeating), n = n(), .by = admin2)
  13. Slide 13 / 24

    Repetition is a decision, not an event

    DistrictRepetition raten
    Sud12.3%583
    Artibonite12.1%618
    Nord-Ouest12.0%625
    Centre7.4%624
  14. Slide 14 / 24

    Repetition is a decision, not an event — In Python

    centre = clean[(clean["admin2"] == "Centre") & (clean["school_year"] == 2023)]
    elsewhere = clean[(clean["admin2"] != "Centre") & (clean["school_year"] == 2023)]
    for name, frame in [("Centre", centre), ("elsewhere", elsewhere)]:
        over = frame["age_years"] > frame["grade"] + 5
        print(f"{name}: repetition {frame['repeating'].mean():.1%}, "
              f"over-age {over.mean():.1%}")
    Speaker notes
    Centre reports repetition nearly five points below every other district. That is either the best-performing district in the region or a recording habit, and the register alone cannot tell you which. It is a recording habit: Centre flags a share of its genuine repeaters as new entrants. The way to suspect it without being told is that repetition should track over-age, and in Centre it does not — 8.7% repetition against 11.8% elsewhere, while Centre's over-age share is 40.6% against 35.7%. The district reporting the least repetition has the most over-age children, which is the wrong way round for any explanation except the coding.
  15. Slide 15 / 24

    Repetition is a decision, not an event — In R

    # Repetition should predict over-age. Where it does not, suspect the coding.
  16. Slide 16 / 24

    Repetition is a decision, not an event

    • A rate that is out of line with the indicator it mechanically causes is a question about the office before it is a…
    Speaker notes
    A rate that is out of line with the indicator it mechanically causes is a question about the office before it is a finding about the schools. That is the same reasoning as the protection course's closure-reason catch-all, and it is worth having as a reflex.
  17. Slide 17 / 24

    Dropout by grade, and the grade-6 spike — In Python

    by_grade = cohort[cohort["grade"].between(1, 6)].groupby("grade").agg(
        n=("student_id", "size"),
        dropout=("end_of_year_status", lambda s: s.eq("dropped-out").mean()),
        repetition=("end_of_year_status", lambda s: s.eq("repeated").mean()),
    )
    print((by_grade * [1, 100, 100]).round(1))
  18. Slide 18 / 24

    Dropout by grade, and the grade-6 spike — In R

    cohort |> filter(between(grade, 1, 6)) |>
      summarise(n = n(),
                dropout = mean(end_of_year_status == "dropped-out"),
                repetition = mean(end_of_year_status == "repeated"), .by = grade)
  19. Slide 19 / 24

    Dropout by grade, and the grade-6 spike

    GradenDropoutRepetition
    11498.1%2.0%
    22718.5%7.0%
    333213.0%6.6%
    42318.7%10.4%
    51658.5%19.4%
    611018.2%12.7%
  20. Slide 20 / 24

    Dropout by grade, and the grade-6 spike

    • Repetition rises with grade and dropout does not — Grade 5 holds back 19.4% of its students while its dropout is 8.5%;…
    • Grade 6 dropout is 18.2% on 110 students — That is the largest rate in the table and the smallest denominator, so…
    Speaker notes
    Two things in that table need reading carefully. Repetition rises with grade and dropout does not. Grade 5 holds back 19.4% of its students while its dropout is 8.5%; grade 6 does the opposite. The two are alternative exits from the same decision point, and reading either alone gets the shape of the problem wrong. Grade 6 dropout is 18.2% on 110 students. That is the largest rate in the table and the smallest denominator, so before treating it as the priority, put an interval on it — the survey course's machinery, on a proportion that would move several points if four children had done something else.
  21. Slide 21 / 24

    Report the exits, then the rates — Example (cont.)

    End of school year 2023, 1,292 students
    
      Promoted                     70.4%   910
      Dropped out                  10.2%   132
      Repeated                      9.8%   127
      Completed the final grade     5.7%    74
      Transferred out               2.9%    37   excluded from the rates below
      Still enrolled at cut-off     0.9%    12
    
      Rates on 1,255 students, transfers excluded:
        Promotion (incl. completion)  78.4%
        Repetition                    10.1%
        Dropout                       10.5%
    
      Centre reports repetition at 7.4% against 12.0-12.3% elsewhere while
      holding the highest over-age share of the four districts. Treat as a
  22. Slide 22 / 24

    Report the exits, then the rates — Example (cont.)

      recording difference pending verification, not as performance.
  23. Slide 23 / 24

    What comes next

    • These are one year's exits.
    Speaker notes
    These are one year's exits. Turning them into "how many children finish primary school" needs a cohort, and the next lesson builds one — along with the reason the number it produces is almost certainly wrong.
  24. Slide 24 / 24

    Where this goes next

    Read the full lesson, with runnable code Back to the lesson