Knowing the statistics behind the defaults lets you defend a figure, reproduce it exactly and avoid slow plots on large data.
Confidence intervals in barplot, pointplot, lineplot, regplot and so.Est come from a nonparametric bootstrap: resample the rows of each group with replacement n_boot times (default 1000), recompute the estimator each time, and take the percentiles of those estimates (2.5th and 97.5th for 95%). Consequences:
- Results vary slightly from run to run unless you pass
seed=. - Cost grows with rows times
n_boot; on millions of rows useerrorbar="se"(analytic for the mean) orNone. - The bootstrap assumes independent rows. With repeated measures, pass
units="subject"so whole subjects are resampled instead of individual rows, which gives honestly wider intervals. weights=(added in 0.13 for some functions) changes the estimator to a weighted mean.
KDE bandwidth uses Scott's rule by default (via a Gaussian kernel), scaled by bw_adjust. It assumes roughly unimodal, unbounded data; multimodal or bounded data needs a smaller bw_adjust or cut=0.
Histogram bins default to NumPy's "auto" rule, which takes the larger number of bins from the Sturges and Freedman-Diaconis rules; discrete=True centers one bin on each integer, which is right for counts.
When in doubt, reproduce the number yourself, as the example does, and check that the plot shows what you would report.
Example
import matplotlib
matplotlib.use("Agg")
import numpy as np
import pandas as pd
import seaborn as sns
rng = np.random.default_rng(9)
df = pd.DataFrame({"g": ["a"] * 200, "v": rng.exponential(10, 200)})
ax = sns.pointplot(data=df, x="g", y="v", errorbar=("ci", 95),
n_boot=2000, seed=42)
lo, hi = ax.lines[1].get_ydata() # lines[0] is the point, [1] its CI
print(round(lo, 2), round(hi, 2)) # 9.03 12.1
# Reproduce a percentile bootstrap by hand
boot_rng = np.random.default_rng(42)
v = df["v"].to_numpy()
means = [boot_rng.choice(v, v.size).mean() for _ in range(2000)]
print(np.percentile(means, [2.5, 97.5]).round(2)) # very close to seaborn's
print(round(v.mean(), 2), round(v.std(ddof=1) / np.sqrt(v.size), 2)) # mean, seRemember
- Default CIs are percentile bootstraps:
n_bootresamples, then percentiles - Use
seed=for reproducibility andunits=for repeated measures - KDE uses Scott's rule times
bw_adjust; bins default to NumPy's 'auto'
Watch out for
- Bootstrapping rows of repeated-measures data, which treats correlated rows as independent and gives too-narrow intervals.