How to draw paired observations with a point plot in Seaborn (with Code)
The Short Answer (TL;DR)
Map each subject identifier to hue in sns.pointplot and set errorbar=None to connect that subject's observed values across conditions. Keep one row per subject and condition, because repeated rows within a pair are otherwise aggregated by the estimator.
import pandas as pd, seaborn as sns
data = pd.DataFrame({"subject": list("AABBCCDD"), "condition": ["Before", "After"] * 4, "score": [5, 4, 6, 5, 7, 8, 8, 6]})
sns.pointplot(data=data, x="condition", y="score", hue="subject", order=["Before", "After"], hue_order=list("ABCD"), estimator="mean", errorbar=None, dodge=False, markers="o", linestyles="-", palette="colorblind", legend=False)
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({
"subject": list("AABBCCDD"),
"condition": ["Before", "After"] * 4,
"score": [5, 4, 6, 5, 7, 8, 8, 6],
})
if data.duplicated(["subject", "condition"]).any():
raise ValueError("Expected one observation per subject and condition.")
paired = data.pivot(index="subject", columns="condition", values="score")
if paired[["Before", "After"]].isna().any().any():
raise ValueError("Every subject must have both observations.")
differences = paired["After"] - paired["Before"]
fig, ax = plt.subplots(figsize=(5.5, 3.8), layout="constrained")
sns.pointplot(data=data, x="condition", y="score", hue="subject",
order=["Before", "After"], hue_order=list("ABCD"),
estimator="mean", errorbar=None, dodge=False,
markers="o", linestyles="-", palette="colorblind", ax=ax)
ax.set(xlabel="Condition", ylabel="Synthetic score",
title="Four complete pairs; each line follows one subject")
ax.legend(title="Subject", bbox_to_anchor=(1.02, 1), loc="upper left")
fig.savefig("paired-subject-pointplot.png", dpi=160)
plt.show()
Four subject lines join the Before and After conditions. Three subjects decrease and one increases; their mean observed change is -0.75 score units.
Why put subject identifiers in hue?
Each subject becomes a separate series, so a line joins that subject's before and after values. A pointplot without this mapping normally joins condition-level estimates and does not show the individual pairings.
Does the units parameter draw subject trajectories?
The units parameter controls multilevel bootstrap resampling when estimating uncertainty. It does not by itself create a separate line for each subject. This example uses hue for the series and disables error bars; each mean is a single observed value.
How should incomplete or repeated pairs be handled?
Check identifiers and completeness before plotting. The example rejects duplicate subject-condition rows and incomplete pairs. If repeated measurements should be averaged, define that aggregation explicitly; if missing pairs are retained, state which comparisons remain available.
Mathematical Context
For subject i, define the observed change as d_i = y_after,i – y_before,i. The four synthetic changes are -1, -1, 1 and -2, so the mean paired change is -0.75. Connecting pairs preserves the subject association that would be lost by treating the before and after columns as unrelated samples. The lines describe these observations; a population confidence interval or causal effect requires additional assumptions and a suitable analysis.
\[ d_i=y_{\mathrm{after},i}-y_{\mathrm{before},i},\qquad \bar{d}=\frac{1}{n}\sum_i d_i=-0.75 \]
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
- Learn Python plotting with Matplotlib
- Create small-multiple scatterplots in Seaborn
- How to share a colorbar across subplots in Matplotlib (with Code)
- How to save a figure with a transparent background in Matplotlib (with Code)
- How to plot error bars from measured standard errors in Matplotlib (with Code)
- How to add a logarithmic axis in Matplotlib (with Code)
- How to format dates on a time-series axis in Matplotlib (with Code)
- How to plot a histogram with unequal bin widths in Matplotlib (with Code)