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

Lesson 06

Histograms with histplot

Show how one column is spread with sns.histplot: choose bins or binwidth, switch stat to percent or density, and compare groups with hue and multiple.

Intermediate25 min
Histograms with histplot hero Module 26 Lesson 6Histograms with histplot hero Module 26 Lesson 6

A histogram shows how one column is spread: where most values sit, how wide the range is, and whether it leans to one side. sns.histplot counts the rows in each bin for you, and adds options for comparing groups and switching what the bar height means.

Histogram of quiz scores in five-point bins from 40 to 85Histogram of quiz scores in five-point bins from 40 to 85

A first histplot

Name one column for x. Seaborn picks a bin count from the data and draws one bar per bin. Each bar is a Matplotlib rectangle, so counting ax.patches tells you how many bins it chose.

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()

ax = sns.histplot(data=study, x="score")
plt.show()

print(len(ax.patches), "bins")
print(study["score"].min(), "to", study["score"].max())

Output:

text
8 bins
40.0 to 85.0

bins or binwidth

bins= sets how many bins. binwidth= sets how wide each one is, which gives rounder edges. binrange=(40, 85) fixes where they start and stop. Too few bins hide the shape, too many turn it into noise.

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, 3, figsize=(12, 3.5), sharey=True)
sns.histplot(data=study, x="score", bins=4, ax=axes[0])
sns.histplot(data=study, x="score", binwidth=5, binrange=(40, 85), ax=axes[1])
sns.histplot(data=study, x="score", bins=40, ax=axes[2])
for ax, name in zip(axes, ["bins=4", "binwidth=5", "bins=40"]):
    ax.set_title(name)
plt.show()

print(np.histogram(study["score"], bins=range(40, 90, 5))[0])

Output:

text
[ 2  9 11 19 16 24 19 10 10]

What the height means: stat

By default a bar's height is a count. stat="percent" makes all bars add up to 100, stat="probability" to 1, and stat="density" makes the total area 1, which lets groups of different sizes share one axis.

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()

ax = sns.histplot(data=study, x="score", binwidth=5, stat="percent")
plt.show()

print(round(sum(bar.get_height() for bar in ax.patches), 1))

Output:

text
100.0

Compare groups with hue and multiple

hue draws one histogram per group. multiple decides how they share the space: "layer" overlaps them, "stack" piles them, "dodge" puts bars side by side, and "fill" stretches each bin to 100% so you see the mix.

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(2, 2, figsize=(10, 7))
for ax, how in zip(axes.flat, ["layer", "stack", "dodge", "fill"]):
    sns.histplot(data=study, x="score", hue="mode", binwidth=5, multiple=how, ax=ax)
    ax.set_title(f"multiple={how!r}")
plt.tight_layout()
plt.show()

print(study["mode"].value_counts())

Output:

text
mode
online       66
classroom    54
Name: count, dtype: int64

Add a smooth curve

kde=True draws a smooth estimate of the shape over the bars, and element="step" draws the outline only, which keeps overlapping groups readable.

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.histplot(data=study, x="score", hue="track", element="step", kde=True, binwidth=5)
plt.show()

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

Output:

text
track
Data      68.0
Python    65.0
Web       63.0
Name: score, dtype: float64

Key takeaways

  • histplot counts rows per bin for one column

  • bins= sets how many; binwidth= and binrange= set the edges

  • stat="percent" or "density" changes what the height means

  • hue with multiple="layer", "stack", "dodge", or "fill" compares groups

  • Next: KDE and ECDF Plots

  • Previous: relplot and Small Multiples

Loading playground…

Check your understanding

Histograms with histplot Quiz

What does a histogram show?

1 / 10