Statistics for Traders #23: Partial correlation on today's slot-1 CAD CPI × USDCAD sample — total r is −0.43, but controlling for the 5-bucket assignment collapses it to −0.05 (95% CI crosses zero)
The Pearson r between surprise_z and 15-minute move_pips across 195 CAD CPI m/m × USDCAD releases is −0.4256— the correlation-language version of today’s slot-1 5-bucket monotonic walk. Partial correlation formally decomposes it. Controlling for the 5-bucket surprise assignment, the partial r drops to −0.05 (ordinal) or −0.06 (full categorical) and both Fisher 95% CIs cross zero. The between-bucket r (weighted by n) is −0.9691: nearly the entire structure lives in the differences between bucket means.
This is the Statistics installment that closes a queue item flagged since Stats #11 introduced Pearson r 12 days ago, and formalises the “buckets carry the signal” pattern via the partial-correlation identity r_XY|Z ≈ 0.
![Horizontal-bar chart with Fisher 95% confidence-interval whiskers. Top rows: 'Overall (n=195)' Pearson r = -0.426 with tight CI [-0.534, -0.303] drawn in solid dark, clearly negative. 'Partial | bucket ordinal' r = -0.053 with CI [-0.193, +0.089] drawn in coral, crossing the r=0 coral reference line. 'Partial | bucket categorical' r = -0.064 with CI [-0.204, +0.078] also in coral, also crossing zero. Bottom rows: five within-bucket r values in muted grey. big_miss (n=13) r=-0.178 with wide CI crossing zero; small_miss (n=46) r=+0.096 crossing zero; in_line (n=84) r=-0.350 with tighter CI slightly to the negative side; small_beat (n=32) r=+0.024 crossing zero; big_beat (n=20) r=+0.247 crossing zero. Only in_line has a within-bucket r above 0.2 in magnitude and CI that doesn't fully cross zero. Coral vertical line at r=0 makes the crossings visible at a glance.](/insights/stats-for-traders-partial-correlation/comparison.png)
The partial-correlation formula, plugged
The identity for partial correlation of X and Y controlling for Z is
r_XY|Z = (r_XY − r_XZ · r_YZ) / sqrt((1 − r_XZ²)(1 − r_YZ²))
With X = surprise_z, Y = 15m move_pips, Z = bucket ordinal (big_miss = −2, small_miss = −1, in_line = 0, small_beat = +1, big_beat = +2):
| r(surprise_z, move_pips) | −0.4256 |
| r(surprise_z, bucket_ord) | +0.9488 |
| r(move_pips, bucket_ord) | −0.4327 |
| r_XZ · r_YZ | −0.4106 |
| r_XY − r_XZ·r_YZ (numerator) | −0.0151 |
| sqrt((1−0.9488²)(1−0.4327²)) (denom) | +0.2848 |
| r_partial (ordinal bucket) | −0.0531 |
Fisher 95% CI with dof = n − 1 − 3 = 191 (subtracting 1 for the controlled variable and 3 for Fisher’s empirical n−3 correction from Stats #22): [−0.192, +0.089] — crosses zero. Controlling for bucket collapses the correlation to statistical noise.
The categorical (4-dummy) version is even cleaner
The ordinal encoding fits a single linear coefficient on the bucket variable. That’s fine when the response is monotonic in bucket (as today’s is). To fully absorb any pattern including tail saturation or inversions, use a full 4-dummy categorical encoding: subtract off each bucket’s own mean of z and mean of move, then compute Pearson r on the residuals.
Result: r_partial (categorical) = −0.0643. Fisher CI with dof = n − 4 − 3 = 188: [−0.204, +0.078] — also crosses zero. The categorical version is algebraically identical to the sample-size-weighted pooled within-bucket r (both are the mean of the demeaned-in-bucket product divided by demeaned-in-bucket SDs). It comes out slightly more negative than the ordinal because the categorical absorbs the small median inversion between small_miss and big_miss that the ordinal-linear treatment mis-models.
Within-bucket r values
| Bucket | n | z-range in bucket | r | Reading |
|---|---|---|---|---|
| big_miss | 13 | −3.0 to −1.5 | −0.178 | small negative, CI wide (crosses zero) |
| small_miss | 46 | −1.5 to −0.5 | +0.096 | essentially zero |
| in_line | 84 | −0.5 to +0.5 | −0.350 | the ONE bucket with meaningful within-bucket r |
| small_beat | 32 | +0.5 to +1.5 | +0.024 | essentially zero |
| big_beat | 20 | +1.5 to +4.2 | +0.247 | positive (mild noise), CI wide |
Only in_line has a within-bucket r above 0.2 in magnitude — and it’s the only bucket with enough z-range (−0.5 to +0.5) for within-bucket surprise-direction to carry information. The four tail buckets all quantise a narrower z-range and their within-bucket r’s are indistinguishable from zero at 95%.
SS budget — where the variance lives
The ANOVA-style variance decomposition (between-bucket + within-bucket = total sum of squares) for both variables:
| Variable | Total SS | Between-bucket SS | Within-bucket SS | % between |
|---|---|---|---|---|
| surprise_z | 258.83 | 235.26 | 23.56 | 90.9% |
| move_pips | 169,211 | 33,031 | 136,180 | 19.5% |
Reading: buckets soak up 91% of the surprise-z variance (mechanical — bucket boundaries are quantiles of z). Buckets soak up 19.5% of the move-pips variance. That 19.5% is what the overall r = −0.43 is picking up: nearly all bucket-explained. The remaining 80.5% of move-pips variance is within-bucket noise that surprise_z does not linearly explain.
Same-day cross-links
Today’s slot 1 ( CAD CPI m/m × USDCAD) shows the 5-bucket monotonic-pct-up walk: 92.3 / 73.9 / 52.4 / 37.5 / 20.0 percent up-rate. That’s the between-bucket story — the −0.9691 weighted between-bucket r translated into pct-up columns. Today’s Stats installment quantifies the complement: within a bucket, surprise magnitude adds essentially nothing.
Cross-links: Stats #11 ( 2026-08-11 correlation intro, same event on CADJPY mirror pair — established the within-bucket pattern qualitatively); Stats #22 ( 2026-08-22 Fisher r-to-z, provides the CI machinery this post reuses); Stats #10 (autocorrelation caveat — the between-release sample is IID enough at monthly spacing).
Verification note
All r values computed in a scratch TypeScript script against the 195-release sample from /api/v1/news-impact/releases on 2026-08-23. Cross- verified against Python numpy.corrcoef to 4 decimal places on the total, ordinal-partial, and categorical-partial r values, plus all five within-bucket r values. Chart generated via a one-off script reusing scripts/insights-charts/svg.ts and theme.ts primitives; not committed under scripts/ since the horizontal-bar-with-whiskers layout is single-use for this post.