Phasing¶
Pass phase_resolver= when you have evidence linking variants on the same
allele. Varcode can then predict combined effects; patient-baseline annotation
also takes germline context.
Phased VCF¶
Load the jointly phased calls and select the sample whose phase should be used:
from varcode import load_vcf, VCFPhaseResolver
phased_variants = load_vcf("merged_phased.vcf", genome=81)
phaser = VCFPhaseResolver(phased_variants, sample="TUMOR")
effects = phased_variants.effects(phase_resolver=phaser)
The resolver uses phased GT and PS fields from the loaded collection.
Use one resolver per sample. Phase-set tags are local to their source VCF:
matching tags in independently produced tumor and normal files do not establish
relative phase. A joint call set or molecular evidence is needed.
For germline-aware somatic annotation, the resolver's collection should contain
both sets of alleles, but call somatic_variants.effects(germline=...,
phase_resolver=phaser) on the somatic variants you want to classify.
RNA evidence¶
For direct read/fragment co-occurrence in an RNA-seq BAM:
from varcode import MolecularPhaseResolver, RNAReadPhasingSource
source = RNAReadPhasingSource("tumor.rna.bam")
phaser = MolecularPhaseResolver(source)
effects = variants.effects(phase_resolver=phaser)
Here variants is the loaded collection to annotate. Short- and long-read
data can both leave phase unknown; coverage and linked alleles determine whether
a pair is resolved. Raw BAM phasing does not provide an assembled
MutantTranscript. Assembly-backed sources can provide one; see the
source protocols.
ReadPhaseResolver remains a compatibility name for MolecularPhaseResolver.
Unknown phase¶
When a somatic and germline variant share a codon and relative phase is unknown,
the result can be a PhaseCandidateSet, with one classified effect per
haplotype hypothesis. Read .candidates to retain the alternatives. Resolved
phase can reduce the set to one effect, as in the example below.
Two variants in one codon¶
CFTR has a somatic at GRCh38 7:117531100 T→A. A neighbouring
germline at 7:117531101 T→C lands in the same codon.
from pyensembl import cached_release
from varcode import Variant, VariantCollection, GermlineContext
g = cached_release(81)
cftr = g.transcript_by_id("ENST00000003084")
somatic = Variant("7", 117_531_100, "T", "A", genome=g)
germline = Variant("7", 117_531_101, "T", "C", genome=g)
ctx = GermlineContext.from_variants([germline], reference_name="GRCh38")
Without germline context¶
eff = somatic.effect_on_transcript(cftr)
print(type(eff).__name__, eff.short_description)
# Substitution p.L159M
With germline context, phase unknown¶
eff = somatic.effect_on_transcript(cftr, germline=ctx)
print(type(eff).__name__, eff.short_description)
# PhaseCandidateSet ?p.L159M
for c in eff.candidates:
ev = c.evidence
print(f" haplotype={ev['haplotype']:<2} "
f"germline_in_cis={[v.short_description for v in ev['germline_variants']]} "
f"=> {c.effect.short_description}")
# haplotype=B germline_in_cis=[] => p.L159M
# haplotype=A germline_in_cis=['chr7 g.117531101T>C'] => p.S159T
The ? prefix on ?p.L159M flags the description as the
most-likely candidate of a PhaseCandidateSet. In application code,
check isinstance(eff, PhaseCandidateSet) and iterate
eff.candidates (a tuple of EffectCandidate objects carrying
per-hypothesis evidence keys); use eff.effects if only the
inner classified MutationEffects are needed.
With known phase¶
A real pipeline gets the cis/trans answer from a phased VCF or an RNA assembly. For this demo, a small resolver makes the collapse visible:
These stubs only implement in_cis(...), which is all the
codon-collapse path consults. Richer pipelines implement more of
the duck-typed resolver interface, including mutant_transcript and
phased_partners. The phasing API documents the built-in
resolvers and source protocols; no public PhaseResolver class is required.
class ForceCis:
source = "demo"
def in_cis(self, v1, v2, transcript=None): return True
class ForceTrans:
source = "demo"
def in_cis(self, v1, v2, transcript=None): return False
eff_cis = somatic.effect_on_transcript(
cftr, germline=ctx, phase_resolver=ForceCis())
print("cis ->", type(eff_cis).__name__, eff_cis.short_description)
# cis -> Substitution p.S159T
eff_trans = somatic.effect_on_transcript(
cftr, germline=ctx, phase_resolver=ForceTrans())
print("trans ->", type(eff_trans).__name__, eff_trans.short_description)
# trans -> Substitution p.L159M
For real evidence, use the phased VCF or RNA examples above; these stubs only demonstrate the two possible answers.
Combining evidence¶
Given a somatic VariantCollection, patient context, phase resolver, and RNA
resolver:
effects = somatic_variants.effects(
germline=germline_ctx,
phase_resolver=phaser,
rna_resolver=rna,
)
Order: germline modifies the transcript first, phase collapses the
candidate set next, and RNA refines multi-candidate effects. Splice
mechanism sets are reconciled to observed mechanisms; other
multi-outcome effects append observed-only candidates. Cross-axis key is
EffectCandidate.evidence["haplotype"], so an
RNA observation tagged with the same haplotype tag aligns with the
right germline-aware outcome.
Known deletion haplotypes in RNA alignments¶
Splice-aware aligners can encode a known deletion's RNA sequence with N
rather than D, or split the gap among mismatches and smaller deletions.
Opt in to a local sequence hypothesis instead of interpreting the gap as
proof of a DNA deletion:
from varcode import MolecularPhaseResolver, RNAReadPhasingSource
source = RNAReadPhasingSource("tumor.rna.bam", min_alt_reads=1)
source.register_haplotype([known_deletion, adjacent_snv_1, adjacent_snv_2])
resolver = MolecularPhaseResolver(source)
effects = variants.effects(phase_resolver=resolver)
The variants must share one genome dataset and contig. The genome must provide reference sequence across the interval, through a FASTA or annotated transcript. Every retained base between five-base reference flanks must match one alignment and pass the configured quality/read-edge filters. No mates or separate partial haplotypes are stitched together. Registration invalidates cached counts.
Registration does not assert cis phase: it supplies a candidate to test. For registered variants, only full-context matches count as alternate support; nonmatches remain unknown, not trans evidence. The current local mode supports nonoverlapping substitutions and deletions. Register competing combinations separately. Unregistered variants keep the ordinary CIGAR-based behavior.
For the audited GRCh38 MAP2 example, the combination of 209694769 C>A,
209694770 T>G, and the 28-base deletion at 209694773 gives the same local
sequence under 28D, 28N, and 22D2M6D alignments. An unrelated 19,449-base
exon skip lacks the required local anchors and is not supporting evidence.
Implementation limits¶
Combined haplotype effects are currently built by VariantCollection.effects(),
outside the selected annotator (#437).
Switching to an experimental annotator does not give it ownership of that step.
See germline limitations for hypothesis limits and
normalization requirements, and the phasing API for custom sources.