Matplotlib·Lesson 8·13 min·0/3 exercises

Distributions

What one column looks like before you average it

ax.histbinsdensityax.boxplotax.violinplotnp.histogram

Watch it happen

Play it through, or step back and forth yourself.

ax.hist(wait, bins=10)chop, count, draw
n, edges, patches = ...what it hands back
np.histogram(wait, 10)the counts without a chart

Everything so far plotted one thing against another. A histogram plots one column against itself: chop the range into bins, count what falls in each. It's how you find out what "typical" even means before you start averaging.

The idea

Every chart so far plotted one thing against another. A histogram plots one column against itself: chop the range into bins, count what falls in each. It's how you find out what "typical" means before you start quoting averages.

fig, ax = plt.subplots()
n, edges, patches = ax.hist(orders["wait"], bins=10)
ax.set_xlabel("wait (seconds)")
ax.set_ylabel("orders")

Three things come back: the counts per bin, the len(n) + 1 bin edges, and the Rectangle artists. For our 120 wait times with 10 bins that's [4, 26, 30, 27, 14, 7, 5, 3, 2, 2] — the bulk between 30 and 90 seconds, and a long thin tail out to 218. An average would have folded that tail silently into one number.

If you want the counts without a chart, np.histogram(wait, bins=10) gives you the first two and skips the drawing.

Bins change the story

This is the one chart parameter that can change your conclusion rather than the appearance. Too few bins and the shape flattens into a lump — the tail disappears. Too many and every bin holds one or two rows, so you're reading sampling noise as structure. Nothing warns you, because there is no correct answer to warn about.

ax.hist(wait, bins=30)                  # a fair default for a few hundred rows
ax.hist(wait, bins="auto")              # numpy picks — 12, here
ax.hist(wait, bins=[0, 30, 60, 90, 120, 240])   # edges that mean something

Explicit edges are underrated. If your reader thinks in "under a minute / one to two minutes / longer", give them those boundaries rather than whatever an algorithm chose.

Two more arguments worth knowing:

  • density=True — the bars integrate to 1 instead of counting rows, which is how you compare two groups of different sizes on the same axes.
  • cumulative=True — answers "how many were served in under 60 seconds?" without arithmetic.

Comparing two distributions? Plot both with alpha=0.5 and density=True, or pass a list and use histtype="step" so the outlines don't hide each other.

What the shape tells you

Our wait times are right-skewed: a long tail on the high side pulls the mean up past the median.

s = pd.Series(wait)
s.mean()     # 77.57
s.median()   # 70.65
s.skew()     # 1.19

Whenever mean and median disagree like that, the mean is not "typical" — it's being dragged by the tail. Quote the median for a typical wait, then quote the tail separately (s.quantile(0.95)), because the tail is where the complaints come from. Waiting times, incomes, file sizes and response latencies are all this shape, which is why "average response time" is such a poor service metric.

Box plots

ax.boxplot() compresses the same distribution to five numbers: the median line, a box holding the middle half (the quartiles), whiskers out to 1.5× that box, and anything beyond as individual outlier dots.

bp = ax.boxplot([wait[:60], wait[60:]], tick_labels=["early", "late"])
sorted(bp.keys())   # boxes, caps, fliers, means, medians, whiskers

You lose the shape entirely — two very different histograms can produce identical boxes, and a box will never show you that a distribution has two humps. That's the price.

What you buy is comparison. Twelve histograms don't fit on a page; twelve boxes compare at a glance. So: one distribution, histogram. Many, box plots. ax.violinplot() sits between the two, drawing the box's summary with the histogram's shape around it.

See it run

The lesson's code, ready to run and to fiddle with.

Putting the kettle on…

Starting up…

Worked example

not graded

Already written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.

trychanging bins to 5, then 40, and watching the tail appear and disappear.

Press Run — the output appears here.

Your turn

3 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.

Draw a histogram of wait with 10 bins and return the counts as a list of ints: [int(v) for v in n].

your answer

How many bins would numpy choose for wait on its own? Return the number, using np.histogram_bin_edges with bins="auto". (Edges, not bins — mind the off-by-one.)

your answer

Box-plot the first 60 wait times against the last 60 as two boxes, then return [len(bp["boxes"]), len(bp["medians"])].

your answer