How to compare distributions with an empirical cumulative distribution plot in Seaborn (with Code)
The Short Answer (TL;DR)
Use sns.ecdfplot with a numeric x column and hue for the groups you want to compare. With stat="proportion", each group’s curve shows the fraction of its observations at or below a value.
import pandas as pd, seaborn as sns
data = pd.DataFrame({"duration": [1, 2, 2, 4, 2, 3, 5, 5], "group": ["A"] * 4 + ["B"] * 4})
sns.ecdfplot(data=data, x="duration", hue="group", hue_order=["A", "B"], stat="proportion")
Detailed Implementation
This example uses synthetic data and does not need a network connection. Install the recorded package versions in a separate Python environment before running it.
python -m pip install numpy==2.5.4 pandas==3.0.6 matplotlib==3.11.2 seaborn==0.13.2
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
data = pd.DataFrame({
"duration": [1.0, 2.0, 2.0, 4.0, 2.0, 3.0, 5.0, 5.0],
"group": ["A"] * 4 + ["B"] * 4,
})
fig, ax = plt.subplots(figsize=(5.8, 3.6), layout="constrained")
sns.ecdfplot(data=data, x="duration", hue="group", hue_order=["A", "B"],
stat="proportion", palette={"A": "#176b87", "B": "#b35324"}, ax=ax)
ax.axvline(2, color="gray", linestyle="--", linewidth=1)
ax.set(xlabel="Synthetic duration (minutes)", ylabel="Cumulative proportion",
title="Empirical distributions of two groups", ylim=(0, 1.05))
ax.grid(axis="y", alpha=0.25)
fig.savefig("group-ecdf.png", dpi=160)
plt.show()
Both curves rise to one. At two minutes the blue curve reaches 0.75 and the orange curve reaches 0.25; tied values create larger jumps.
How is a curve read at a threshold?
At two minutes, group A has cumulative proportion 0.75 and group B has 0.25. These are within-group proportions, even when groups have different sample sizes.
What do repeated values do?
Ties create larger steps. Two of group A’s four observations equal two, so its curve rises by 0.5 at that value. No histogram bin width or density smoothing bandwidth is selected.
Does separation establish a statistically significant difference?
The curves describe the supplied samples. Population inference requires a suitable sampling model and statistical procedure. Check units, missingness and censoring; an ordinary plot of observed times is not a censoring-adjusted survival analysis.
Mathematical Context
For n unweighted observations, the empirical CDF is F_n(x) = (1/n) Σ I(X_i ≤ x). Each observed value contributes mass 1/n; ties contribute multiple such masses at the same position. Group A has three of four observations at or below two, giving F_A(2)=3/4. Group B has one of four, giving F_B(2)=1/4.
\[ F_n(x)=\frac{1}{n}\sum_{i=1}^{n}\mathbf{1}\{X_i\leq x\} \]
Reproducibility
Executed with Python 3.12.14; numpy 2.5.4, pandas 3.0.6, matplotlib 3.11.2, seaborn 0.13.2. Automated checks confirm the stated output properties.
Primary documentation
Related guides
- Choose Seaborn color palettes
- Learn Python plotting with Matplotlib
- How to share a colorbar across subplots in Matplotlib (with Code)
- How to save a figure with a transparent background in Matplotlib (with Code)
Related technical references
- How to draw a diverging image with a zero-centered normalization in Matplotlib (with Code)
- How to plot a histogram with unequal bin widths in Matplotlib (with Code)
- How to format dates on a time-series axis in Matplotlib (with Code)
- How to add a logarithmic axis in Matplotlib (with Code)
- How to plot error bars from measured standard errors in Matplotlib (with Code)