Skillversitas

Box Plots

A box plot compresses a distribution into five numbers - the quartiles, the median, and the outlier range - so several groups can be compared side by side at a glance. It is the standard chart for "does this variable differ across categories?" (salary by department, latency by region, test scores by cohort) and it makes outliers visible as individual points instead of burying them in an axis range.

Basic Box Plot

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(5)
groups = {
    "control": rng.normal(50, 8, 300),
    "treatment": rng.normal(55, 9, 300),
    "boosted": rng.normal(60, 12, 300),
}

fig, ax = plt.subplots()
ax.boxplot(groups.values(), tick_labels=list(groups.keys()))
ax.set_ylabel("score")
plt.show()

Pass a list of arrays and boxplot draws one box per array; tick_labels names the groups (in matplotlib 3.9 the older labels argument was renamed). The box spans Q1 to Q3, the line inside is the median, the whiskers extend to the farthest point within 1.5 IQR of the box, and anything beyond is plotted as a flier.

Horizontal and Styling

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(6)
data = [rng.normal(mu, 5, 250) for mu in (40, 50, 60, 70)]

fig, ax = plt.subplots()
bp = ax.boxplot(data, vert=False,
                patch_artist=True,
                medianprops=dict(color="red", linewidth=2),
                flierprops=dict(markerfacecolor="gray", alpha=0.5))
for patch in bp["boxes"]:
    patch.set_facecolor("lightblue")
ax.set_xticks(range(1, 5), ["g1", "g2", "g3", "g4"])
plt.show()

vert=False flips the chart for long labels; patch_artist=True turns boxes into fillable patches; medianprops and flierprops style individual parts. The return value is a dict of artists ("boxes", "medians", "fliers", ...) - the same artist-handle pattern as bars and lines.

Box Plot plus Jittered Points

Boxes hide the actual data; a light scatter overlay restores it - the standard "distribution plus density" view for sample sizes under a few thousand:

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(7)
g1, g2 = rng.normal(50, 8, 400), rng.normal(56, 8, 400)

fig, ax = plt.subplots()
ax.boxplot([g1, g2], tick_labels=["A", "B"], width=0.5)
xjitter1 = rng.uniform(-0.15, 0.15, len(g1))
xjitter2 = rng.uniform(-0.15, 0.15, len(g2))
ax.scatter(np.ones(len(g1)) + xjitter1, g1, s=8, alpha=0.3, color="tab:blue")
ax.scatter(np.ones(len(g2)) + 1 + xjitter2, g2, s=8, alpha=0.3, color="tab:orange")
plt.show()

The jitter keeps points readable without changing their vertical (value) positions. For larger samples, swap the points for a strip or violin outline instead.

Reading It Correctly

  • The median line compares central tendency; box height compares spread (IQR), not variance.
  • Fliers are points beyond 1.5 IQR - a rule of thumb for "unusual," not a proof of error. Do not strip them without a reason.
  • A shifted box with the same height is a location difference; same position with different heights is a variance difference - both are findings.

Summary

Use boxplot with one array per group to compare distributions across categories: quartiles, median, and fliers in one mark. Style the parts through the returned artist dict, overlay jittered points for small samples, and read the box as location-plus-spread - the five-number summary that survives a wall of categories.