Reject soft clipping that comes from only one read strand - #424
Merged
Conversation
SC evidence was unusable on real data. Across 29 LTEE Ara-2 clones it
accepted 1040 positions, 5 to 620 per sample, and nearly all of them were
one artifact: Illumina dark-cycle poly-G read tails. 89% of the accepted
calls had a homopolymer run of 8 or more in the 12-base consensus tail and
802 were pure GGGGGGGGGGGG or CCCCCCCCCCCC. The tail-consensus test is
structurally blind to these -- tails that are all G agree with each other
perfectly -- and they pile up at reproducible positions because the aligner
extends the poly-G into a reference G-run and clips at its last base.
The discriminator is strand. The artifact is always the read's 3' end, so
for a given clip direction it can only come from one strand: direction +1
from forward reads, direction -1 from reverse reads. 993 of those 1040
calls had every clipped read on a single strand; every plausible real
breakpoint was balanced. Two new soft rejects:
FISHER_STRAND Fisher's exact test of the agreeing clipped reads'
strand split against the strand split of the reads
that read through the same position, mirroring the RA
polymorphism test. Comparing against local coverage
rather than 50/50 is what keeps a genuinely
strand-skewed pileup from reading as a strand-skewed
clip. Where a position has no read-through at all --
the frequency == 1.000 case, which carries the highest
clip counts -- the contingency row is empty and Fisher
returns 1 for anything, so it falls back to the
genome-wide spanning strand ratio.
LOW_COMPLEXITY_TAIL Homopolymer or near-homopolymer consensus tail. Reaches
the 5-7 read positions where the strand test has no
power.
Neither changes p0, rho or the score: the artifact clips stay in the null,
which keeps it conservative. Removing them would instead make everything
else more significant.
Tabulation now splits both the spanning coverage and the tail agreement by
read strand. The strand rides in bit 63 of the packed tail (21 bases x 3
bits fills only bits 0-62), so it costs no memory, and the two spanning
difference arrays sum to what the single array held before -- every
pre-existing SC field in every golden is byte-identical. Counts file goes to
sc_format=3 with four new columns.
summary.html gains an SC gates table. Its load-bearing row is the fraction
of clip events that saw only one read strand: 98.1% and 99.9% in the long
tests here. That number is what explains a library predicting implausibly
many SC items, and nothing in the report showed it before.
Measured effect on the long tests: m1_40k_pe36 3 accepted -> 0,
p1_50k_pe101 6 -> 2, p3_30k_pe150 8 -> 2, including CTGTCTCTTATA (the
Nextera adapter, 26 forward reads and 0 reverse). Replayed over the Ara-2
set, 1040 -> 40. No non-SC line of any golden changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
SC evidence has been unusable on real data. Across the 29 LTEE Ara-2 clones in
claude-clone-curation, breseq accepted 1040 SC positions — 5 to 620 per sample — and nearly all of them are one artifact: Illumina dark-cycle poly-G read tails.GGGGGGGGGGGGorCCCCCCCCCCCC.+1→poly-G,−1→poly-C), because BAM stores SEQ reference-forward.…GGCGGon the read's own strand).The existing tail-consensus test is structurally blind to this: tails that are all G agree with each other perfectly.
The discriminator
Strand. The artifact is always the read's 3' end, so for a given clip direction it can only come from one strand — direction
+1from forward reads, direction−1from reverse reads. Reads clipped at a real breakpoint come from both.993 of 1040 accepted calls (95.5%) had every clipped read on a single strand. Verified read-by-read in the BAMs:
+1−1−1GGTGCAGGTGCG, f=1.00+1TACTTTATGTAC−1GGGTGCAGTACA, f=1.00Changes
Two new Tier-2 soft rejects in
add_sc_evidence():FISHER_STRAND(--soft-clipping-fisher-strand-p-value-cutoff, default 0.05) — Fisher's exact test of the agreeing clipped reads' strand split against the strand split of the reads that read through the same position, mirroring the RA polymorphism test and reusingfisher_exact_test_2x2. Comparing against local coverage rather than 50/50 keeps a genuinely strand-skewed pileup from reading as a strand-skewed clip. Where a position has no read-through at all — thefrequency = 1.000case, which carries the highest clip counts — the contingency row is empty and Fisher returns 1 for anything, so it falls back to the genome-wide spanning strand ratio.LOW_COMPLEXITY_TAIL(--soft-clipping-maximum-tail-homopolymer-fraction0.66,--soft-clipping-maximum-tail-base-fraction0.75) — reaches the 5–7 read positions where the strand test has no power.Neither changes
p0,rhoor the score. The artifact clips stay in the null, which keeps it conservative; removing them would instead make everything else more significant.Supporting work:
#sc_format=3.summary.html. Its load-bearing row is the fraction of clip events that saw only one read strand — 98.1% and 99.9% in the long tests here. That number is what explains a library predicting implausibly many SC items, and nothing in the report showed it before.Verification
All 95 tests pass (
make test-long, 23:17 total).Long-test effect:
long_ltee_ara_m1_40k_pe36long_ltee_ara_p1_50k_pe101long_ltee_ara_p3_30k_pe150CTGTCTCTTATA(Nextera adapter, 26 fw / 0 rv)Replayed over the Ara-2 set: 1040 → 40 accepted, 0–9 per sample.
The tabulation change is provably inert: across all four SC tests every pre-existing SC field (
read_count,total_count,agree_read_count,log10_e_value,frequency,clipped_sequence,consensus_fraction) is byte-identical, no SC row appeared or disappeared, both strand splits sum exactly to their totals, and all 1941/1103/2915 non-SC golden lines are unchanged.Note
p3_30k_pe150position 2005207 survives at 8 fw / 0 rv (p = 0.077, just above 0.05) because its tail isn't low-complexity. Tightening to p < 0.10 would catch it, at some cost in genuine low-count calls.🤖 Generated with Claude Code