Categorical plots: box, violin, bar, strip

seaborn · Essentials · 12 min

Comparing groups, such as A/B variants, regions or models, is one of the most common analysis tasks.

seaborn groups categorical plots into three families:

  • Show every observation: stripplot (jittered points) and swarmplot (non-overlapping points, good up to a few hundred per group).
  • Show the distribution: boxplot (median, quartiles, whiskers at 1.5 IQR, outliers as points), violinplot (mirrored KDE) and boxenplot (more quantiles, for large data).
  • Show an estimate: barplot (mean with a 95% confidence interval by default), pointplot (the same as points and lines, good for interactions) and countplot (number of rows per category).

Put the category on x for vertical plots or on y for horizontal ones; seaborn infers the orientation. Control the order with order=[...], and split each category by a second variable with hue=, which dodges the groups side by side.

Layering is common: a box plot with a strip plot on top shows both summary and raw data. Pass the same ax to both.

A bar of the mean hides spread and sample size; prefer box, violin or points whenever the distribution matters. And barplot computes the mean itself: do not pass pre-aggregated values unless you also set errorbar=None, because a confidence interval from one value per group is meaningless.

In seaborn 0.13, passing palette= without hue= is deprecated; write hue="day", legend=False to color by the category.

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(2)
df = pd.DataFrame({
    "variant": np.repeat(["A", "B", "C"], 60),
    "time_s": np.r_[rng.normal(30, 5, 60), rng.normal(26, 4, 60), rng.normal(31, 9, 60)],
})

fig, (a, b) = plt.subplots(1, 2, figsize=(9, 3.5), layout="constrained")
sns.boxplot(data=df, x="variant", y="time_s", hue="variant", legend=False,
            fill=False, ax=a)
sns.stripplot(data=df, x="variant", y="time_s", color="0.3", alpha=0.5,
              size=3, ax=a)
sns.barplot(data=df, x="variant", y="time_s", order=["B", "A", "C"], ax=b)

print(df.groupby("variant")["time_s"].mean().round(1).to_dict())
# {'A': 30.4, 'B': 25.9, 'C': 30.2}
print([t.get_text() for t in b.get_xticklabels()])  # ['B', 'A', 'C']
fig.savefig("variants.png")

Remember

  • Strip/swarm show raw points, box/violin show distributions, bar/point show estimates
  • Use order= and hue= to control and split categories
  • Layer a strip plot over a box plot to show summary and data together

Watch out for

  • Passing already-averaged values to barplot, which then draws meaningless or missing error bars.

Check yourself

  1. What does a default `sns.barplot` bar height represent?

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

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