DEV Community

Wenlo
Wenlo

Posted on AI-assisted

How stable is your mean? A small bootstrap experiment in a Python notebook

Fourteen fictional tasks took between 17 and 52 minutes. Their mean is a useful summary, but how much does that summary depend on a small sample and one long observation?

This notebook compares mean and median, resamples the observations, plots the distribution of resampled means, and tests a deliberate alternative dataset. It also demonstrates a practical notebook habit: restart and run every cell before trusting the final chart.

Cell 1: keep the sample visible

The data is fictional. It is not a claim about anyone's productivity, and the larger value is not automatically a mistake. Keeping the original observations in the first cell makes the experiment inspectable.

import random
from statistics import mean, median, quantiles
import matplotlib.pyplot as plt

minutes = [18, 22, 19, 21, 25, 20, 17,
           23, 19, 24, 20, 18, 22, 52]
assert len(minutes) >= 2
assert all(value > 0 for value in minutes)
print("Fictional observations:", len(minutes))
print(f"Mean: {mean(minutes):.2f} minutes")
print(f"Median: {median(minutes):.2f} minutes")
Enter fullscreen mode Exit fullscreen mode

The actual iPhone PythonIDE run reported 14 observations, a mean of 22.86 minutes, and a median of 20.50 minutes. The mean uses every value; the median uses the middle of the sorted sample and is less sensitive to this long observation.

PythonIDE notebook input and actual mean and median

Cell 2: resample with replacement

Each bootstrap sample contains 14 draws from the original 14 values, with replacement. Some observations can appear more than once while others are absent. We compute a mean for each of 2,000 such samples.

def bootstrap_means(values, repeats=2000, seed=42):
    if not values or repeats < 2:
        raise ValueError("Need data and at least two repeats")
    rng = random.Random(seed)
    return [mean(rng.choices(values, k=len(values)))
            for _ in range(repeats)]

simulated = bootstrap_means(minutes)
assert simulated == bootstrap_means(minutes)
cutoffs = quantiles(simulated, n=40, method="inclusive")
low, high = cutoffs[0], cutoffs[-1]
print(f"Approximate percentile interval: {low:.2f}–{high:.2f}")
print("Resampled means:", len(simulated))
Enter fullscreen mode Exit fullscreen mode

The local Random instance avoids dependence on unrelated notebook random calls. Repeating the function with the same input and seed is checked explicitly. A seed aids repeatability in this runtime; it does not remove sampling uncertainty or guarantee identical high-level random sequences across all Python versions.

The actual run produced an approximate percentile interval of 19.71 to 27.93 minutes. quantiles(..., n=40) divides the simulated distribution into 40 parts; the first and last cutoffs correspond to 2.5% and 97.5% using the specified interpolation method.

Treat this as a bootstrap teaching example. Fourteen observations are a small sample. A bootstrap cannot invent rare events that were never observed, repair selection bias, or make correlated measurements independent. The interval is not a range containing 95% of individual task durations.

Cell 3: inspect the whole distribution

fig, ax = plt.subplots(figsize=(7, 4))
ax.hist(simulated, bins=30, color="#3B82F6")
ax.axvline(mean(minutes), color="#EF4444", label="Observed mean")
ax.axvspan(low, high, color="#22C55E", alpha=.15,
           label="Approx. percentile interval")
ax.set(xlabel="Resampled mean (minutes)",
       ylabel="Resample count",
       title="Fictional task durations")
ax.legend()
plt.tight_layout()
plt.show()
Enter fullscreen mode Exit fullscreen mode

The histogram counts resampled means, not original task durations. The red line shows the original sample mean, and the shaded band shows the percentile interval. Reading the axis labels matters: a distribution of means is a different object from a distribution of individual observations.

PythonIDE notebook plotting cell and bootstrap histogram

Cell 4: change one assumption without changing the original

alternative = minutes[:-1] + [22]
print("Original mean:", round(mean(minutes), 2))
print("Alternative mean:", round(mean(alternative), 2))
print("Original median:", median(minutes))
print("Alternative median:", median(alternative))
assert minutes[-1] == 52  # The original data is intact.
Enter fullscreen mode Exit fullscreen mode

Replacing 52 with 22 in a separate copy lowered the actual-run mean from about 22.86 to 20.71 minutes. Both medians remained 20.5. The final assertion verifies that the original sample still ends in 52.

This is a sensitivity experiment, not data cleaning. We have no evidence that the 52-minute task was erroneous, so we should not silently remove or replace it in a real report.

PythonIDE notebook sensitivity experiment and actual results

Why notebook execution order matters

A notebook can show an old chart beneath newly edited code. Changing the input cell does not automatically rebuild simulated, low, and high in the kernel. Run all four code cells in order after a change, and use a clean kernel rerun to check for hidden state where the current interface supports it.

The supplied notebook keeps explanatory cells short so the code and results are easier to inspect on a phone. The reported numbers above come from the actual PythonIDE run, not invented notebook outputs.

The same code as a script

The four code cells can also run in sequence as a Python script. These screenshots show the complete code and the matching output in PythonIDE.

Complete notebook code exported to a script, first page

Complete notebook code exported to a script, second page

Actual PythonIDE script output confirming the bootstrap interval and sensitivity results

Try one change

Increase repeats to 10,000, then change the seed. Compare the interval endpoints. More bootstrap draws reduce simulation variability, but they do not create more real observations.

Then edit the input and deliberately run only the chart cell. Explain why the chart can remain stale before rerunning the complete chain.

I build PythonIDE, a Python coding app for iPhone and iPad. AI assisted with drafting and translation.

PythonIDE on the App Store

Top comments (0)