---
title: 'Choosing Descriptive Summaries'
source: 'https://academia.sh/en/courses/statistical-analysis-and-exploratory-data-analysis/choosing-descriptive-summaries'
course: 'Statistical Analysis and Exploratory Data Analysis'
language: en
updated: '2026-10-07T04:31:13+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Computes the mean, median and trimmed mean on the same dataset and writes how many cubic meters the choice moved the reported number'
    - 'Produces the margin of variation of each summary with bootstrap and reads the margin next to the choice difference'
    - 'Shows with numbers that the summary with the narrow margin is not the right summary by itself, and that the three summaries answer three different questions'
---

# Choosing Descriptive Summaries

Three defensible summaries come out of the same 557 readings: the mean is 24.674 m³, the median 24.120 m³ and the 10% trimmed mean 24.581 m³, with a largest difference of 0.554 m³ among them. Bootstrap measures the margin of variation of all three and the widths come out as 1.552, 1.762 and 1.458 m³: the difference the choice moves is a third of the margins, and all three intervals contain the 25 m³ tariff step. When the same computation drops from the 557-reading group to the 142-reading group, the choice difference rises to 1.679 m³ and the margin width to 2.917 m³, and this time the decision cannot be read independently of the margin.

The exploration phase did not produce a decision. Over five lessons the distributions were opened,
the breakdowns were tried and the relationships were scanned, and what remained is a list of
questions; none of the questions on the list has been tested yet. Every breakdown that was looked at
during exploration also left a debt to testing, and that debt will be counted at the end of the topic.

The first step of testing is not, as one might think, choosing a test. Before it comes a step that is
often skipped: with which summary the number to be tested will be written. When the "mean
consumption" of the east region's readings is asked for, there are at least three defensible answers,
and the three give three different numbers from the same dataset. The measure of this lesson is how
many cubic meters this choice moves the number in the report, and how much that movement amounts to
next to the number's own margin.

- **AS1. A descriptive summary is a decision.** For the same dataset the mean, the median and the
  trimmed mean give three different numbers; none of them is an approximation of another, and the
  three answer three different questions.
- **AS2. Every summary has a margin of variation**, and the margin is measured with **bootstrap**:
  the dataset is drawn from itself many times, at the same size and with replacement, and the summary
  is recomputed at every draw. The draw count is written down; the margin depends on that count too.
- **AS3.** A summary with a narrow margin is not the right summary by itself. Narrowness does not
  change **what** the summary answers; it only says how much that answer moves from sample to sample.
- **AS4.** The difference the choice moves is read next to the margin. An argument over a difference
  smaller than the margin is an argument over noise.
- **AS5.** The smaller the group, the larger the margin. Next to every reported summary is written
  **how many observations it rests on**; the margin of a summary without an `n` cannot be read.

## Three Summaries, Three Numbers

```python
# dataset.py — MODEL: the measurement network of K03-K04 is regenerated with the same seed.
# A long-format reading table of 3,199 rows, 1,329 subscribers, three periods, five regions.
import math

SEED, RAW, M32 = 20260218, 1400, 0xFFFFFFFF
PERIODS = ["2026-01", "2026-02", "2026-03"]
REGION = [("north", 0.28, 21), ("south", 0.22, 17), ("east", 0.18, 26),
          ("west", 0.14, 14), ("central", 0.18, 23)]


def rng(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def next_value():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return next_value


def pick(u, w):
    t = 0.0
    for i, x in enumerate(w):
        t += x
        if u < t:
            return i
    return len(w) - 1


_kept, _dropped = [], []
for i in range(RAW):
    r = rng(SEED + i)
    b = REGION[pick(r(), [x[1] for x in REGION])]
    pick(r(), [0.06, 0.24, 0.30, 0.24, 0.11, 0.05])
    pick(r(), [0.08, 0.14, 0.27, 0.34, 0.17])
    (_dropped if r() < 0.046 else _kept).append(
        {"subscriber_id": 10001 + i, "region": b[0], "base": b[2]})
VALID = {a["subscriber_id"] for a in _kept}

READINGS = []
for a in _kept + _dropped:
    r = rng(SEED + 7000 + a["subscriber_id"])
    for d in range(pick(r(), [0.05, 0.12, 0.21, 0.62])):
        t = 0.0 if r() < 0.038 else math.floor(
            a["base"] * math.exp((r() + r() + r() - 1.5) * 0.62)
            * (1 - d * 0.05) * 100 + 0.5) / 100
        if a["subscriber_id"] not in VALID:
            r()
            continue
        READINGS.append({"subscriber_id": a["subscriber_id"], "region": a["region"],
                         "period": PERIODS[d], "consumption_m3": t})
        r()


def mean(v):
    return sum(v) / len(v)


def median(v):
    s = sorted(v)
    n = len(s)
    return s[n // 2] if n % 2 else (s[n // 2 - 1] + s[n // 2]) / 2


def trimmed(v, p=0.10):
    s = sorted(v)
    k = int(len(s) * p)
    return mean(s[k:len(s) - k])


east = [o["consumption_m3"] for o in READINGS if o["region"] == "east"]
U = [mean(east), median(east), trimmed(east)]
print(f"fictional dataset (seed {SEED}): {len(READINGS)} rows, {len(VALID)} subscribers")
print(f"east region: {len(east)} readings, {sum(1 for x in east if x == 0.0)} zero readings")
print(f"mean {U[0]:.3f} | median {U[1]:.3f} | trimmed(10%) {U[2]:.3f}")
print(f"largest difference among the three {max(U) - min(U):.3f} m3")
```

```
fictional dataset (seed 20260218): 3199 rows, 1329 subscribers
east region: 557 readings, 20 zero readings
mean 24.674 | median 24.120 | trimmed(10%) 24.581
largest difference among the three 0.554 m3
```

The dataset is fictional and is regenerated with the same seed from the measurement network of
K03–K04; the 3,199 rows and 1,329 subscribers are the same numbers the previous course left behind.
The east region's mean of 24.674 m³ also comes from there, and it is the number asked about at the
opening of this course.

Now two more numbers stand next to it. The median is 24.120 m³: the value that stands in the middle
when the readings are sorted, that is, the number below which half of the dataset stays. The 10%
trimmed mean is 24.581 m³: the mean of the 447 values left after the smallest 55 and the largest 55
readings are removed. All three are correct, all three come from the same 557 readings, and the
largest difference among them is 0.554 m³.

That the mean is larger than the median is a piece of information: the distribution is **skewed** to
the right, that is, a few large readings pull the mean up. The twenty zero readings, on the other
hand, work in the opposite direction and press the mean down. These two effects partly cancel each
other inside the same number, and someone who looks only at the mean cannot see either of them.

## Measuring the Margin

```python
# margin.py — the margin of variation is measured with bootstrap: the dataset is drawn from itself
# 1,000 times at the same size and with replacement, the summary is recomputed at every draw,
# and the middle 95% of the 1,000 values is taken.
DRAWS = 1000


def percentile(s, p):
    i = p * (len(s) - 1)
    lower = int(i)
    upper = min(lower + 1, len(s) - 1)
    return s[lower] + (s[upper] - s[lower]) * (i - lower)


def margin(v, summary, seed, draws=DRAWS):
    rand = rng(seed)
    n = len(v)
    d = sorted(summary([v[int(rand() * n)] for _ in range(n)]) for _ in range(draws))
    return percentile(d, 0.025), percentile(d, 0.975)


E = [12, 10, 10, 10, 10]
y = lambda h: "".join(str(v).rjust(E[k]) if k else str(v).ljust(E[0])
                      for k, v in enumerate(h))
SUMMARIES = (("mean", mean), ("median", median), ("trimmed", trimmed))
print(y(["summary", "number", "lower", "upper", "width"]))
for name, f in SUMMARIES:
    a, b = margin(east, f, SEED + 101)
    print(y([name, round(f(east), 3), round(a, 3), round(b, 3), round(b - a, 3)]))
print(f"\n{len(east)} observations, {DRAWS} draws, all three on the same draws")
print("the 25 m3 tariff step stays inside all three intervals")
```

```
summary         number     lower     upper     width
mean            24.674    23.889    25.441     1.552
median           24.12    23.248     25.01     1.762
trimmed         24.581    23.876    25.334     1.458

557 observations, 1000 draws, all three on the same draws
the 25 m3 tariff step stays inside all three intervals
```

Bootstrap answers a single question: if another set of readings of the same size had come from the
same network, what would this summary have produced. Since there is no other dataset at hand, the
dataset itself is put in the place of the population, 557 values are drawn from it with replacement,
the summary is recomputed, and this is repeated 1,000 times. The middle 95% of the resulting 1,000
values is the summary's **margin of variation**.

The three summaries were computed on the same draws; the difference between them comes from the
summary, not from the luck of the draw. The widths lie between 1.458 and 1.762 m³. The difference the
choice moved, on the other hand, was 0.554 m³, that is, a third of even the narrowest margin. The
argument over which of the three summaries to write is, in this dataset, an argument that stays
inside the margin.

In the report this amounts to a single sentence: is the east region's consumption below the 25 m³
tariff step. All three numbers are below it, but all three intervals contain 25. The claim of staying
below the step cannot be defended without writing the margin, whichever summary is chosen. In a report
that does write the margin, the claim softens by itself and takes its correct form: the three observed
numbers are all below the step, but this data cannot rule out that another set of readings from the
same network would exceed the step.

## Which Summary Answers What

```python
# choice.py — what the summary choice writes into the report: total volume and trimmed rows.
k = int(len(east) * 0.10)
print(f"mean x n = {mean(east) * len(east):.2f} m3 reported volume")
print(f"median x n = {median(east) * len(east):.2f} m3 same product, different number")
print(f"difference between the two volumes {(mean(east) - median(east)) * len(east):.2f} m3")
print(f"trimmed mean leaves {2 * k} readings out; "
      f"{sum(1 for x in sorted(east)[:k] if x == 0.0)} of them are zero readings")
nonzero = [x for x in east if x > 0.0]
print(f"if zero readings are not counted at all the mean becomes {mean(nonzero):.3f} m3")
```

```
mean x n = 13743.34 m3 reported volume
median x n = 13434.84 m3 same product, different number
difference between the two volumes 308.50 m3
trimmed mean leaves 110 readings out; 20 of them are zero readings
if zero readings are not counted at all the mean becomes 25.593 m3
```

That the three summaries answer three different questions is visible here. The mean carries the
**total** of the dataset: multiplied by 557 it gives 13,743.34 m³, and this is the volume that
passed through the network itself. The median gives 13,434.84 m³ in the same product, and this number
is the total of nothing; the 308.50 m³ in between is the measure of the median not answering the
volume question. When the volume to be billed is asked, the median is the wrong summary.

The trimmed mean answers the third question: a center that resists extreme readings. It does so by
leaving 110 readings out, and 20 of the ones it leaves out are zero readings. This is the summary with
the narrowest margin, but the source of the narrowness is not the dataset itself, it is that 20% of
the dataset does not enter the computation. A narrow margin here is not an advantage, it is a sign of
a narrowed coverage.

The last line shows what the coverage decision does by itself. If the zero readings are not counted at
all, the mean becomes 25.593 m³ and exceeds the tariff step. What the zero readings are was dealt with
in the cleaning lesson of M26/K03; no elimination is done here, it is only recorded that the coverage
decision moved the summary by 0.919 m³ and that this movement is larger than the choice difference.

## When the Group Shrinks

```python
# small.py — the same three summaries and the same margin, recomputed on a small breakdown.
sub = [o["consumption_m3"] for o in READINGS
       if o["region"] == "east" and o["period"] == "2026-03"]
for name, v in (("east / all", east), ("east / 2026-03", sub)):
    s = [f(v) for _, f in SUMMARIES]
    g = [b - a for a, b in (margin(v, f, SEED + 101) for _, f in SUMMARIES)]
    print(f"{name}: n={len(v)}, choice difference {max(s) - min(s):.3f} m3, "
          f"margin width between {min(g):.3f} and {max(g):.3f} m3")
    print(f"    mean {s[0]:.3f} | median {s[1]:.3f} | trimmed {s[2]:.3f}")
```

```
east / all: n=557, choice difference 0.554 m3, margin width between 1.458 and 1.762 m3
    mean 24.674 | median 24.120 | trimmed 24.581
east / 2026-03: n=142, choice difference 1.679 m3, margin width between 2.527 and 2.917 m3
    mean 22.394 | median 20.715 | trimmed 22.112
```

The same region, a single period: 142 readings. The choice difference rose from 0.554 m³ to 1.679
m³, that is, to three times. The widths of the margins grew too and settled in a band of 2.5–2.9 m³.
The two increases are read together: in the small breakdown the summaries both move away from each
other and the variation of each one grows.

The source of the divergence is visible. In this period the mean is 22.394 m³ and the median is
20.715 m³, and the range of 1.679 m³ in between comes from a few large readings pulling the mean in the
single-period breakdown. The trimmed mean, at 22.112 m³, stays next to the mean, because the readings
doing the pulling are more than the trimmed 10%.

For the report the result is this: in the 557-reading group the summary choice is a detail that stays
inside the margin, whereas in the 142-reading group the choice difference exceeds half of the margin
and the decision can no longer be read independently of the margin. In both, what must be written is
the same — next to the number, the `n` and the margin.

## The Margin Is Also a Number

```python
# draws.py — the margin comes from a run, not from a calculation; when the run count changes, the
# bounds of the margin move too. In this course the upper limit is 1,000 draws.
for c in (200, 500, 1000):
    a, b = margin(east, mean, SEED + 101, c)
    print(f"{c:5d} draws: {a:.3f} - {b:.3f}, width {b - a:.3f} m3")
```

```
  200 draws: 23.955 - 25.406, width 1.451 m3
  500 draws: 23.949 - 25.426, width 1.477 m3
 1000 draws: 23.889 - 25.441, width 1.552 m3
```

The margin comes not from a formula but from a run, and when the run count changes the bounds of the
margin move too. With two hundred draws the width is 1.451 m³, with a thousand draws it is 1.552 m³;
the 0.101 m³ in between is the margin's own uncertainty. The ends of the interval are the part that
moves the most, because the ends rest on the outermost twenty-fifth values of the 1,000, and in that
region there are few draws.

In this course the draw count does not exceed 1,000. The limit is not a convenience but a record: a
reported margin is not complete without writing how many draws it came from. The same rule will hold
for the permutation count in later lessons, and there the run count will directly determine the
resolution of the margin.

## Summary

- The same 557 readings give three defensible summaries: the mean is 24.674 m³, the median 24.120 m³,
  the 10% trimmed mean 24.581 m³; the largest difference the choice moves is 0.554 m³.
- Bootstrap puts the dataset in the place of the population, draws from it 1,000 times with
  replacement and measures the margin of each summary: the widths come out as 1.552, 1.762 and 1.458
  m³, that is, three times the choice difference.
- The summary with the narrowest margin is the trimmed mean, but the narrowness comes from 110
  readings not entering the computation; a narrow margin does not mean the right summary, it means the
  coverage narrowed.
- The three summaries answer three different questions: the mean carries the total and its product
  with 557 is 13,743.34 m³, while the median's same product, 13,434.84 m³, corresponds to no volume.
- When the breakdown drops from 557 readings to 142, the choice difference rises to 1.679 m³ and the
  margin width to 2.917 m³; this is why the number of observations it rests on is written next to
  every summary.

## Next Step

A single number was chosen and its margin was written, but a single number does not describe a
distribution. The real question in a report is often the comparison of two groups, and by habit this
comparison is made over five numbers: the minimum, the first quartile, the median, the third quartile,
the maximum. The next lesson shows that these five numbers do not determine a distribution — two
datasets whose five numbers are identical to each other are built, the bin counts are printed side by
side, and it comes out in numbers that one has a single peak and the other has two. The notch interval
of the median is again produced with bootstrap.
