Paired T-Test
The paired t-test (dependent samples t-test) tests whether the mean difference between paired observations is zero.
where d = x₁ - x₂ for each pair.
When to Use Paired T-Test
| Design | Example |
|---|---|
| Before-After (same subjects) | Blood pressure before and after treatment |
| Matched pairs | Twins assigned to different diets |
| Cross-over trials | Same subject receives both treatments (in sequence) |
| Repeated measurements | Same patient measured at two time points |
Key: Each observation in group 1 has a natural partner in group 2.
Why Pairing Increases Power
By analyzing differences, we remove between-subject variability (e.g., people who are generally heavier vs. lighter). Only within-subject changes remain.
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
np.random.seed(42)
n = 25
# Simulate blood pressure data (paired design)
# Between-subject effect: people have different baseline BPs
individual_baselines = np.random.normal(130, 15, n) # true individual means
true_treatment_effect = -8 # drug reduces by 8 mmHg
before = individual_baselines + np.random.normal(0, 5, n)
after = individual_baselines + true_treatment_effect + np.random.normal(0, 5, n)
differences = before - after
print("=== PAIRED T-TEST ===")
print(f"n = {n} patients measured before and after treatment")
print(f"\nBefore: mean={before.mean():.2f}, SD={before.std(ddof=1):.2f}")
print(f"After: mean={after.mean():.2f}, SD={after.std(ddof=1):.2f}")
print(f"Diff (before−after): mean={differences.mean():.2f}, SD={differences.std(ddof=1):.2f}")
# Paired t-test: t-test on differences
t_paired, p_paired = stats.ttest_rel(before, after)
print(f"\nPaired t({n-1}) = {t_paired:.4f}, p = {p_paired:.6f}")
# Compare with (incorrect) independent t-test
t_indep, p_indep = stats.ttest_ind(before, after)
print(f"Independent t = {t_indep:.4f}, p = {p_indep:.4f} (would miss the effect!)")
print(f"\nThe paired test is {p_indep/p_paired:.0f}× more powerful here")
print("because it removes between-person variability")
Visualization
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 1. Before-after connected plot
for i in range(min(25, n)):
color = 'green' if before[i] > after[i] else 'red'
axes[0].plot([0, 1], [before[i], after[i]], color=color, alpha=0.4, linewidth=1)
axes[0].plot([0, 1], [before.mean(), after.mean()], 'k-', linewidth=3, label='Mean')
axes[0].scatter([0]*n, before, s=20, color='steelblue', alpha=0.5)
axes[0].scatter([1]*n, after, s=20, color='coral', alpha=0.5)
axes[0].set_xticks([0, 1])
axes[0].set_xticklabels(['Before', 'After'])
axes[0].set_title('Paired Observations')
axes[0].set_ylabel('Blood Pressure (mmHg)')
# 2. Histogram of differences
axes[1].hist(differences, bins=12, edgecolor='black', color='steelblue', alpha=0.7)
axes[1].axvline(0, color='red', linewidth=2, linestyle='--', label='H₀: Δ=0')
axes[1].axvline(differences.mean(), color='green', linewidth=2, linestyle='-',
label=f'Δ̄={differences.mean():.2f}')
axes[1].set_title('Distribution of Differences')
axes[1].set_xlabel('Before − After')
axes[1].legend()
# 3. Q-Q plot of differences (check normality assumption)
stats.probplot(differences, dist='norm', plot=axes[2])
axes[2].set_title('Q-Q Plot of Differences\n(Should be approximately linear)')
plt.tight_layout()
plt.savefig('paired_t_test.png', dpi=150)
plt.show()
# Confidence interval for the mean difference
ci = stats.t.interval(0.95, df=n-1, loc=differences.mean(),
scale=stats.sem(differences))
print(f"\n95% CI for mean difference: ({ci[0]:.2f}, {ci[1]:.2f}) mmHg")
print(f"Interpretation: We are 95% confident the drug reduces BP by")
print(f"{ci[0]:.1f} to {ci[1]:.1f} mmHg on average")
Effect Size for Paired T-Test
# Cohen's d for paired design
d_paired = differences.mean() / differences.std(ddof=1)
print(f"Cohen's d (paired) = {d_paired:.4f}")
print(f"Effect size: {'small' if abs(d_paired)<0.5 else 'medium' if abs(d_paired)<0.8 else 'large'}")
# Compared to independent Cohen's d (would be smaller)
sp = np.sqrt(((n-1)*before.std()**2 + (n-1)*after.std()**2) / (2*n-2))
d_indep = (before.mean() - after.mean()) / sp
print(f"Cohen's d (independent, for comparison) = {d_indep:.4f}")
Key Takeaways
- Paired t-test = one-sample t-test on the differences
- Use it when observations are naturally linked (before-after, matched, cross-over)
- More powerful than independent t-test when pairing is appropriate, because between-subject noise is removed
- Check normality of differences (not of the original groups)
- scipy.stats.ttest_rel(a, b) performs the paired t-test
- Report: mean difference, SD of differences, t, df, p, 95% CI for difference, Cohen's d