Distributions
What one column looks like before you average it
ax.histbinsdensityax.boxplotax.violinplotnp.histogramWatch it happen
Play it through, or step back and forth yourself.
ax.hist(wait, bins=10)chop, count, drawn, edges, patches = ...what it hands backnp.histogram(wait, 10)the counts without a chartEverything 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 somethingExplicit 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.19Whenever 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, whiskersYou 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 gradedAlready 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.
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].
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.)
Box-plot the first 60 wait times against the last 60 as two boxes, then return [len(bp["boxes"]), len(bp["medians"])].
