Wilcoxon Signed-Rank Test
The nonparametric alternative to the paired t-test. Tests whether the median of differences equals zero, without assuming normality.
import numpy as np
from scipy import stats
np.random.seed(42)
before = np.array([85, 90, 78, 92, 88, 76, 95, 82, 87, 91, 73, 89])
after = np.array([78, 82, 75, 88, 85, 70, 89, 79, 80, 86, 68, 84])
differences = before - after
print(f"Median difference: {np.median(differences):.2f}")
# Wilcoxon signed-rank test
stat, p = stats.wilcoxon(before, after, alternative='two-sided')
print(f"W = {stat:.2f}, p = {p:.4f}")
# Compare with paired t-test
t, p_t = stats.ttest_rel(before, after)
print(f"Paired t-test: t={t:.4f}, p={p_t:.4f}")
# When to prefer Wilcoxon
print("\nUse Wilcoxon signed-rank when:")
print("• n is small and normality of differences can't be verified")
print("• Differences have heavy tails or outliers")
print("• Data is ordinal")
How It Works
- Compute differences dᵢ = x₁ᵢ − x₂ᵢ
- Remove zero differences
- Rank |dᵢ| (absolute values)
- Assign signs from original dᵢ
- W = sum of positive ranks (or negative ranks)
- Compare W to null distribution
# Manual computation
d = before - after
d_nonzero = d[d != 0]
n = len(d_nonzero)
ranks = stats.rankdata(np.abs(d_nonzero))
W_plus = ranks[d_nonzero > 0].sum()
W_minus = ranks[d_nonzero < 0].sum()
W = min(W_plus, W_minus)
print(f"W+ = {W_plus:.1f}, W- = {W_minus:.1f}, W = {W:.1f}")
print(f"n = {n}")
Key Takeaways
- Nonparametric equivalent of paired t-test — no normality assumption
- More powerful than sign test — uses magnitude of differences, not just direction
- Use when: small n, non-normal differences, ordinal data
- Exact test for small n; normal approximation for n > 25
- Effect size: matched-pairs rank biserial correlation r = W / (n(n+1)/2)