Relationships between B-cell clones from paired single-cell RNA and receptor sequencing
Threadfin is a Python package for paired single-cell RNA and B-cell receptor (BCR) sequencing. It uses the clone — the B cells that descend from one V(D)J rearrangement — as the unit of analysis. Each clone is represented by the distribution of transcriptional states among its sampled cells, estimated relative to the cells sampled alongside it and shrunk according to the number of cells captured. Clones are then compared with one another: in a clone embedding with one point per clone, in clonal programmes of clones that behave alike, and in clone-level tests of anything you measured. Threadfin works with AnnData and Scanpy, and defines clones with Dandelion.
The cells of one clone are distributed across states: germinal-centre (GC) B cells, plasmablasts, plasma cells and memory B cells. Two clones can have the same average state and still differ in how their cells are distributed. Selection, antigen affinity and fate bias act on clones, so questions such as which clones connect the germinal centre with plasma-cell output or does antigen binding go with a clone's state need the clone as the unit of representation and of statistical inference. Three features of repertoires sampled in vivo make this difficult, and Threadfin addresses each:
- Most clones are captured as one or a few cells. Profiles are shrunk according to the number of cells sampled, and each clone carries a reliability score. No clone is discarded for being small.
- Clone membership is confounded with donor, sample, tissue and sorting gate. Each clone is described relative to the cells sampled with it, and every test shuffles labels within the sampling design.
- Treating cells as independent inflates significance. Tests use clones, not cells, as replicates.
| Question | Output | Function |
|---|---|---|
| Is cell state inherited within clones? | clonal coherence: variance explained by clone, against labels shuffled within samples | tl.clonal_coherence |
| How are clones related to one another? | the clone embedding, laid out automatically so that continua stay connected | tl.embed_clones |
| Which clones behave alike? | clonal programmes, with split tests and bootstrap stability | tl.find_programmes |
| What explains the differences between clones? | variance in clone profiles explained by a label; per-programme effects | tl.profile_association, tl.association_test |
| Do clones keep their state over time or across tissues? | the clonal memory index | tl.clonal_memory |
| How far behind its germinal centre is a clone's output? | the export lag, in somatic mutations and in days | tl.export_lag |
The manuscript applies Threadfin to nine paired datasets from immunization, infection, vaccination and spontaneous germinal centres (figures); scripts and results for these and further public datasets are in case studies.
- Gut-associated lymphoid tissues (Siniscalco et al., Immunity 2025): the clone embedding resolves a continuous path of clones from GC B cells, through clones that contain both GC B cells and plasma cells, to plasma-cell clones, with clones that lack GC-fate labelling grouped at its end.
- Spontaneous germinal centres (Castrillon et al., eLife 2023): position along the clone embedding orders clones by the diversity of their cell states, and memory-containing clones concentrate at one end.
- Plasmodium infection (Skinner et al., Nat. Immunol. 2026): whether plasmablasts were sampled together with GC B cells of the same clone is a property of clones that cannot be read from the cell-level embedding.
- Model-antigen germinal centres (Merkenschlager et al., Nature 2025): clone profiles are associated with recent division, antigen binding and dark-zone sorting, none of which was used to construct them.
pip install "git+https://github.com/Reynold4k/Threadfin.git"Threadfin requires Python ≥ 3.10; Scanpy, Leiden, UMAP and Harmony are installed with it, and no GPU or R
is needed. To define clones from BCR contigs, also install Dandelion (pip install sc-dandelion), or
provide your own clone ids in adata.obs["clone_id"].
import scanpy as sc
import threadfin as tf
adata = sc.read_h5ad("b_cells.h5ad") # raw counts, one row per cell
result = tf.run(
adata,
bcr="filtered_contig_annotations.csv", # Cell Ranger VDJ output or an AIRR .tsv
donor_key="donor", # the individual each cell came from
sample_key="sample", # the sample or library each cell came from
)
print(result.summary()) # plain-language report
result.plot("threadfin_overview.pdf") # clone map, coherence, stability, compositiontf.run defines clones within each donor, builds an expression embedding without immunoglobulin genes,
estimates one profile per clone, tests clonal coherence, lays out the clone embedding, finds clonal
programmes and reports their stability. Add test=[...] to test your own labels and time_key= to ask
whether clones keep their state. No data at hand? tf.sim.simulate_repertoire() makes a repertoire with
a known answer (Tutorial 1).
UMAP draws continuous populations of clones as one picture only for suitable settings, and the suitable settings differ between datasets. Threadfin therefore chooses them for each dataset by default: it lays the clones out over a ladder of settings, scores each layout for coherence (the fraction of clones in one connected piece) and for fidelity to the clone profiles, and keeps the most faithful setting that holds together in every layout it tried. A map is not allowed to be more fragmented than the clone profiles themselves, and the final map is checked once more before it is returned. The record of the choice is stored with the results, and the settings can always be fixed by hand (Tutorial 3).
tf.tl.embed_clones(adata) # automatic (default)
tf.tl.embed_clones(adata, n_neighbors=30, min_dist=0.3) # by hand
tf.tl.map_quality(features, xy) # score any clone map (profiles, coordinates)- Clones are defined from paired heavy- and light-chain sequences within each donor (Dandelion).
- Clone profiles. Each cell is described relative to the mean of its sampling context. The cells of a clone are summarised by a kernel mean embedding of their state distribution, so a clone divided between two states differs from a clone in an intermediate state, and the profile is shrunk towards the context according to the number of cells (best linear unbiased prediction under a random-effects model).
- Clonal coherence is the intraclass correlation of state within clones, tested against clone labels permuted within samples.
- The clone embedding is a UMAP of the clone profiles with settings chosen as above.
- Clonal programmes are graph communities of clones; each split is tested against a single Gaussian group, and each programme's stability is estimated by bootstrap resampling.
- Clone-level tests quantify how much of the variance between clone profiles a label explains, with labels permuted among the clones of each donor.
The full description, with references, is in Methods.
| Tutorial 1 — Getting started | the whole analysis on simulated data, with its output |
| Tutorial 2 — Your own data | from Cell Ranger output to results, with several samples and donors |
| Tutorial 3 — The clone map | automatic and hand-set layouts, and how to check a map |
| Tutorial 4 — Step by step | the individual functions in threadfin.tl |
| Methods | the statistical model behind each step |
| Case studies | scripts and results for the public datasets |
| Figures | figures of the manuscript, with their source code |
| Changelog | what changed in each version |
A manuscript describing Threadfin is in preparation. Until it is available, please cite this repository (CITATION.cff) and the tools Threadfin builds on: Dandelion (Suo et al., Nat. Biotechnol. 2024) for clone definition, Scanpy (Wolf et al., Genome Biol. 2018) and UMAP (McInnes et al., J. Open Source Softw. 2018).
- Dandelion — single-cell immune receptor analysis; Threadfin's clone definition.
- Scirpy — single-cell immune receptor analysis in the scverse ecosystem.
- Scanpy and AnnData — the data structures and single-cell tools Threadfin works with.
- Immcantation — B-cell receptor repertoire analysis, including clonal lineage reconstruction.
Please report bugs and ask questions through GitHub issues.
MIT; see LICENSE.


