Skip to main content
MahiDev — Learn. Build. Become AI-ready.

Lesson 09

Box Plots

Summarise a distribution in five numbers with sns.boxplot: read the box, median, whiskers, and outlier dots, and compare several groups side by side.

Intermediate25 min
Box Plots hero Module 26 Lesson 9Box Plots hero Module 26 Lesson 9

A box plot squeezes a distribution into a few marks. The box runs from the first quartile to the third, so it holds the middle half of the rows. The line inside is the median. The whiskers reach the furthest values within 1.5 box lengths, and anything beyond them is drawn as a separate dot.

Three box plots of score by track with medians, whiskers, and outlier circlesThree box plots of score by track with medians, whiskers, and outlier circles

The five numbers

Draw one box for the whole column, then print the quartiles. The box edges are the 25% and 75% values, the line is the 50% value, and the distance between the edges is the interquartile range, or IQR.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

sns.boxplot(data=study, y="score")
plt.show()

q1, median, q3 = study["score"].quantile([0.25, 0.5, 0.75])
print(q1, median, q3)
print("IQR:", q3 - q1)

Output:

text
57.0 65.0 72.0
IQR: 15.0

Groups side by side

Put a category on x and a number on y to get one box per group. Compare the medians first, then the box heights: a taller box means the middle half of that group is more spread out.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

sns.boxplot(data=study, x="track", y="score", order=["Python", "Data", "Web"])
plt.show()

summary = study.groupby("track")["score"].quantile([0.25, 0.5, 0.75]).unstack()
print(summary)

Output:

text
        0.25  0.50   0.75
track                    
Data    63.0  68.0  72.75
Python  57.0  65.0  72.00
Web     54.5  63.0  70.50

Whiskers and outliers

Whiskers stop at the last value within 1.5 times the IQR of the box. Values beyond that are drawn as dots. Working out the fences yourself shows exactly which rows became dots.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

data = study[study["track"] == "Data"]["score"]
q1, q3 = data.quantile([0.25, 0.75])
low, high = q1 - 1.5 * (q3 - q1), q3 + 1.5 * (q3 - q1)
print("fences:", low, high)
print("outliers:", sorted(data[(data < low) | (data > high)]))

sns.boxplot(x=data)
plt.show()

Output:

text
fences: 48.375 87.375
outliers: [40.0]

A dot is not automatically a mistake. It is a value worth checking, which may be a typo or may be the most interesting learner in the table.

Split by a second group

hue puts boxes for each mode next to each other inside every track. whis changes the whisker reach, and showfliers=False hides the outlier dots when you plan to draw every point yourself.

python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

rng = np.random.default_rng(26)
study = pd.DataFrame({
    "track": rng.choice(["Python", "Data", "Web"], 120),
    "mode": rng.choice(["online", "classroom"], 120),
    "week": rng.integers(1, 7, 120),
})
study["hours"] = (rng.uniform(1, 6, 120) + study["week"] * 0.8).round(1)
study["score"] = (35 + study["hours"] * 4.5 + rng.normal(0, 7, 120)).clip(0, 100).round()

fig, axes = plt.subplots(1, 2, figsize=(11, 4), sharey=True)
sns.boxplot(data=study, x="track", y="score", hue="mode", ax=axes[0])
sns.boxplot(data=study, x="track", y="score", whis=3, showfliers=False, ax=axes[1])
axes[1].set_title("whis=3, no outlier dots")
plt.show()

print(study.groupby(["track", "mode"])["score"].median().unstack())

Output:

text
mode    classroom  online
track                    
Data         68.0    68.0
Python       63.0    66.5
Web          66.5    63.0

Key takeaways

  • The box spans the first to third quartile; the line is the median

  • The IQR is the box height: the spread of the middle half

  • Whiskers reach 1.5 IQR; values beyond are drawn as dots

  • hue splits boxes by a second group; whis and showfliers adjust the rest

  • Next: Violin Plots

  • Previous: Count Plots and Bar Plots

Loading playground…

Check your understanding

Box Plots Quiz

What does the box span?

1 / 10