cassionData Analysis

Back to the lessonLesson 8 of 8The record that survives you

The log that answers the question before it is asked

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 / 19

    What this lesson covers

    • The question you will be asked
    • A log is a table, not a narrative
    • The seven columns, and why each earns its place
    • Generate it from the code
    • The before-and-after table is the deliverable
    • What must not be in the log
    • Where the log lives
    • The annex the report needs
    • Where this course leaves you
    Speaker notes
    One row per rule applied — what it was, how many rows it touched, what changed, who approved it. Generated from the code rather than written afterwards, and shipped as the annex that ends the argument.
  2. Slide 2 / 19

    The question you will be asked

    • A donor's monitoring visit: "Your caseload fell 4% from the draft to the final report. What changed?"
    • The programme manager: "Last quarter Gros-Morne was second worst. Now it is fourth. Did something improve?"
    • Your successor, in an email: "There is a script here that drops rows. Do you know why?"
    Speaker notes
    It arrives in one of three forms and it always arrives. Every one of them is answerable in thirty seconds with a cleaning log and not at all without one. This lesson is the log.
  3. Slide 3 / 19

    A log is a table, not a narrative

    • One row per rule applied. Every row carries a count
    Speaker notes
    The instinct is to write a paragraph in the methodology section. A paragraph cannot be counted, cannot be diffed against last quarter, and cannot be checked. One row per rule applied. Every row carries a count.
  4. Slide 4 / 19

    A log is a table, not a narrative

    ruleseverityrowsactioneffect on the indicatordecided bydate
    muac-rangeerror7excludedGAM 8.62% to 8.63%R. Cassion2026-07-14
    exact-duplicateerror12one copy keptcaseload 366 to 360R. Cassion2026-07-14
    re-registrationwarning6merged after reviewcaseload 360 to 354M. Joseph, supervisor2026-07-16
    oedema-yn-codingwarning25recoded to falseGAM unchangedR. Cassion2026-07-14
    age-missingnote226kept, flaggedage-disaggregated table onlyR. Cassion2026-07-14
    district-mappingwarning75mapped to HT07Nord-Ouest 14.7% to 15.0%A. Pierre, field office2026-07-15
    Speaker notes
    Read down the rows column and you have the whole story of what happened to this file. Read down effect on the indicator and you have the answer to the donor's question, in advance.
  5. Slide 5 / 19

    The seven columns, and why each earns its place

    • rule — the identifier from the rule table in lesson 5, so the log and the code use the same name. If the log says…
    • severity — what the check meant, so a reader can skim to the errors.
    • rows — the count. Not "a few", not "some outliers". A number.
    • action — excluded, corrected, recoded, merged, kept and flagged. Five verbs cover almost everything, and…
    • effect on the indicator — the before and after. This is the column people skip and the only one a donor reads.
    • decided by — a person. Not "the team". The rule with no name against it is the rule that turns out to be wrong.
  6. Slide 6 / 19

    The seven columns, and why each earns its place (cont.)

    • date — when, so the log can be read against the version of the output it produced.
  7. Slide 7 / 19

    Generate it from the code — In Python (cont.)

    LOG = []
    
    
    def apply_step(df, rule, mask, action, note=""):
        before = indicator(df)
        if action == "exclude":
            out = df[~mask]
        elif action == "flag":
            out = df.assign(**{f"flag_{rule}": mask})
        else:
            raise ValueError(action)
        after = indicator(out)
    
        LOG.append({
            "rule": rule,
            "rows": int(mask.sum()),
    Speaker notes
    A log written afterwards from memory is fiction, and it is always shorter than the truth. Make each cleaning step return its own log row.
  8. Slide 8 / 19

    Generate it from the code — In Python (cont.)

            "action": action,
            "before": before,
            "after": after,
            "effect": round(after - before, 4),
            "note": note,
        })
        return out
    
    
    muac = apply_step(muac, "muac-range", ~muac["muac_mm"].between(80, 220), "exclude")
    muac = apply_step(muac, "age-missing", muac["age_months"].isna(), "flag",
                      "kept; age-disaggregated tables only")
    
    log = pd.DataFrame(LOG)
    log.to_csv("outputs/cleaning-log.csv", index=False)
  9. Slide 9 / 19

    Generate it from the code — In R (cont.)

    LOG <- list()
    
    apply_step <- function(df, rule, mask, action, note = "") {
      before <- indicator(df)
      out <- switch(action,
        exclude = df[!mask, ],
        flag    = dplyr::mutate(df, "flag_{rule}" := mask),
        stop("unknown action: ", action)
      )
      after <- indicator(out)
    
      LOG[[length(LOG) + 1]] <<- tibble::tibble(
        rule = rule, rows = sum(mask, na.rm = TRUE), action = action,
        before = before, after = after, effect = round(after - before, 4), note = note
      )
      out
  10. Slide 10 / 19

    Generate it from the code — In R (cont.)

    }
    
    muac <- apply_step(muac, "muac-range", !dplyr::between(muac$muac_mm, 80, 220), "exclude")
    muac <- apply_step(muac, "age-missing", is.na(muac$age_months), "flag",
                       "kept; age-disaggregated tables only")
    
    readr::write_csv(dplyr::bind_rows(LOG), here::here("outputs", "cleaning-log.csv"))
  11. Slide 11 / 19

    Generate it from the code

    • A step cannot happen without being logged — because logging is what the function does
    • The effect is measured, not estimated — before and after come from the same indicator() function, evaluated on…
    Speaker notes
    Two things this arrangement guarantees that discipline alone does not. A step cannot happen without being logged, because logging is what the function does. The tempting one-line df = df[df.muac_mm > 80] inserted at 6 p.m. on a deadline is exactly the change that never reaches a written log; here there is no shorter way to do it than the logged way. The effect is measured, not estimated. before and after come from the same indicator() function, evaluated on the same data a moment apart. Nobody has to remember what the number was.
  12. Slide 12 / 19

    The before-and-after table is the deliverable — In Python

    print(f"Records received:  {len(raw):>6,}")
    print(f"Excluded:          {len(raw) - len(analysis):>6,}")
    print(f"Analysed:          {len(analysis):>6,}")
    print(f"GAM, raw:          {indicator(raw):>6.1%}")
    print(f"GAM, cleaned:      {indicator(analysis):>6.1%}")
    Speaker notes
    Aggregate the log into three lines and it becomes something a non-technical reader can act on:
  13. Slide 13 / 19

    The before-and-after table is the deliverable — In R

    cat(sprintf("Records received: %6d\n", nrow(raw)))
    cat(sprintf("Excluded:         %6d\n", nrow(raw) - nrow(analysis)))
    cat(sprintf("Analysed:         %6d\n", nrow(analysis)))
    cat(sprintf("GAM, raw:         %6.1f%%\n", 100 * indicator(raw)))
    cat(sprintf("GAM, cleaned:     %6.1f%%\n", 100 * indicator(analysis)))
    Speaker notes
    If the raw and cleaned figures are close, say so — it is the strongest sentence available to you, because it means the finding does not depend on your judgement. If they are far apart, say that too, and expect the conversation. A cleaning process that moves an indicator by three points is not a defect in the analysis; it is a finding about data collection, and hiding it is the only version of this that is misconduct.
  14. Slide 14 / 19

    What must not be in the log

    • No direct identifiers. Not names, not phone numbers, not GPS coordinates. A log row reading `merged CH03315 into…
    • No small cells in a sensitive category. "3 rows recoded, GBV case category rape, Marmelade" identifies people in…
    • No free-text detail copied from a case note. The rule is what you applied, not what the record said.
    Speaker notes
    The log is an annex. It gets emailed, attached to reports and forwarded, which means it travels further than the dataset does. The habit is worth learning on nutrition data, where it costs nothing, so that it is automatic on the day you are handed a protection caseload — where the principles are not advice.
  15. Slide 15 / 19

    Where the log lives — Example

    outputs/
      profiles/muac-2024-q4.json        arrival profile      (lesson 1)
      validation/muac-2024-q4.json      validation findings  (lesson 7)
      cleaning-log.csv                  decisions            (this lesson)
      review/duplicate-candidates.csv   reviewed and signed  (lesson 4)
      tables/gam_by_commune.csv         the result
      tables/definitions.csv            numerator, denominator, source
    Speaker notes
    Beside the output, not in a document. Those six files are a complete account of one quarter. Someone with the raw export and this folder can reconstruct everything you did and check every number, and that is a much stronger claim than "the analysis was careful". Note what is not there: the cleaned dataset is not the deliverable. The raw file stays read-only and the cleaned file is regenerated by running the script. A cleaned CSV emailed around detaches from its log within a week and then nobody can tell which version anyone is holding.
  16. Slide 16 / 19

    The annex the report needs

    • Data source and completeness — Where the file came from, how many records, what share of expected reporting units it…
    • Cleaning and exclusions — The log table, or a summary of it with the full table attached
    • Limitations — What the cleaning could not fix
    Speaker notes
    Three short sections, and they belong in every analysis this sector produces. Data source and completeness. Where the file came from, how many records, what share of expected reporting units it covers, over what period. Cleaning and exclusions. The log table, or a summary of it with the full table attached. Every exclusion with its count. Limitations. What the cleaning could not fix. The missing-not-at-random you suspect but cannot demonstrate. The six merged records a supervisor confirmed and the two they were unsure about. The district whose rate rests on thirty-three households.
  17. Slide 17 / 19

    The annex the report needs

    The annex is not a confession. It is the reason the number in the executive summary can be defended, and the professional difference between a figure and a claim.
  18. Slide 18 / 19

    Where this course leaves you

    • You can take a raw programme export, describe honestly what arrived, find what is wrong with it in four different ways, decide what to do about each fault against a published standard, make those decisions run again next quarter without you, and hand the whole thing to an auditor with the working attached.
    Speaker notes
    You can take a raw programme export, describe honestly what arrived, find what is wrong with it in four different ways, decide what to do about each fault against a published standard, make those decisions run again next quarter without you, and hand the whole thing to an auditor with the working attached. The next course in this module, Joining and Reshaping Programme Data, takes the clean table and puts it together with the others — household to member, register to population denominator, facility register to DHIS2 aggregate — where the failure mode is not a wrong value but a row count that doubled while you were looking at it.
  19. Slide 19 / 19

    Where this goes next

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