Skip to content

Serotype expansion for prediction (expand serotype name → member alleles, pool affinities) #293

Description

@iskandr

Summary

Add a preprocessing layer that expands a serotype name (e.g. A2, HLA-B7) into its member MHC class I alleles, so users can request a prediction for a serotype and get a pooled answer across member alleles. Today mhcflurry has no serotype support beyond collapse_singleton_serotypes=True passed to mhcgnomes.parse; a serotype name that maps to more than one allele fails to parse and is rejected.

Motivation

Users (and IEDB-style inputs) sometimes specify alleles only at serotype resolution. There's no way to ask mhcflurry "what does serotype A2 present?" without manually enumerating member alleles.

Note: this is prediction-only value — it does not recover training data (see the sibling "serotypes in training" issue; serotype rows are ≈0% of the curated training set).

Feasibility (already scoped)

mhcgnomes does the hard part:

  • mhcgnomes.parse("A2") / parse("HLA-A2")mhcgnomes.Serotype with a .alleles tuple of member Allele objects. [a.to_string() for a in st.alleles] yields strings mhcflurry's canonicalize_allele_name already consumes.
  • Full enumerable table: mhcgnomes.data.serotypes (170 HLA names; 122 class I). parse(..., only_class1=True) filters class II to None.

Critical gotcha: normalize_allele_name (mhcflurry/common.py) restricts results to [Allele, AlleleWithoutGene, Gene], so parse("A2") returns None and normalize_allele_name("A2") raises today. A serotype expander must run before normalization, not inside it.

Proposed approach

  • New utility expand_serotype(name) -> list[str] in mhcflurry/common.py (next to normalize_allele_name), wrapping parse(name, only_class1=True) and returning member allele strings when the result is a Serotype, else falling through to single-allele behavior.
  • Wire into mhcflurry/predict_command.py _split_allele_string (≈ line 196), so a serotype cell expands into the existing genotype min-affinity pooling path (predict already reports the tightest-nM binder across an allele set, with optional best_allele column).
  • Do not expand inside Class1AffinityPredictor.predict_to_dataframe — it requires alleles length-aligned with peptides, and expansion changes row cardinality.

Open design questions

  • Pooling: default to min affinity (tightest binder = "presented by any member"); offer mean/max as options.
  • Percentile ranks: ranks aren't directly comparable across alleles — pool on raw nM, then optionally re-rank. (The one genuinely subtle modeling detail.)
  • Unsupported members: drop/NaN members not in predictor.supported_alleles.
  • Non-serotype coarse labels: gate explicitly — "HLA class I" doesn't parse to a serotype; "HLA-A" parses to a Gene (a much larger, different expansion); A2=47 members, Bw4=36, so cap/dedup to bound cost.

Effort

Small for the predict path (expander + wiring + tests) — the serotype data, class-I filtering, and min-pooling all already exist.

Key files

  • mhcflurry/common.py (normalize_allele_name, new expand_serotype)
  • mhcflurry/predict_command.py (_split_allele_string)
  • mhcflurry/class1_affinity_predictor.py (canonicalize_allele_name, predict_to_dataframe)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions