Getting started#

A first pass through mirpy in about fifteen minutes: install it, embed receptors, denoise the embedding, and measure the distance between two groups of repertoires. Everything here runs offline against the prototype tables that ship in the wheel, so there is nothing to download.

If you already know what you want, the guides are organised one task per page, Command reference lists every command, and Mathematical foundations explains why the embedding is valid.

Install#

pip install mirpy-lib
mir --help

Pure-Python wheel. The alignment, Pgen and sampling work is reused from seqtree and vdjtools, which install with it.

Step 1 – receptors to work on#

Normally you would read your own file, in any format vdjtools understands:

from vdjtools import io as vio

sample = vio.read("clones.tsv")     # AIRR, MiXcr, immunoSEQ, Parquet, native -- detected

So that this page runs offline, use the bundled prototype table instead. It is 10,000 real, unique, productive human TRB receptors, which is a perfectly good set of clonotypes to embed:

from mir.embedding.prototypes import load_prototypes

pool = load_prototypes("human", "TRB")
pool.columns      # ['v_call', 'j_call', 'junction_aa']
pool.height       # 10000

Note

Rows 0 to 999 of this table are the default prototype set at n_prototypes=1000. Embedding a receptor against itself gives distance 0, which would flatter any result, so the examples below take clonotypes from row 5000 onward – genuinely held out from the coordinate system they are measured in.

Step 2 – receptors to vectors#

from mir.embedding.tcremp import TCREmp

model  = TCREmp.from_defaults("human", "TRB", n_prototypes=1000)
sample = pool.slice(5000, 400)          # 400 held-out clonotypes
X = model.embed(sample)
X.shape                                 # (400, 3000)

3,000 columns for 1,000 prototypes, because each receptor contributes three distances per prototype: one for V, one for J, and one for the junction. The junction distance is computed through seqtree.gapblock; the V and J distances are precomputed from the arda germline, so they cost a lookup rather than an alignment.

Leaving n_prototypes out uses the per-chain preset instead, which is what you want unless you are deliberately testing sensitivity – 2,000 prototypes for human TRB, so 6,000 columns.

Step 3 – denoise#

Three thousand columns are correlated by construction: nearby prototypes measure nearly the same thing. PCA to the preset’s dimension keeps the geometry and drops the redundancy:

from mir.embedding.pca import pca_denoise
from mir.embedding.presets import get_preset

preset = get_preset("human", "TRB")     # n_prototypes=2000, n_components=65, recon=260
Xd = pca_denoise(X, n_components=65)
Xd.shape                                # (400, 65)

Use the 95-percent-variance dimension (n_components) for clustering and visualisation, and the 99-percent one (n_components_recon) when reconstructing sequences with a neural codec, where losing sequence detail matters. See Which prototypes? for the full table and for how to test whether a result depends on which prototypes you drew.

Step 4 – repertoires, not receptors#

To compare donors rather than receptors, each repertoire needs its own single vector. Build two groups that genuinely differ – here, repertoires of short junctions against repertoires of long ones – so that the distance has something to find:

import numpy as np
import polars as pl
from mir.repertoire import fit_repertoire_space, sample_embedding, mmd_matrix

pool = pool.with_columns(pl.col("junction_aa").str.len_chars().alias("L"))

def donor(expr, n, seed):
    """One synthetic repertoire: n clonotypes matching expr, with random clone sizes."""
    rng = np.random.default_rng(seed)
    d = pool.filter(expr).sample(n, seed=seed)
    return d.with_columns(pl.Series("duplicate_count", rng.integers(1, 50, n)))

samples = ([donor(pl.col("L") <= 13, 300, s) for s in (1, 2)] +     # two short-junction donors
           [donor(pl.col("L") >= 17, 300, s) for s in (3, 4)])      # two long-junction donors

space = fit_repertoire_space(model, pl.concat(samples))   # ONE basis for the whole cohort
embs  = [sample_embedding(space, s) for s in samples]
mmd_matrix(embs, unbiased=True)
       short1  short2   long1   long2
short1  0.0000  0.0000  0.0410  0.0430
short2  0.0000  0.0000  0.0542  0.0509
long1   0.0410  0.0542  0.0000  0.0000
long2   0.0430  0.0509  0.0000  0.0000

Two donors of the same kind are at distance 0 and two of different kinds are 0.041 to 0.054 apart. Exact zeros within a group are the right answer rather than a rounding artefact: the unbiased estimator removes the self-similarity term analytically, so for two samples drawn from the same distribution its estimate is zero up to noise, and a small negative estimate is clamped to zero.

Important

fit_repertoire_space must be fitted once, on the pooled cohort, and every sample embedded through that one basis. Two samples embedded through different bases – or through different prototype replicates – are not comparable, and every container here refuses a prototype-hash mismatch rather than mixing them silently.

Pass unbiased=True whenever your samples differ in depth or diversity. The biased estimator carries a positive self-term of about 1/n_eff, which inflates distances for low-diversity samples – so if diversity is itself what you are studying, the bias shows up as signal with the wrong sign.

Step 5 – the same thing from the shell#

# one repertoire -> per-clonotype embedding table, the input to clustering and ML
mir embed clonotypes sample.tsv --pca 50 -o clonotypes.parquet

# a set of repertoires -> one fingerprint per sample on a shared basis, plus the MMD matrix
mir embed repertoires cohort/*.tsv.gz -o phi.tsv --mmd mmd.tsv

Sample ids default to the filename stem and the locus is inferred per file. With several loci the MMD matrix is written per chain, as mmd.TRB.tsv, mmd.TRA.tsv and so on. See Command reference.

Step 6 – one named vector per sample#

Phi(S) is a coordinate system fitted on your cohort, which is what makes it powerful and also what makes it incomparable with anyone else’s. When you need a vector that a collaborator can reproduce independently, use a Repertoire signatures instead: fixed, named columns standardised against a published reference corpus.

mir signature --corpus blood cohort/*.tsv.gz -o rsig.tsv

That emits the geometry half. The statistics half comes from vdjtools, and the two join on sample_id:

vdjtools signature --corpus blood cohort/*.tsv.gz -o vsig.tsv

Where to go next#

Next

Page

Cluster antigen-specific receptors, and find enriched neighbourhoods

User guide

What must be filtered before embedding, and why it is not optional

Data pre-processing

Portable, named, standardised columns

Repertoire signatures | Channels

Why the prototype embedding is a valid coordinate system

Mathematical foundations

Every command and flag

Command reference

A term you did not recognise

Glossary