cassionData Analysis

Back to the lessonLesson 1 of 8Look at it first

A mean of 29.6 and a median of zero

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

    What this lesson covers

    • Compute the summary last
    • Look at the shape
    • Three shapes and what each demands
    • When the skew is the data error
    • What to report for each shape
    • The histogram you should always draw
    • What comes next
    Speaker notes
    The mean E. coli count in tested households is 29.6 CFU/100 mL. The median is 0. More than half the sample has no detectable contamination, and the mean is describing a tail that reaches 608.
  2. Slide 2 / 27

    Compute the summary last — In Python

    import pandas as pd
    
    wash = pd.read_csv("wash-household-survey-2024.v1.csv")
    ecoli = wash["ecoli_cfu_100ml"].dropna()
    
    print(ecoli.describe().round(1))
  3. Slide 3 / 27

    Compute the summary last — In R

    library(dplyr)
    
    wash |> filter(!is.na(ecoli_cfu_100ml)) |>
      summarise(n = n(), mean = mean(ecoli_cfu_100ml),
                median = median(ecoli_cfu_100ml), sd = sd(ecoli_cfu_100ml))
  4. Slide 4 / 27

    Compute the summary last

    Value
    n802
    Mean29.6
    Median0.0
    Standard deviation85.6
    Maximum608
  5. Slide 5 / 27

    Compute the summary last

    • The mean is 29.6 and the median is 0 — Those two numbers describe the same 802 households, and only one of them…
    Speaker notes
    The mean is 29.6 and the median is 0. Those two numbers describe the same 802 households, and only one of them describes any of them. More than half the tested households have no detectable E. coli at all. The mean is what you get by averaging a large group of zeros with a small group of very large numbers, and it lands in a region where almost nobody sits.
  6. Slide 6 / 27

    Look at the shape — In Python

    bins = [-1, 0, 10, 100, 1000]
    labels = ["0 (none detected)", "1-10", "11-100", ">100"]
    print(pd.cut(ecoli, bins, labels=labels).value_counts().reindex(labels))
    print(f"\nskew: {ecoli.skew():.2f}")
  7. Slide 7 / 27

    Look at the shape — In R

    wash |> filter(!is.na(ecoli_cfu_100ml)) |>
      count(band = cut(ecoli_cfu_100ml, c(-1, 0, 10, 100, Inf)))
  8. Slide 8 / 27

    Look at the shape

    BandHouseholdsShare
    0 — none detected43053.6%
    1–1017321.6%
    11–10013917.3%
    >100607.5%
  9. Slide 9 / 27

    Look at the shape

    • Skew +4.04 — A symmetric distribution has a skew near zero; anything past about +1 means the mean and the median are…
    • This is why the WASH course reported risk classes rather than a mean — The classes are not a presentational choice —…
    Speaker notes
    Skew +4.04. A symmetric distribution has a skew near zero; anything past about +1 means the mean and the median are answering different questions. This is why the WASH course reported risk classes rather than a mean. The classes are not a presentational choice — they are the only summary that survives a distribution shaped like this one.
  10. Slide 10 / 27

    Three shapes and what each demands — In Python (cont.)

    import numpy as np
    
    def profile(series, name):
        s = series.dropna()
        return {
            "indicator": name, "n": len(s),
            "mean": round(s.mean(), 1), "median": round(s.median(), 1),
            "skew": round(s.skew(), 2),
        }
    
    points = pd.read_csv("water-point-monitoring-2024.v1.csv")
    protection = pd.read_csv("protection-referrals-2024.v1.csv")
    
    print(pd.DataFrame([
        profile(wash["ecoli_cfu_100ml"], "E. coli, CFU/100mL"),
        profile(points["days_since_breakdown"], "days a water point is down"),
    Speaker notes
    Run the same three numbers over four indicators from four courses and the pattern is immediate.
  11. Slide 11 / 27

    Three shapes and what each demands — In Python (cont.)

        profile(protection["days_to_first_service"], "days to first service"),
        profile(wash["litres_per_person_day"], "litres per person per day"),
    ]))
  12. Slide 12 / 27

    Three shapes and what each demands — In R

    # One function, four indicators, four different answers about which summary
    # to report.
  13. Slide 13 / 27

    Three shapes and what each demands

    IndicatornMeanMedianSkew
    E. coli, CFU/100 mL80229.60.0+4.04
    Days a water point is down709125.756.0+1.64
    Days to first service72810.19.0+0.52
    Litres per person per day2,40324.223.7+8.54
  14. Slide 14 / 27

    Three shapes and what each demands

    • Days to first service is nearly symmetric — mean 10.1, median 9.0, skew +0.52
    • Days a water point is down is heavily right-skewed — a mean of 125.7 against a median of 56, because a handful of…
    • Litres per person per day looks almost symmetric and has a skew of +8.54 — That combination is the interesting one
    Speaker notes
    Days to first service is nearly symmetric — mean 10.1, median 9.0, skew +0.52. A mean is a fair summary and a standard deviation means something. Days a water point is down is heavily right-skewed — a mean of 125.7 against a median of 56, because a handful of abandoned points have been broken for over 600 days. Report the median and the quartiles. Litres per person per day looks almost symmetric and has a skew of +8.54. That combination is the interesting one.
  15. Slide 15 / 27

    When the skew is the data error — In Python

    litres = wash["litres_per_person_day"].dropna()
    print(f"median {litres.median():.1f}, p90 {litres.quantile(0.90):.1f}, "
          f"max {litres.max():.1f}")
    print(f"above 80: {(litres > 80).sum()} households")
  16. Slide 16 / 27

    When the skew is the data error — In R

    wash |> summarise(median = median(litres_per_person_day),
                      p90 = quantile(litres_per_person_day, 0.9),
                      max = max(litres_per_person_day))
  17. Slide 17 / 27

    When the skew is the data error — In Python

    clean = litres[litres <= 80]
    print(f"after removing 11 rows: skew {clean.skew():.2f}, "
          f"mean {clean.mean():.1f}, median {clean.median():.1f}")
    Speaker notes
    Median 23.7, ninetieth percentile 33.7, maximum 277.6. The skew statistic is not describing the population; it is detecting eleven bad rows. Those eleven are the households whose total consumption was entered in a per-person column — the defect the WASH course taught. Here it arrives from the other direction: a skew far out of line with the interquartile range is a data quality signal before it is a distributional finding.
  18. Slide 18 / 27

    When the skew is the data error — In R

    # Recompute after the correction and see whether the shape was real.
  19. Slide 19 / 27

    When the skew is the data error

    • Eleven rows out of 2,403 and the skew goes from +8.54 to +0.06 — Mean 23.5, median 23.6 — a distribution that was…
    • Compute the skew, then decide whether it is a finding or a bug — Both are common and they are told apart by looking at…
    Speaker notes
    Eleven rows out of 2,403 and the skew goes from +8.54 to +0.06. Mean 23.5, median 23.6 — a distribution that was symmetric all along, wearing a shape that belonged entirely to a unit error. Compute the skew, then decide whether it is a finding or a bug. Both are common and they are told apart by looking at the extreme values, not by looking at the statistic.
  20. Slide 20 / 27

    What to report for each shape

    ShapeReportDo not report
    Roughly symmetricMean and standard deviation—
    Right-skewedMedian and interquartile rangeA mean without the median beside it
    Zero-inflatedThe share at zero, then the distribution of the restA mean at all
    Bounded proportionThe proportion and its intervalA standard deviation
  21. Slide 21 / 27

    What to report for each shape

    • E. coli is zero-inflated — which is a distinct case from merely skewed: 53.6% of the sample sits at exactly one value,…
    Speaker notes
    E. coli is zero-inflated, which is a distinct case from merely skewed: 53.6% of the sample sits at exactly one value, and no continuous summary describes that. The two-part report — how many are at zero, and among the rest, how bad — is the only honest one.
  22. Slide 22 / 27

    What to report for each shape — Example

    E. coli at point of collection, 802 households tested
    
      No detectable E. coli        53.6%   430 households
      Among the 372 with any detected:
        median                     13 CFU/100 mL
        interquartile range        5 to 49
        maximum                    608
    
      Mean over all 802 households is 29.6 CFU/100 mL. It is not reported as a
      summary because 53.6% of the sample sits at zero and the mean falls in a
      range occupied by almost no household.
  23. Slide 23 / 27

    The histogram you should always draw — In Python

    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(1, 2, figsize=(9, 3))
    axes[0].hist(ecoli, bins=40)
    axes[0].set_title("E. coli, raw")
    axes[1].hist(np.log1p(ecoli), bins=40)
    axes[1].set_title("log(1 + E. coli)")
    plt.tight_layout()
  24. Slide 24 / 27

    The histogram you should always draw — In R

    hist(wash$ecoli_cfu_100ml, breaks = 40)
    hist(log1p(wash$ecoli_cfu_100ml), breaks = 40)
  25. Slide 25 / 27

    The histogram you should always draw

    • Draw it before you summarise it, every time, and do not put it in the report — The histogram is an instrument for you,…
    • Two minutes of looking would have prevented every error in this lesson — and that is the entire argument for the habit
    Speaker notes
    Draw it before you summarise it, every time, and do not put it in the report. The histogram is an instrument for you, not a finding for the reader — its job is to tell you which summary is honest, and once it has done that the summary goes in and the histogram stays out. Two minutes of looking would have prevented every error in this lesson, and that is the entire argument for the habit.
  26. Slide 26 / 27

    What comes next

    • You now know which single number describes an indicator.
    Speaker notes
    You now know which single number describes an indicator. The next lesson is about how much that number could be wrong by, and the notation that carries it into a sentence a reader can act on.
  27. Slide 27 / 27

    Where this goes next

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