Skip to content

Add mutation spectra analysis#37

Merged
Adamtaranto merged 13 commits into
mainfrom
spectra_profiling
Jul 14, 2026
Merged

Add mutation spectra analysis#37
Adamtaranto merged 13 commits into
mainfrom
spectra_profiling

Conversation

@Adamtaranto

Copy link
Copy Markdown
Owner

Introduce derip2-spectra, a new entry point that builds SigProfiler SBS-96 (pyrimidine-collapsed) and SBS-192 (strand-resolved) trinucleotide mutation spectra by calling every substitution of each aligned sequence against the deRIP'd ancestral consensus.

  • src/derip2/spectra/: channel ordering + pyrimidine folding + nearest non-gap context (channels.py), SigProfiler-compliant matrix I/O (matrix_io.py)
  • src/derip2/stats/mutation_spectra.py: SpectraResult + vectorised compute_spectra, with a multi-hit-column homoplasy proxy
  • src/derip2/plotting/spectra.py: native SBS-96/192, strand-asymmetry (binomial screen) and homoplasy figures, reusing the strand-bias palette
  • DeRIP.calculate_spectra/write_spectra_matrix/plot_spectra
  • app_spectra.py CLI + derip2-spectra script entry point
  • tests: channels, spectra (synthetic + golden), plots, CLI (32 tests)

Pure-Python, no new dependencies. Full suite green (234 passed).

Adamtaranto and others added 2 commits July 13, 2026 17:30
Introduce derip2-spectra, a new entry point that builds SigProfiler
SBS-96 (pyrimidine-collapsed) and SBS-192 (strand-resolved) trinucleotide
mutation spectra by calling every substitution of each aligned sequence
against the deRIP'd ancestral consensus.

- src/derip2/spectra/: channel ordering + pyrimidine folding + nearest
  non-gap context (channels.py), SigProfiler-compliant matrix I/O
  (matrix_io.py)
- src/derip2/stats/mutation_spectra.py: SpectraResult + vectorised
  compute_spectra, with a multi-hit-column homoplasy proxy
- src/derip2/plotting/spectra.py: native SBS-96/192, strand-asymmetry
  (binomial screen) and homoplasy figures, reusing the strand-bias palette
- DeRIP.calculate_spectra/write_spectra_matrix/plot_spectra
- app_spectra.py CLI + derip2-spectra script entry point
- tests: channels, spectra (synthetic + golden), plots, CLI (32 tests)

Pure-Python, no new dependencies. Full suite green (234 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codspeed-hq

codspeed-hq Bot commented Jul 13, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 9 untouched benchmarks
🆕 2 new benchmarks
⏩ 1 skipped benchmark1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
🆕 Simulation test_bench_compute_spectra N/A 10.9 ms N/A
🆕 Simulation test_bench_compute_spectra_from_tree N/A 13.8 ms N/A

Comparing spectra_profiling (e4f0c22) with main (2489994)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

Adamtaranto and others added 3 commits July 13, 2026 18:32
Layer the rigorous IQ-TREE path on top of the tree-free baseline. Instead of
comparing every sequence to one reference, it reconstructs ancestral sequences
on a rooted tree and calls each substitution per parent->child branch, reading
context from the parent. This counts recurrent (homoplasic) deamination
correctly: on mintest, a site the baseline scores as 4 hits is 2 independent
branch events.

- src/derip2/spectra/tree_asr.py: first subprocess wrapper in the codebase.
  Runs IQ-TREE --ancestral (iqtree3/2 auto-detected), parses .state, loads the
  tree with ete4, roots it (outgroup/midpoint/none) by orienting edges from an
  existing node so every node keeps a sequence. reconstruct(), assign_clades(),
  orientation_flip_fraction() for root sensitivity.
- src/derip2/spectra/call_mutations.py: per-branch event stream -> shared
  assembler; true per-branch homoplasy; parent/child event provenance;
  min_prob posterior filtering.
- src/derip2/stats/mutation_spectra.py: extract assemble_matrices() +
  IDX96/IDX192 code tables shared by both paths; generalise SpectraResult with
  method + parent/child event names.
- src/derip2/spectra/qc.py: per-column gap/ambiguity profile + reports.
- app_spectra.py: --method phylo, --tree, --iqtree-model, --threads, --rooting,
  --outgroup, --min-prob, --root-sensitivity, --partition-by clade; run manifest.
- environment.yml/pyproject: iqtree + ete4 (optional 'spectra' group).
- tests: hand-built trees (polarity, homoplasy, inheritance, min_prob, clades),
  committed .state/.treefile fixtures, QC, IQ-TREE runs guarded by shutil.which.
  253 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.08115% with 141 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.50%. Comparing base (2489994) to head (e4f0c22).

Files with missing lines Patch % Lines
src/derip2/app_spectra.py 76.56% 40 Missing and 5 partials ⚠️
src/derip2/stats/spectra_compare.py 72.35% 22 Missing and 12 partials ⚠️
src/derip2/spectra/tree_asr.py 89.08% 16 Missing and 9 partials ⚠️
src/derip2/derip.py 54.54% 13 Missing and 2 partials ⚠️
src/derip2/stats/mutation_spectra.py 95.51% 5 Missing and 2 partials ⚠️
src/derip2/spectra/matrix_io.py 84.61% 3 Missing and 3 partials ⚠️
src/derip2/aln_ops.py 75.00% 3 Missing and 2 partials ⚠️
src/derip2/spectra/call_mutations.py 97.47% 1 Missing and 2 partials ⚠️
src/derip2/spectra/channels.py 97.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #37      +/-   ##
==========================================
+ Coverage   86.99%   87.50%   +0.51%     
==========================================
  Files          12       22      +10     
  Lines        1853     3034    +1181     
  Branches      306      488     +182     
==========================================
+ Hits         1612     2655    +1043     
- Misses        151      252     +101     
- Partials       90      127      +37     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Adamtaranto and others added 8 commits July 13, 2026 19:30
…tone 3)

- docs/tutorials/mutation-spectra.md: full tutorial with real example images
  from the Sahana transposon, added to mkdocs nav. Covers baseline vs
  phylogenetic, supplying a precalculated phylogeny (--tree with iqtree3), and
  the recommended RIP-masked-topology workflow (deRIP2 --mask -> iqtree3 ->
  --tree so the masked topology carries ancestral states recomputed for the
  UNMASKED sequences). README + CLI docs updated with the same workflows.
- run_iqtree now always passes -m model, so --iqtree-model applies to fixed-tree
  (-te) runs — required for the masked-topology workflow.
- tree_asr: map IQ-TREE-sanitised tree leaf names ([^A-Za-z0-9._-] -> _) back to
  alignment ids, so realistic FASTA headers (e.g. 'scf:1-9(+)') work. New
  _sanitize_name + tests.
- Benchmarks for compute_spectra and compute_spectra_from_tree.
- Reconcile pyproject: ete4 is a core dep; drop the redundant spectra extra
  (IQ-TREE binary still installed separately).

Baseline-vs-phylo contrast on 60 Sahana copies: 38,727 vs 7,560 events, 1,163
sites hit >=3x independently. 256 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ilestone 4)

Feature: per-group spectra
- --groups FILE maps sequence names to group labels (e.g. species) and reports
  one spectrum per group. Works for baseline (per-row samples) and phylo (a
  branch is attributed to a group only when its whole descendant clade shares
  it; spanning branches -> 'mixed'). Name matching tolerates IQ-TREE
  sanitisation, so the same label file works for MSA ids and tree tips.
  assign_groups() in tree_asr, _load_group_lookup() in app_spectra, samples=
  override on DeRIP.calculate_spectra. Tests for both methods.

Plot fixes
- Strand asymmetry: bars now encode strand by colour (blue=coding, orange=
  template) so the legend matches; added a footnote explaining '*' = binomial
  p<0.05 vs 50:50.
- Homoplasy: translucent stacked markers (overlaps stay visible) + a
  substitution-class legend; y-axis names the recurrence unit per method.
- homoplasy_table now counts recurrence over the actual per-event ref/alt
  (fixes a phylo KeyError where the root base equalled a derived base).

Docs (docs/tutorials/mutation-spectra.md)
- Real COSMIC decomposition of the full Sahana spectrum via
  SigProfilerAssignment (cosine 0.80) with a prominent caveat that human-cancer
  signatures do not apply directly to fungi and a fungal reference library is
  the right path.
- Per-clade and per-group example figures; masked-topology spectrum figure;
  baseline/phylo homoplasy figures.
- Full-Sahana (n=396) baseline and phylogenetic spectra (n in titles); honest
  note that ML ASR's majority bias inflates T>C on heavily-RIP'd families, so
  the consensus baseline is more robust for direction.
- CLI docs + README note --groups; masked workflow gains -st DNA (heavily
  masked alignments defeat IQ-TREE's sequence-type auto-detection).

260 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Statistical comparison of spectra (new stats/spectra_compare.py, no SciPy):
- cosine_similarity (effect size) and a dependency-free chi-squared test of
  homogeneity (regularised incomplete gamma; validated to match SciPy exactly),
  with per-channel standardised residuals and Cramer's V.
- compare_spectra (two vectors -> cosine, chi2, top differentiating channels)
  and pairwise_compare (all group pairs, Bonferroni). Works on group columns or
  two precalculated matrices. Tutorial adds worked examples for both, with the
  caveat to read the p-value beside the effect size (huge n over-powers chi2).

Strand-asymmetry plot: only star a class when both strands carry >= min_count
events (default 10) so a near-empty strand is not flagged; asterisk sits on the
bar top (hugs small bars at the axis) not floating; footnote states the rule.

Homoplasy plot: legend moved to a horizontal row below the axes so it no longer
overlaps the densely-packed data points.

Baseline: full worked example in the tutorial explaining the deRIP-consensus
ancestor - the default internal reconstruction and how to compute/pass it
explicitly via `derip2` + `--ancestor` (CLI and Python).

269 tests pass; docs build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+tests

derip2-spectra baseline can now reuse an ancestor already in the input MSA:
- new --reference-tag (default deRIPseq, exact id) detects a consensus row,
  uses it as the ancestor, and excludes it from the counted sequences
- precedence: --ancestor file > in-MSA tag > recomputed deRIP consensus
- --ancestor validated for length == alignment width up front; read via new
  ao.loadFirstSequence so single-sequence FASTAs work

Input validation:
- ao.validate_no_degenerate gates input to A/C/G/T/- (lowercase accepted);
  N and IUPAC codes raise a clear, located error
- ao.uppercase_alignment normalises soft-masking so lowercase input no longer
  silently changes the deRIP consensus (RIP detection is upper-case only)

strandbias plot: named ARM_LABEL_HEADROOM (1.22) replaces the inline 1.06 so
the forward/reverse strand arm labels clear the tallest bars.

Tests: new tests/conftest.py (data-path fixtures, make_reconstruction factory,
have_iqtree); offline coverage for tree_asr guards, app_spectra new features
and edge cases, spectra-plot branches, strandbias headroom. 307 tests pass;
plotting/spectra 100%, tree_asr/app_spectra 97%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…igures

- README + CLI docs + mutation-spectra tutorial: --reference-tag / in-MSA
  deRIPseq reuse, --ancestor length validation, and the A/C/G/T/- input
  requirement (IUPAC rejected). Corrected the worked example so the reused
  row id matches derip2's --prefix (default deRIPseq).
- Regenerated strand_bias_{derip,logo,noise}.png so the forward/reverse arm
  labels clear the tallest bars (ARM_LABEL_HEADROOM change). Content identical,
  same dimensions, only added vertical headroom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Adamtaranto Adamtaranto merged commit a3a73d7 into main Jul 14, 2026
11 checks passed
@Adamtaranto Adamtaranto deleted the spectra_profiling branch July 14, 2026 06:27
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