Skip to content

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

Related technical references