Aggregation and error bars

seaborn · Practical · 12 min

An error bar is a claim about your data; you must know whether it shows spread or uncertainty and say which.

When several rows share the same x (and hue), lineplot, barplot and pointplot aggregate them with an estimator (default "mean") and draw an interval given by errorbar:

  • ("ci", 95): a bootstrap confidence interval of the estimate (the default). It shrinks as you add data.
  • "se": the standard error, about a 68% interval for the mean.
  • "sd": one standard deviation, showing the spread of the data, which does not shrink with more data.
  • ("pi", 50): a percentile interval covering the middle 50% of observations.
  • None: no interval, which also skips the bootstrap and is much faster on big data.

The first two describe uncertainty about the estimate, the last two describe the data's spread. Choose based on the question and state it in the caption.

Other knobs:

  • estimator="median" or any function, such as np.max.
  • n_boot= and seed= control the bootstrap; set seed for reproducible figures.
  • units="subject" with estimator=None in lineplot draws one line per unit instead of aggregating, good for repeated measures.
  • lineplot(err_style="bars") draws error bars instead of a band.

If your data is already one row per x value, there is nothing to aggregate and no band is drawn; the interval must come from your own columns, plotted with Matplotlib's fill_between or errorbar.

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(4)
df = pd.DataFrame({
    "week": np.tile(np.arange(1, 9), 50),        # 50 users x 8 weeks
    "user": np.repeat(np.arange(50), 8),
})
df["minutes"] = 20 + 3 * df["week"] + rng.normal(0, 10, len(df))

fig, axs = plt.subplots(1, 3, figsize=(12, 3.5), sharey=True, layout="constrained")
for ax, eb in zip(axs, [("ci", 95), "sd", ("pi", 50)]):
    sns.lineplot(data=df, x="week", y="minutes", errorbar=eb, seed=0, ax=ax)
    ax.set_title(f"errorbar={eb}")

wk1 = df.loc[df.week == 1, "minutes"]
print(round(wk1.std(), 1), round(wk1.std() / np.sqrt(len(wk1)), 1))
# 11.2 1.6: sd describes spread, se/ci the precision of the mean
fig.savefig("errorbars.png")

Remember

  • Repeated x values are aggregated with estimator (default mean)
  • ci/se show uncertainty of the estimate; sd/pi show spread of the data
  • Set seed= for reproducible bootstrap intervals; errorbar=None for speed

Watch out for

  • Presenting a 95% CI band as if it showed the range of individual values.

Check yourself

  1. You add 10 times more users to the data. Which band gets substantially narrower?

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

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