Comparing many distributions at once, and what each summary throws away.
Overview
What a box plot is
Five numbers, drawn:
The box spans the first to the third quartile, so it contains the middle half of the data. Its height is the interquartile range.
The line inside is the median — not the mean.
The whiskers reach the furthest data point within 1.5×IQR of the box. They are not a fixed multiple of anything; they stop at real observations.
Points beyond that are drawn individually as outliers.
That 1.5 is a convention, chosen so that roughly 0.7% of normally distributed data falls outside. It has no deeper justification, and matplotlib lets you change it with whis=.
Worth knowing
A box is five numbers: quartiles, median, and whiskers reaching the furthest point within 1.5×IQR. Everything beyond is drawn individually.
Box plots exist to compare many groups at once in the space one histogram would take.
Any distribution with the same five numbers draws the same box — including a bimodal one with a hole in the middle.
A violin shows the shape, at the cost of a bandwidth you did not choose, and it extends past the observed data.
Overlaying the points with jitter restores sample size and shape; showfliers=False stops outliers being drawn twice.
vert=False and sorting by median apply the same readability arguments as barh.
Box and Violin Plots
Comparing many distributions at once, and what each summary throws away.
What the box contains
Five numbers, and everything beyond the whiskers drawn individually.
example_01.pymatplotlib
Output
Comparing groups is what it is for
Many distributions in the space one histogram would take.
example_02.pymatplotlib
Output
What the box hides
Two very different distributions can produce the same box.
example_03.pymatplotlib
Output
Violins show the shape
At the cost of a smoothing parameter you did not choose.
example_04.pymatplotlib
Output
Showing the points as well
The best of both, when the sample is small enough.
example_05.pymatplotlib
Output
Horizontal, and ordered
The same readability arguments as bar charts apply.
example_06.pymatplotlib
Output
What they are for
Comparing many groups.
Five histograms take five panels and require the reader to move between them. Five boxes sit side by side on one axis, sharing a scale, and the comparison is immediate.
That is the whole argument, and it is a strong one. For one distribution, a histogram is better. For eight, a box plot is much better. Somewhere around three the answer changes.
patch_artist=True makes the boxes filled rather than outlines, which is needed before set_facecolor does anything — a common small frustration.
What they hide
A box plot is a summary, and every summary discards.
Any distribution sharing those five numbers produces an identical box. That includes a bimodal distribution — two clusters with a gap in the middle — whose median sits in the empty space where no data is.
The third editor draws exactly that: two distributions with nearly identical boxes and completely different shapes.
This is not a hypothetical failure. Bimodality usually means two populations have been mixed, which is often the most important thing in the data, and the box plot conceals it perfectly.
The practical rule: look at a histogram of each group once before settling on box plots for the comparison.
Violin plots
A violin shows an estimated density, mirrored, so the width at any height is how much data is around that value. It shows the bimodality a box hides.
Two honest caveats.
It is a kernel density estimate, with a bandwidth that smooths the data. That parameter has exactly the arbitrariness of a histogram's bin count, and matplotlib picks it for you. Too much smoothing merges peaks; too little invents them.
It extends past the observed data at both ends, because the kernel has tails. A violin of a strictly positive quantity will happily show density below zero, which is a claim the data does not support.
showmedians=True adds the median line; the defaults draw the extrema.
Showing the points
For small samples, the best display is usually both: a box or violin for the summary, and the individual points with jitter on top.
showfliers=False prevents the outliers being drawn twice, once by the box and once by the scatter.
zorder=3 on the scatter puts the points above the box.
Below roughly fifty points per group this is strictly more informative than either element alone: you get the quartiles and the actual sample. Above a few hundred, the points overplot and the summary is doing the work.
Layout
vert=False draws them horizontally, which lets long category names read normally — the same argument that makes barh preferable to bar.
Sorting the groups by median makes the ranking immediate rather than something the reader assembles.
And as with any grouped comparison, the y axis must be shared, which it is by construction here — that is part of why the display works.
Notches and confidence
notch=True draws a waist around the median whose width approximates a 95% confidence interval for it.
The heuristic it enables is that if two notches do not overlap, the medians differ significantly. That is roughly true and depends on assumptions the plot does not state.
Two practical problems. On small samples the notch can extend beyond the box, producing a folded shape that looks like a rendering error. And the heuristic is exactly the overlap reasoning that is unreliable for error bars, applied to a different statistic.
Useful when comparing many groups quickly; not a substitute for a test.
Ordering and orientation
The same arguments as bar charts apply.
Sort by median unless the category order means something — time, size, an experimental sequence.
Use vert=False when the labels are words.
And keep the y axis shared, which happens automatically here because all groups share one axes, and is exactly why the display works for comparison.
Styling the parts
boxplot returns a dict of the artists, keyed by "boxes", "whiskers", "caps", "medians", "fliers":
bp = ax.boxplot(data, patch_artist=True)
for b in bp["boxes"]:
b.set(facecolor="#a8dadc", edgecolor="0.3")
for m in bp["medians"]:
m.set(color="crimson", linewidth=2)
patch_artist=True is required before boxes can be filled — without it they are unfillable outlines and set_facecolor does nothing, silently.
The property dicts — medianprops, boxprops, whiskerprops, flierprops — do the same at call time and are usually tidier.
Violin internals
violinplot returns a dict too, with "bodies" and optional "cmedians", "cmeans", "cbars".
By default it draws the extrema and no median, which is an odd choice; showmedians=True is nearly always wanted.
widths controls the maximum thickness, and bw_method the smoothing — a number, or "scott" / "silverman". Halving the bandwidth roughly doubles the visible detail and the visible noise.
Because the two halves are mirror images, one half is redundant. A half violin paired with the raw points on the other side uses the space better, and is built by clipping the body path — more work than it sounds, and worth it for a figure that will be published.
Choosing between them
Box — many groups, when the quartiles are the summary you want and shape is not expected to be interesting.
Violin — when shape matters and the samples are large enough for a density estimate to mean something, roughly a hundred per group.
Strip or swarm — small samples, where the individual values are the honest display.
Box plus points — the general-purpose answer under about fifty per group.
Histogram grid — when the shapes are the subject and there are few enough groups to give each a panel.
The mistake is treating the box plot as the default for all distribution comparison, which is how bimodality goes unnoticed.
Outliers
The points beyond the whiskers are labelled outliers, and the label is doing more work than the statistics support.
They are simply points more than 1.5×IQR from the box. On normally distributed data about 0.7% of observations qualify, so a sample of a thousand produces seven "outliers" that are nothing of the kind.
On skewed data the rule flags far more, all on one side, which is a property of the distribution rather than of the points.
So: a point beyond a whisker is worth looking at and is not evidence of an error. showfliers=False hides them when they are distracting, and whis=(5, 95) changes the rule to percentiles, which is often more interpretable.
Sample size
A box plot looks the same for twelve observations and twelve thousand, which is a real weakness when groups differ in size.
Three ways to show it:
Width proportional to n — widths= accepts a list, and scaling by the square root of the count is conventional.
The count in the tick label — "group a (n=12)".
The points overlaid, which shows the sample size directly.
Without one of these, a group of eight and a group of eight hundred are presented as equally reliable, and the reader has no way to know.
Ordering and grouping
With more than a handful of groups, the arrangement is most of the chart.
Sort by median unless the categories have a natural order. It turns a search into a reading.
Group related categories together and separate the groups with space, which positions= allows: passing [1, 2, 3, 5, 6, 7] leaves a gap between two clusters.
Use colour for the grouping, not for the individual boxes, so the colour carries the structure rather than repeating the labels.
Consider a reference line — the overall median, a target — drawn across the whole axes, which turns each box from an isolated summary into a comparison.
With those, twenty boxes on one chart remains readable. Without them, six is already hard.
In summary
A box is five numbers, and whiskers reach the furthest point within 1.5×IQR rather than a fixed multiple.
The display exists to compare many groups at once, which is what it does better than anything else.
It hides shape, and specifically hides bimodality — two populations mixed together produce a box that looks unremarkable.
A violin restores the shape and introduces a bandwidth you did not choose, plus tails extending past the observed data.
Under about fifty points per group, showing the points with jitter is more informative than either.
And patch_artist=True, showfliers=False and vert=False are the three arguments that come up most: fillable boxes, no double-drawn outliers, and horizontal layout for wordy labels.
Beeswarm and the alternatives
A strip plot with random jitter shows every point and lets some of them overlap by chance.
A beeswarm places the points deterministically so none overlap, which reads better and is not built into matplotlib — seaborn's swarmplot is the usual route, and it refuses to plot when there are too many points to place, which is a reasonable failure.
A rain cloud combines three: a half violin for the shape, a box for the summary, and the points below. It is the most complete display of a distribution comparison and takes the most space, which is the trade.
For most work the ordering is: points if the sample is small, box if there are many groups, box plus points in between, and a violin when the shape is genuinely the question and the samples are large enough to estimate it.
What matters more than the choice is knowing what each one hides, which is the theme of this whole module.
A closing note
Every distribution display is a compromise between completeness and comparability.
The points show everything and compare badly past a few groups. The box compares many groups and hides the shape. The violin restores the shape and adds a smoothing choice. The histogram shows one distribution well and several badly.
There is no display that does all of it, which is why the useful skill is knowing what each one discards rather than having a favourite.
The specific thing worth remembering: a box plot cannot show bimodality, and bimodality usually means two populations have been mixed — which is frequently the most important fact about the data. Looking at a histogram of each group once, before settling on boxes for the comparison, costs a minute and prevents the failure this module is mostly about.
The short version
Both displays trade completeness for comparability, and both hide something specific: the box hides shape, the violin hides its own smoothing choice.
Knowing what each discards is more useful than preferring one, and looking at a histogram of each group once before choosing is what prevents the bimodality failure this module exists to describe.
Reading the code back
A box plot is one call with three arguments worth passing: patch_artist to make the boxes fillable, showfliers to control the outliers, and vert to choose the orientation. The decisions before it are which groups, in what order, and whether the points should be shown as well - and those are what determine whether the chart works.
Check yourself
0 of 4
Answer without scrolling back up.
What do the whiskers on a matplotlib box plot reach?
They stop at real observations, and points beyond are drawn individually. The 1.5 is a convention, changeable with whis=.
What can a box plot completely hide?
Bimodality usually means two populations have been mixed, which is often the most important thing in the data.
What is the cost of a violin plot?
A violin of a strictly positive quantity will show density below zero - a claim the data does not support.
Why pass `showfliers=False` when overlaying the raw points?
Below about fifty points per group, box plus points is strictly more informative than either alone.
Cheat sheet
Box and Violin Plots
The whiskers reach the furthest data point within 1.5×IQR of the box. They are not a fixed multiple of anything; they stop at real observations.
Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.