cassionData Analysis

Lab · Intermediate

The rate the headcount hid

Turn a treatment register into a cohort, count person-days instead of children, and watch three sites that share a defaulter proportion separate once you account for how long each one held its children.

RYour own machine150 min

A treatment register is a cohort that nobody has declared. Each child enters on a date, is under observation for a while, and leaves through one of several exits — which is the exact structure a rate with person-time in its denominator is built for, and the exact structure a percentage throws away.

This lab is the first lesson’s arithmetic done properly on a real register. You will produce two defaulter figures per site, find that they rank the sites differently, and decide which one the programme should be judged on.

The file

cmam-admissions-2024.v1.csv — 1,100 treatment episodes across six sites, each with an admission date, a discharge date where one exists, and an outcome. Synthetic.

This lab is not about CMAM. The admission criteria, the MUAC and weight-for-height thresholds, and what the outcomes mean clinically belong to Nutrition Analysis elsewhere in the programme. Here the register is a cohort and nothing more: entry, exit, and time in between.

Set up first

An RStudio project, the file read-only, an outputs/ directory, and a script that runs top to bottom from a clean session.

Part one: build the person-time

Three decisions, and each has to be written down before it is coded.

The cut-off. Seventy-one children have no discharge date because they were still in treatment when the register closed. They are censored, not missing — they contributed observation time and then the observation stopped. Choose a cut-off date, justify it in one line, and use it for every child.

The clock. Days from admission to exit, where the exit is the discharge date or the cut-off, whichever applies. Compute it as an integer count of days and say whether you count the day of admission.

The exclusions. Forty-five children were transferred. Decide whether their person-time counts, and say what the decision assumes.

# The shape of it, not the whole of it:
episodes |>
  mutate(
    exit = coalesce(discharge_date, cutoff),
    person_days = as.integer(exit - admission_date),
    censored = is.na(discharge_date)
  )

Assert the total before you go on. The whole register holds about 58,400 person-days across 1,100 episodes, which is a mean of roughly 53 days each.

Part two: two figures per site

For each site compute:

  • the defaulter proportion — defaulters over episodes that reached an outcome, excluding transfers; and
  • the defaulter rate — defaulters per 1,000 person-days of observation.

Print them side by side, each with its own denominator, and sort the table by each in turn.

The sites do not rank the same way. Three of them sit within a tenth of a point of each other on the proportion and separate cleanly on the rate, because they are not holding their children for the same length of time. Work out which figure moved which site, and why, before reading part three.

Do the same for deaths. Ten deaths across the register is a proportion of about 1.0% and a rate of about 0.17 per 1,000 person-days — two numbers that a reader will assume are the same statistic unless the table says otherwise.

Part three: the exposure is not the same

The reason the two figures disagree is in the length of stay, and it is worth seeing directly.

episodes |>
  summarise(
    n = n(),
    median_days = median(person_days),
    .by = c(site_id, outcome)
  )

Defaulters leave early — a median of about 22 days against 58 for cured children — so a site that cures more children accumulates more person-days per episode and dilutes its own rate. The proportion asks “of the children who finished, how many defaulted”; the rate asks “per day of treatment offered, how often did somebody stop coming”. They are different questions, and a site with a long median stay answers them differently.

Write the two questions out in your script, above the two figures, in the wording you would use with a programme manager.

Part four: the table that is the deliverable

Site Episodes Person-days Defaulters % of outcomes per 1,000 pd
SITE-01 ? ? ? ? ?
…

Every row carries both denominators. Then add two lines beneath it:

  • the site that is worst on both figures, and
  • the site whose ranking changes between them, with the length-of-stay number that explains it.

Check your numbers

Expected
Total person-days about 58,400
Defaulter rate, whole register about 2.3 per 1,000 person-days
Mortality rate, whole register about 0.17 per 1,000 person-days
Worst site, defaulter proportion about 20.5%
Worst site, defaulter rate about 3.6 per 1,000 person-days

If your person-day total is far below 58,400, the censored episodes were dropped rather than counted to the cut-off. If it is far above, a discharge date is being read as text and subtracting oddly.

The questions to answer in prose

Three sentences each.

1. Your two defaulter figures rank the sites differently. Name the site that moves, give both of its numbers, and say which of the two you would put in a performance report and why.

2. Censoring the 71 children at the cut-off assumes something about them. State the assumption, and describe the circumstance in which it would be wrong in a way that biases the rate.

3. The mortality rate per 1,000 person-days is far smaller than the mortality percentage, and both are correct. Explain to a manager why the two numbers differ by a factor of sixty, and what each one is useful for.

What to hand in

An R script, sourced from a clean session, producing:

  • the per-site table above, with both denominators on every row
  • the median length of stay by outcome and by site
  • the whole-register person-time totals, printed with the cut-off date and the transfer decision stated as comments at the head of the file
  • the three prose answers as a comment block at the foot

How to know you are done

Delete outputs/, restart R, one source(), every table regenerates identically. Changing the cut-off date at the top of the script changes every rate and no proportion — if a proportion moves, person-time has leaked into a denominator where it does not belong.

What this lab is not

It is not survival analysis. Kaplan–Meier curves, hazard ratios and time-varying exposure are a later subject, and none of them is needed to see the thing this lab exists for: that a register is a cohort, that observation time is data, and that a percentage which ignores it is answering a narrower question than the one that was asked.