fix: bootstrap ratios from raw saliences, not S-scaled loadings - #26
fix: bootstrap ratios from raw saliences, not S-scaled loadings#26eilidhmacnicol wants to merge 2 commits into
Conversation
Code reviewFound 1 issue (blocker):
Reproduced on The mathematical motivation (raw-salience BSR, no S) is correct; the alignment step needs reworking so both sides retain genuine resampling variance (e.g. derive the rotation from a non-degenerate quantity rather than the square Vt block, following the pyls/McIntosh convention). Do not merge or release until the Y-side SE is non-degenerate. Lines 146 to 151 in 16d1a0c 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
|
This was spearheaded by an LLM hallucination. On further investigation, s-scaled loadings are preferred, so won't fix. |
Summary
Bootstrap ratios were being formed from S-scaled loadings (
U·S,S·Vᵀ) instead of the raw saliences (U,Vᵀ). The canonical PLSC bootstrap ratio (McIntosh & Lobaugh 2004; Krishnan et al. 2011; thepylsreference implementation) issalience / bootstrap SE, with singular values playing no part.Because
Swas re-estimated in every resample, its sampling variability leaked into the bootstrap SE, deflating the ratios — most on weaker / non-dominant latent variables. The dominant LV is largely unaffected (its singular value is stable, soSnearly cancels); the error therefore caused under-reporting (false negatives) on secondary effects, never false positives.Changes
plsdo/core.pyfit():u_loadings/vt_loadingsare now the raw saliencesu/vt(no* s).bootstrap(): the bootstrap distribution collects the Procrustes-aligned raw singular vectors, sou_se/vt_seare salience SEs andBSR = salience / SE. Removed the now-unusedboot_s.BSR = loadings / SEline and all ofpipeline.pyare unchanged: the meanings ofu_loadings/u_se/u_bootstrap_ratiosshift together and stay internally consistent (loading plots, error bars,filter_lvs).tests/test_core.pytest_loadings_are_scaled_vectors→test_loadings_are_raw_saliences.u_loadings[:, -1] ≈ 0assertion in the inert-LV test (only true under S-scaling; the LV's harmlessness is still guarded by the significance /filter_lvsassertions).tests/data/regression/: regenerated the 8 affected loadings/BSR snapshots (both methods). Singular values, p-values, subject scores and CV snapshots re-matched on regeneration, confirming nothing else moved.Verification
pytest tests/→ 208 passed.ruff check/ruff format --checkclean.🤖 Generated with Claude Code