Skip to content

fix: thread PCA n_pcs and external covariates into GWAS assoc step - #247

Merged
dvitale199 merged 3 commits into
refactor/mainfrom
refactor/hardening-round-3-gwas-args
Jul 16, 2026
Merged

fix: thread PCA n_pcs and external covariates into GWAS assoc step#247
dvitale199 merged 3 commits into
refactor/mainfrom
refactor/hardening-round-3-gwas-args

Conversation

@dvitale199

Copy link
Copy Markdown
Owner

Summary

  • Fix PipelineRunner._run_single_step (assoc branch), which threaded only build and maf_lambdas into AssocConfig and silently dropped three user-supplied GWAS args:
    • --pca N (requested PC count): PCAConfig was built without n_pcs, so PCA always ran the default 10 PCs regardless of --pca N.
    • --covars / --covar-names: never reached AssocConfig.covariates, so external covariates were silently ignored and GWAS fell back to PCA eigenvectors as covariates.
  • Now threads n_pcs into PCAConfig and builds a CovariateConfig from the covar path/names when given; normalizes run_pca/run_gwas to bool. Adds CovariateConfig to the runner's config-class map.
  • Extends the old-vs-new parity harness to guard both args.
  • Updates REFACTOR_HARDENING.md (item 8 done) and TESTING.md.

This directly affects real-cohort GWAS: before the fix, --pca N and external --covars were ignored by the new CLI.

What changed

  • genotools/cli/runner.py — assoc branch threads n_pcs + covariates; CovariateConfig added to config-class map.
  • tests/unit/test_cli/test_runner_regression.py — new TestAssocStepThreadsPcaAndCovariateArgs (hermetic capture of the config passed to run_association: n_pcs + external covars reach AssocConfig, build preserved) and TestAssocStepPcaProducesRequestedPcs (end-to-end --pca 3 → 3-PC eigenvec via real PLINK2, was 10).
  • tests/regression/test_parity.pytest_old_vs_new_pca_ncount_parity (--pca 20 → 20-PC eigenvec in both CLIs) and test_old_vs_new_gwas_external_covars_parity (external covars used identically → p-values agree; decision B does not apply because the PCA eigenvectors are discarded for external covars).

Test plan

  • Unit + regression suite green: 397 passed (391 → 397) with .venv-stable present.
  • New unit tests fail RED against the pre-fix runner, pass after (n_pcs=10 vs 3; covar_path=None).
  • Both new parity tests verified to fail against the pre-fix runner (n_pcs 10 vs 20; 40500 p-mismatches + lambda 0.978 vs 1.007) and pass after.
  • Parity tests skip cleanly without .venv-stable/plink2.
  • Real-cohort parity (separate gate, run by dev before refactor/mainmain).

Notes

  • Targets refactor/main, not main.
  • Decision B unaffected: the external-covars parity test intentionally exercises the path where PCA eigenvectors are discarded, so the ratified PCA region-exclusion divergence does not apply and per-variant p-values must match.

The assoc branch of PipelineRunner._run_single_step threaded only `build`
and `maf_lambdas` into the config, silently dropping three user-supplied
GWAS args:

- `--pca N` (n_pcs): PCAConfig was built without n_pcs, so PCA always ran
  the default 10 PCs regardless of the requested count.
- `--covars` / `--covar-names`: never reached AssocConfig.covariates, so
  external covariates were ignored and GWAS fell back to PCA covariates.

Now passes n_pcs into PCAConfig and builds a CovariateConfig from the
covar path/names when given. run_pca/run_gwas are normalized to bool to
match the dataclass field types. Adds CovariateConfig to the runner's
config-class map.

Regression tests (tests/unit/test_cli/test_runner_regression.py):
- n_pcs and external covariates reach AssocConfig (hermetic, capture the
  config passed to run_association)
- build threading is preserved
- end-to-end: --pca 3 produces a 3-PC eigenvec via real PLINK2 (was 10)
Extends the old-vs-new parity suite to cover the two args the runner used
to drop (fixed in the previous commit):

- test_old_vs_new_pca_ncount_parity: --pca 20 must produce a 20-PC
  eigenvec in BOTH CLIs (new previously always wrote 10).
- test_old_vs_new_gwas_external_covars_parity: with --pca --gwas --covars
  --covar-names, both CLIs discard the PCA eigenvectors and use the
  external covariate file, so decision B does not apply and per-variant
  p-values must agree tightly. Before the fix the new CLI ignored --covars
  and fell back to PCA covariates (40500 p-mismatches, lambda 0.978 vs
  1.007); now they match.

Both tests verified to fail against the pre-fix runner and pass after.
They skip cleanly without .venv-stable/plink2.
- REFACTOR_HARDENING.md: add round-3 section (assoc-branch arg threading
  fix + parity harness extension); mark remaining-work item 8 done.
- TESTING.md: bump suite count 391 -> 397; add the two new parity rows.
@dvitale199
dvitale199 merged commit ebdac95 into refactor/main Jul 16, 2026
2 checks passed
@dvitale199
dvitale199 deleted the refactor/hardening-round-3-gwas-args branch July 16, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant