Distributions: histplot, kdeplot, ecdfplot

seaborn · Essentials · 12 min

Understanding a variable's distribution comes before any modeling, and these functions compare groups in one line.

histplot bins values and counts them. Key options:

  • bins= or binwidth=; the default picks a sensible bin count.
  • stat=: "count" (default), "density", "probability", "percent" or "frequency".
  • hue= splits by group, and multiple= decides how groups combine: "layer" (overlapping, default), "stack", "dodge" or "fill" (proportions).
  • common_norm=False normalizes each group separately, essential when groups have very different sizes.
  • kde=True overlays a smooth density curve.

kdeplot draws a smooth estimate of the density. bw_adjust scales the smoothing: below 1 shows more detail (and noise), above 1 smooths more. A KDE can suggest values that are impossible, such as negative prices; cut=0 stops the curve at the data range, and clip= bounds it.

ecdfplot shows, for each value, the proportion of data at or below it. It needs no bins or bandwidth, so it hides nothing, and comparing groups by reading off medians or percentiles is easy.

With both x and y, histplot and kdeplot show bivariate distributions as heatmaps or contours.

displot(kind="hist" | "kde" | "ecdf") is the figure-level version that adds faceting with col= and row=. For one variable in many groups, the categorical plots (boxplot, violinplot) are often more compact.

Example

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np

rng = np.random.default_rng(1)
df = pd.DataFrame({
    "latency": np.r_[rng.lognormal(3, 0.4, 900), rng.lognormal(3.6, 0.3, 100)],
    "region": ["eu"] * 900 + ["us"] * 100,
})

fig, (a, b, c) = plt.subplots(1, 3, figsize=(12, 3.5), layout="constrained")
sns.histplot(data=df, x="latency", hue="region", stat="density",
             common_norm=False, ax=a)
sns.kdeplot(data=df, x="latency", hue="region", common_norm=False,
            bw_adjust=0.7, cut=0, ax=b)
sns.ecdfplot(data=df, x="latency", hue="region", ax=c)

print(round(df.groupby("region")["latency"].median(), 1).to_dict())
# {'eu': 19.9, 'us': 35.8}
fig.savefig("latency.png")

Remember

  • histplot for counts, kdeplot for smooth shape, ecdfplot for exact percentiles
  • Use common_norm=False when comparing groups of different sizes
  • Bin count and bw_adjust change the story; try a few values

Watch out for

  • Reading a KDE's tails as real data, for example density below zero for a strictly positive variable.

Check yourself

  1. Group A has 10,000 rows and group B has 200. With `sns.histplot(..., hue='group', stat='density')` group B's bars look tiny. Why?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: seaborn.pydata.org/tutorial/distributions.html