pythonforbio.
[WASM idle]
HGVS Nomenclature08/11

hgvs Parser

Starting sandbox…
Beginnerlesson

hgvs Parser

In one line: a formal PEG grammar that turns an HGVS string into a structured object — the one part of the library that needs no database.

Basic use

import hgvs.parser

hp = hgvs.parser.Parser()                 # construct ONCE — it compiles a grammar

var = hp.parse_hgvs_variant("NM_000546.6:c.215C>G")
var                                        # SequenceVariant(ac=NM_000546.6, type=c, ...)
str(var)                                   # 'NM_000546.6:c.215C>G' — round-trips

Construct the parser once and reuse it. Parser() compiles a PEG grammar, which takes on the order of a second. Doing it inside a loop over 10,000 variants is a self-inflicted 3-hour job.

Why not regex

A regex like r"c\.(\d+)([ACGT])>([ACGT])" handles the simplest case and fails on everything else:

c.215C>G                     substitution
c.76_78del                   range deletion
c.76_77insT                  insertion between bases
c.-24+5G>A                   intronic, in the 5'UTR
c.*12-8del                   intronic, in the 3'UTR
c.76TG[8]                    a tandem repeat
c.[215C>G;847C>T]            two variants in cis
p.(Arg175His)                a predicted protein change
p.Arg175ProfsTer13           a frameshift with a downstream stop
g.7676154_7676156delinsAC    a delins

The HGVS grammar handles all of these. It also rejects malformed input with a HGVSParseError rather than silently matching a prefix — which is the property that actually matters in a pipeline. A regex that half-matches gives you wrong data with no error.

Error handling

from hgvs.exceptions import HGVSParseError

def safe_parse(hp, s):
    try:
        return hp.parse_hgvs_variant(s)
    except HGVSParseError as e:
        return None, str(e)

HGVSParseError means the string is not valid HGVS syntax. It says nothing about whether the variant is real — that is hgvs Validator's job. A parse succeeding tells you the string is well-formed, not that base 215 of that transcript is actually a C.

Parsing thousands of clinician-entered strings, expect a 5–20% failure rate from legacy notation, typos, and Excel damage. Log the failures; do not discard them silently.

Parsing sub-components

hp.parse_c_variant("NM_000546.6:c.215C>G")
hp.parse_g_variant("NC_000017.11:g.7676154G>C")
hp.parse_p_variant("NP_000537.3:p.(Arg175His)")
hp.parse_c_posedit("215C>G")               # just the position + edit
hp.parse_c_interval("215_218")
hp.parse_hgvs_position("c.215")

Useful when you have the accession in one column and the change in another — a common shape for spreadsheets from clinical labs.

Round-tripping and normalising formatting

str(hp.parse_hgvs_variant("NM_000546.6:c.215C>G"))     # 'NM_000546.6:c.215C>G'

Parsing then re-stringifying gives you canonical formatting — consistent spacing, consistent representation. It does not give you canonical coordinates; that requires hgvs Normalizer and sequence data.

import hgvs.config
hgvs.config.global_config.formatting.max_ref_length = 0   # omit long ref sequences

The config controls how variants are rendered — for example whether c.76_78delCTT prints the deleted bases or just c.76_78del.

Batch parsing

import pandas as pd
from hgvs.exceptions import HGVSParseError

hp = hgvs.parser.Parser()

def parse_column(series):
    parsed, errors = [], []
    for s in series:
        try:
            parsed.append(hp.parse_hgvs_variant(s))
            errors.append(None)
        except HGVSParseError as e:
            parsed.append(None)
            errors.append(str(e))
    return pd.DataFrame({"variant": parsed, "parse_error": errors},
                        index=series.index)

result = df.join(parse_column(df["hgvs"]))
print(f"{result['parse_error'].notna().sum()} of {len(result)} failed to parse")
print(result.loc[result["parse_error"].notna(), "hgvs"].head(20).tolist())

Printing the failures is the important line — the failure patterns usually reveal one systematic problem (a missing accession prefix, a legacy IVS notation) that you can fix in bulk.

Cleaning before parsing

Common fixable problems in real-world data:

def tidy(s):
    s = str(s).strip()
    s = s.replace("–", "-").replace("—", "-")     # en/em dash → hyphen
    s = s.replace(">", ">")                             # HTML-escaped >
    s = re.sub(r"\s+", "", s)                                # internal whitespace
    if ":" not in s and default_accession:
        s = f"{default_accession}:{s}"                        # add a missing accession
    return s

Word processors substituting en-dashes for hyphens is a genuinely common cause of parse failures in variant lists pasted from documents.

Do not "fix" the semantics. Repairing whitespace and dashes is safe. Rewriting c.76_77insG to c.76dup is a normalization decision that belongs to hgvs Normalizer with sequence data behind it, not to a cleanup regex.

Performance

Parsing is pure computation — no network, no database — and runs at roughly 1,000–10,000 variants per second depending on complexity. It will not be your bottleneck. Projection and normalization, which hit UTA and Data Providers, will be.

Common mistakes

  • Constructing Parser() in a loop.
  • Regex instead of the parser.
  • Assuming a successful parse means a valid variant. Use hgvs Validator.
  • Discarding parse failures instead of logging them.
  • Stripping the accession version during cleanup.
  • Catching bare Exception and masking real bugs. Catch HGVSParseError.
  • Rewriting semantics during cleanup.
  • Expecting round-tripping to normalize coordinates. It normalizes formatting only.

See also

SequenceVariant · HGVS Nomenclature · hgvs Normalizer · hgvs Validator · hgvs easy · String Accessor

lesson example

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