pythonforbio.
[WASM idle]
HGVS Nomenclature05/11

SequenceVariant

Starting sandbox…
Beginnerlesson

SequenceVariant

In one line: the object model behind a parsed HGVS string — accession, type, position and edit, all separately addressable.

The structure

SequenceVariant
 ├─ ac        'NM_000546.6'      the accession, with version
 ├─ type      'c'                g | c | n | r | p | m
 ├─ gene      None               optional
 └─ posedit   PosEdit
     ├─ pos   Interval
     │   ├─ start   BaseOffsetPosition(base=215, offset=0, datum=CDS_START)
     │   └─ end     BaseOffsetPosition(base=215, offset=0, datum=CDS_START)
     ├─ edit  NARefAlt(ref='C', alt='G')
     └─ uncertain  False
var = hp.parse_hgvs_variant("NM_000546.6:c.215C>G")

var.ac                          # 'NM_000546.6'
var.type                        # 'c'
var.posedit.pos.start.base      # 215
var.posedit.pos.start.offset    # 0  (non-zero = intronic)
var.posedit.pos.end.base        # 215
var.posedit.edit.ref            # 'C'
var.posedit.edit.alt            # 'G'
var.posedit.edit.type           # 'sub'
str(var)                        # round-trips to the original string

Position classes

Class Used by Fields
SimplePosition g., m., n. base
BaseOffsetPosition c., r. base, offset, datum
AAPosition p. base, aa
Interval all start, end, uncertain

BaseOffsetPosition is where the complexity lives:

from hgvs.enums import Datum

pos.base      # the coding position
pos.offset    # 0 = exonic; ±n = intronic
pos.datum     # Datum.CDS_START (normal), CDS_END (the * region), SEQ_START
pos.is_intronic       # offset != 0
pos.is_uncertain

Worked examples:

HGVS base offset datum
c.215 215 0 CDS_START
c.-24 −24 0 CDS_START
c.87+3 87 +3 CDS_START
c.88-3 88 −3 CDS_START
c.*12 12 0 CDS_END
c.*12-8 12 −8 CDS_END

base alone is not a position. c.87+3 has base 87, and so does c.87; they are 3 bp apart in the genome and in completely different functional contexts (one intronic, one exonic). Any code doing if var.posedit.pos.start.base == 87 without checking the offset is wrong.

Likewise, datum distinguishes c.12 (the 12th coding base) from c.*12 (the 12th base after the stop) — both have base == 12.

Edit classes

Class For Fields
NARefAlt sub, del, ins, delins, identity ref, alt
Dup duplication ref
Inv inversion ref
Repeat tandem repeats ref, min, max
AARefAlt protein sub/del/ins ref, alt
AAFs frameshift ref, alt, length
AAExt extension
AASub protein substitution
var.posedit.edit.type      # 'sub' | 'del' | 'ins' | 'delins' | 'dup' | 'inv' | 'identity'

Dispatching on edit.type is how you write code that handles all variant classes:

def describe(var):
    e = var.posedit.edit
    match e.type:
        case "sub":     return f"{e.ref}>{e.alt}"
        case "del":     return f"deletion of {len(e.ref or '')} bp"
        case "ins":     return f"insertion of {e.alt}"
        case "delins":  return f"{len(e.ref or '')} bp → {e.alt}"
        case "dup":     return "duplication"
        case "inv":     return "inversion"
        case _:         return e.type

ref is often None. HGVS permits c.76_78del without spelling out the deleted bases. hgvs fills it in from the sequence during normalization — one of the reasons to normalize before analysing. Guard with e.ref or "".

Constructing variants programmatically

import hgvs.edit, hgvs.location, hgvs.posedit, hgvs.sequencevariant
from hgvs.enums import Datum

pos = hgvs.location.BaseOffsetPosition(base=215, offset=0, datum=Datum.CDS_START)
iv = hgvs.location.Interval(start=pos, end=pos)
edit = hgvs.edit.NARefAlt(ref="C", alt="G")

var = hgvs.sequencevariant.SequenceVariant(
    ac="NM_000546.6", type="c",
    posedit=hgvs.posedit.PosEdit(pos=iv, edit=edit),
)
str(var)          # 'NM_000546.6:c.215C>G'

Verbose, but it is how you build variants from structured data (a VCF row, a database record) without string formatting. Building a variant by string concatenation and then parsing it is fragile — you can easily produce something syntactically valid and semantically wrong (an ins that should be a dup).

Copying and mutating

SequenceVariant objects are effectively value objects. To modify one, copy it:

import copy
v2 = copy.deepcopy(var)
v2.ac = "NM_000546.5"

Mutating in place risks aliasing bugs if you have kept references. Most hgvs operations (normalize, c_to_g) return new objects rather than mutating, which is the right design.

Extracting to a DataFrame

The bridge into the pandas half of your analysis:

def to_row(var):
    pe = var.posedit
    return {
        "accession": var.ac,
        "type": var.type,
        "start_base": pe.pos.start.base,
        "start_offset": getattr(pe.pos.start, "offset", 0),
        "end_base": pe.pos.end.base,
        "end_offset": getattr(pe.pos.end, "offset", 0),
        "edit_type": pe.edit.type,
        "ref": getattr(pe.edit, "ref", None),
        "alt": getattr(pe.edit, "alt", None),
        "is_intronic": getattr(pe.pos.start, "offset", 0) != 0,
        "hgvs": str(var),
    }

df = pd.DataFrame([to_row(v) for v in variants])

getattr(..., default) throughout, because g. variants use SimplePosition (no offset) and Dup/Inv edits have no alt. Writing pe.pos.start.offset unconditionally crashes on genomic variants.

Comparison and equality

v1 == v2                      # structural equality
str(v1) == str(v2)            # string equality

Neither is biological equivalence. c.76_78del and c.77_79del can be the same event in a repeat, and they compare unequal. To compare variants meaningfully:

  1. Normalize both (hgvs Normalizer).
  2. Project both to the same reference type and accession (AssemblyMapper).
  3. Then compare.

Skipping either step gives you false negatives. This is the single most consequential thing to understand about working with variants. See hgvs Pitfalls.

Common mistakes

  • Using base without offset. c.87+3c.87.
  • Ignoring datum. c.12c.*12.
  • Assuming ref is populated. Normalize first, or guard with or "".
  • Assuming alt exists on dup and inv edits.
  • pos.start.offset on a g. variant → AttributeError.
  • Comparing unnormalized variants and concluding they differ.
  • String-building variants instead of constructing objects.
  • Stripping the accession version.

See also

hgvs Parser · HGVS Nomenclature · hgvs Normalizer · Projection c to g to p · Coordinate Systems · hgvs Pitfalls

lesson example

No output yet — run the code to populate this drawer.