Relationships
Spearman Rank Correlation
Measure monotonic association by ranking each list then applying the
rank-difference formula: r = 1 − 6Σd²/(n(n²−1)), valid only when all
ranks are distinct (no ties). Assign 1-based ranks via sorted-order lookup,
compute d = rx[i] − ry[i] for each pair, sum d². With scipy,
spearmanr(x, y).statistic; p-value snapshot-only.
By hand
Values are distinct so no tie-handling needed. Two ranking loops: for each
value look up its position in sorted(x) (0-based) and add 1 for 1-based
rank. Then one loop: accumulate sum_sq_d via d=rx[i]−ry[i], d²=d*d. For
x=[2,4,1,5,3] and y=[1,3,2,5,4]: ranks_x=[2,4,1,5,3], ranks_y=[1,3,2,5,4],
d=[1,1,−1,0,−1], d²=[1,1,1,0,1], Σd²=4; r = 1−24/120 = 0.8.
x = [2, 4, 1, 5, 3]
y = [1, 3, 2, 5, 4]
n = len(x)
sx = sorted(x)
sy = sorted(y)
rx = []
for v in x:
rx.append(sx.index(v) + 1)
ry = []
for v in y:
ry.append(sy.index(v) + 1)
sum_sq_d = 0
for i in range(n):
d = rx[i] - ry[i]
sum_sq_d = sum_sq_d + d * d
r = 1 - 6 * sum_sq_d / (n * (n ** 2 - 1))
print('RESULT:', round(r, 4))
x ← [2, 4, 1, 5, 3]
1x = [2, 4, 1, 5, 3]2y = [1, 3, 2, 5, 4]values this step[2, 4, 1, 5, 3]xy ← [1, 3, 2, 5, 4]
1x = [2, 4, 1, 5, 3]2y = [1, 3, 2, 5, 4]3n = len(x)values this step[1, 3, 2, 5, 4]yn ← 5
2y = [1, 3, 2, 5, 4]3n = len(x)4sx = sorted(x)values this step5nsx ← [1, 2, 3, 4, 5]
3n = len(x)4sx = sorted(x)5sy = sorted(y)values this step[1, 2, 3, 4, 5]sxsy ← [1, 2, 3, 4, 5]
4sx = sorted(x)5sy = sorted(y)6rx = []values this step[1, 2, 3, 4, 5]syrx ← []
5sy = sorted(y)6rx = []7for v in x:values this step[]rxv ← 2, rx ← [2]
pass 1 of 56rx = []7for v in x:8 rx.append(sx.index(v) + 1)9ry = []values this step2v[] → [2]rxAll 5 passes — pass 1 is the card above pass vrx1 2 [] → [2] 2 2 → 4 [2] → [2, 4] 3 4 → 1 [2, 4] → [2, 4, 1] 4 1 → 5 [2, 4, 1] → [2, 4, 1, 5] 5 5 → 3 [2, 4, 1, 5] → [2, 4, 1, 5, 3] for v in x:
6rx = []7for v in x:8 rx.append(sx.index(v) + 1)ry ← []
8 rx.append(sx.index(v) + 1)9ry = []10for v in y:values this step[]ryv ← 1, ry ← [1]
pass 1 of 59ry = []10for v in y:11 ry.append(sy.index(v) + 1)12sum_sq_d = 0values this step3 → 1v[] → [1]ryAll 5 passes — pass 1 is the card above pass vry1 3 → 1 [] → [1] 2 1 → 3 [1] → [1, 3] 3 3 → 2 [1, 3] → [1, 3, 2] 4 2 → 5 [1, 3, 2] → [1, 3, 2, 5] 5 5 → 4 [1, 3, 2, 5] → [1, 3, 2, 5, 4] for v in y:
9ry = []10for v in y:11 ry.append(sy.index(v) + 1)sum_sq_d ← 0
11 ry.append(sy.index(v) + 1)12sum_sq_d = 013for i in range(n):values this step0sum_sq_di ← 0, d ← 1, sum_sq_d ← 1
pass 1 of 512sum_sq_d = 013for i in range(n):14 d = rx[i] - ry[i]15 sum_sq_d = sum_sq_d + d * d16r = 1 - 6 * sum_sq_d / (n * (n ** 2 - 1))values this step0i1d0 → 1sum_sq_dAll 5 passes — pass 1 is the card above pass idsum_sq_d1 0 1 0 → 1 2 0 → 1 — 1 → 2 3 1 → 2 1 → -1 2 → 3 4 2 → 3 -1 → 0 — 5 3 → 4 0 → -1 3 → 4 for i in range(n):
12sum_sq_d = 013for i in range(n):14 d = rx[i] - ry[i]r ← 0.8
15 sum_sq_d = sum_sq_d + d * d16r = 1 - 6 * sum_sq_d / (n * (n ** 2 - 1))17print('RESULT:', round(r, 4))values this step0.8rstdout ← RESULT: 0.8
16r = 1 - 6 * sum_sq_d / (n * (n ** 2 - 1))17print('RESULT:', round(r, 4))values this stepRESULT: 0.8stdout
With the library
scipy.stats.spearmanr(x, y) computes Spearman r internally (rank then
Pearson). .statistic is the r value; .pvalue is the two-tailed p-value
for H₀: r=0, shown in snapshot but not in the RESULT comparison.
from scipy.stats import spearmanr
from dalib.display import set_display
set_display()
x = [2, 4, 1, 5, 3]
y = [1, 3, 2, 5, 4]
result = spearmanr(x, y)
r = result.statistic
p = result.pvalue
print('r:', round(r, 4))
print('p-value:', round(p, 4))
print('RESULT:', round(r, 4))
r: 0.8
p-value: 0.1041
RESULT: 0.8
Honesty
This lesson shows the computation exactly, on a tiny pinned sample. The arithmetic is correct and reproducible, but with a sample this small the result is not a valid statistical finding — it demonstrates the mechanism, not evidence. Real inference needs an adequate sample size and assumption checks (e.g. independence and continuity of the underlying distribution); the p-value / interval here should be read as "how the formula is computed," not as a conclusion about a population.
Implementation notes
- The formula 1−6Σd²/(n(n²−1)) is algebraically equivalent to Pearson r on the ranks, but only when all ranks are distinct. With ties you must use averaged ranks and Pearson r on those averages — the simple formula no longer holds.
- Spearman r captures monotonic relationships (x rising as y rises, even non-linearly), not just linear ones. Pearson r can be 0 while Spearman r is non-zero if the relationship is monotonic but curved.
- Cross-reference:
pearson-correlation(this chapter) for the linear counterpart and the formula that Spearman applies to ranks.