Gene-expression signatures¶
TCRsift has two related registries:
T_CELL_SIGNATURES: compact functional programs used for exploratory per-cell/per-clone scoring and TIL selection.NEOANTIGEN_SIGNATURES: published neoantigen-reactivity signatures with their own scoring methods, input units, and citations.
Scores describe cell state. They do not establish which antigen a TCR binds. Interpret the same program in its source compartment: AIM is most useful after in-vitro stimulation, while tumor-reactive/exhaustion programs are most useful in fresh TIL.
Compact functional programs¶
All symbols are HGNC human gene names.
| Name | Genes / contrast | Intended use |
|---|---|---|
effector |
IFNG, GZMB, PRF1, GNLY, NKG7 | Cytotoxic-effector state |
activation |
Deprecated alias of effector |
Backward compatibility only |
naive_stem |
TCF7, LEF1, CCR7, SELL, IL7R, CD27, CD28 | Naïve/stem-memory state |
antigen_response |
TNFRSF9, MKI67 | Focal recent-response/proliferation readout |
aim |
TNFRSF9, TNFRSF4, IL2RA, MKI67 | Activation-induced-marker program in stimulated culture |
cytolytic |
PRF1, GZMB | Minimal cytolytic readout |
exhaustion |
PDCD1, TOX, LAG3, HAVCR2, TIGIT | Chronic-antigen/exhaustion state in TIL |
tumor_reactive |
CXCL13, ENTPD1 | Compact tumor-reactive TIL phenotype |
expansion_core |
MKI67, TNFRSF9, EGR2, IFNG, CXCL13, HAVCR2 | Cross-compartment expansion-associated panel |
MARKER_PANEL_HGNC is the union extracted by til-select; it is a display and
scoring panel, not another biological signature.
Published neoantigen-reactivity registry¶
score_by_name dispatches each entry according to its registered method
instead of treating unlike signatures as interchangeable weighted sums.
| Name | Source | TCRsift implementation |
|---|---|---|
MANAscore |
Zeng/Smith, Nature Communications 2025 (PMID 39900903) | Transparent signed-z proxy: (z(CXCL13) + z(ENTPD1) - z(IL7R)) / sqrt(3). The paper's trained RF+linear ensemble is not shipped and this proxy must not be described as the original fitted model. |
NeoTCR8 |
Lowery/Rosenberg, Science 2022 (PMID 35113651) | Published 243-gene CD8 set, unweighted gene-set enrichment |
NeoTCR4 |
Lowery/Rosenberg, Science 2022 (PMID 35113651) | Published 40-gene CD4 set, unweighted gene-set enrichment |
NeoTCR_PBL |
Yossef/Rosenberg, Cancer Cell 2023 (PMID 38039963) | Published 151-gene circulating CD8 set, unweighted gene-set enrichment; use for blood, not as the default TIL signature |
For NeoTCR4, NeoTCR8, and NeoTCR_PBL, pass an AnnData object containing
the full log-normalized gene universe. A bare DataFrame cannot reproduce
rank-enrichment controls, so TCRsift warns and falls back to a mean-z proxy.
Usage¶
Compact unsigned programs:
from tcrsift import compute_signature_scores_per_cell
compute_signature_scores_per_cell(adata)
print([column for column in adata.obs if column.startswith("signature_")])
Published/signed methods:
import scanpy as sc
from tcrsift import score_by_name
sc.pp.normalize_total(adata, target_sum=10_000)
sc.pp.log1p(adata)
for name in ("MANAscore", "NeoTCR8", "NeoTCR4"):
# X is already log1p(CP10K), so do not log it again.
score_by_name(adata, name, log1p=False, key_added=f"signature_{name}")
For multiple samples, score within each sample or donor before aggregating clones; otherwise sample-level expression shifts can dominate the result. See Multi-sample TIL prioritization for a complete example.
Cell-type and state registries¶
The per-cell annotator (tcrsift.annotate_cells) also exposes:
| Constant | What it contains |
|---|---|
CELL_TYPE_SIGNATURES |
Blood/PBMC-oriented default cell-type registry |
PBMC_CELL_TYPE_SIGNATURES |
Explicit alias for that default |
SOLID_TUMOR_CELL_TYPE_SIGNATURES |
Default plus solid-tissue lineages |
T_STATE_SIGNATURES, B_STATE_SIGNATURES |
T- and B-cell sub-state registries |
PBMC_CULTURE_TYPES |
Type allow-list for PBMC/culture annotation |
Malignant-cell calls are handled by MarkerCountOverride/tumor_override,
not by adding a “malignant” expression signature to this registry.
signatures ¶
Canonical T-cell gene-set signatures.
Single source of truth for the gene sets used across TIL selection
(til_select.py) and the per-sample signature scatter
(plots.py). Pulled out of til_select.py so they can be reused
for selecting other kinds of T cells — antigen-response screens,
exhaustion-state phenotyping, healthy-donor panels — without inheriting
the TIL-specific scoring code.
All gene symbols are HGNC (human). When working with Ensembl IDs,
translate via the 10x features.tsv.gz mapping.
Signatures grouped by intent:
- :data:
EFFECTOR_GENES_HGNC— cytotoxic-effector panel (IFNG, GZMB, PRF1, GNLY, NKG7). Used in cytotoxicity readouts.ACTIVATION_GENES_HGNCis a deprecated alias (#142): this set is effector differentiation, not immediate-early activation (CD69, NR4A1-3, EGR1/2, FOS, JUN) — the old name invited that conflation. - :data:
NAIVE_STEM_GENES_HGNC— naïve / stem-memory program (TCF7, LEF1, CCR7, SELL, IL7R, CD27, CD28). The "down" pole of the effector−naïve differentiation contrast (#141), the best axis for separating antigen-expanded clones from naïve-like bystanders. - :data:
ANTIGEN_RESPONSE_GENES_HGNC— focal 2-gene recent-antigen marker: TNFRSF9 (4-1BB / CD137, the AIM-assay marker per Wölfl 2007, Frentsch 2005, Bacher 2013) + MKI67 (Ki-67, proliferation). Kept as the back-compat subset of :data:AIM_GENES_HGNC. Per the expansion-correlation analysis (#303), both members positively track culture clonal expansion (TNFRSF9 ρ≈0.28, MKI67 ρ≈0.46) despite TNFRSF9's sparsity — sparse-but-specific, not a reason to demote it. - :data:
AIM_GENES_HGNC— activation-induced-marker panel for the in-vitro restimulation / antigen-response axis: TNFRSF9 (4-1BB), TNFRSF4 (OX40), IL2RA (CD25) + MKI67 (proliferation). These track culture expansion but sit near zero in TIL (#303), so this is the culture-compartment activation readout, not an in-vivo state. - :data:
CYTOLYTIC_GENES_HGNC— minimal canonical cytotoxic effector pair (PRF1 perforin, GZMB granzyme B). Used as the effector readout in Caushi 2021, Krishna 2021, Hanada 2022. Tracks TIL expansion (ρ≈0.29–0.34) but is flat/negative in culture (#303). - :data:
EXHAUSTION_GENES_HGNC— canonical co-inhibitory / chronic-antigen exhausted-T-cell set (PDCD1, TOX, LAG3, HAVCR2, TIGIT). CTLA4 was dropped (#303): it is an activation/Treg marker, not specific to the exhausted state. Every member positively tracks TIL expansion in the H37 tumor — the expanded intratumoral clones are the chronically-stimulated ones. Residency/tumor-reactive extension: ENTPD1 (CD39), CXCL13, ITGAE (CD103). - :data:
TUMOR_REACTIVE_GENES_HGNC— TIL-resident tumor-reactive phenotype: CXCL13 (Workel 2019, Cohen 2022, Veatch 2022) + ENTPD1 / CD39 (Duhen 2018, Simoni 2018, Thommen 2018). - :data:
EXPANSION_CORE_GENES_HGNC— cross-compartment expansion core (MKI67, TNFRSF9, EGR2, IFNG, CXCL13, HAVCR2): the markers whose clone-level expression tracks expansion in both the peptide-culture and TIL compartments (#303), so the one panel usable to score "expanding & antigen-engaged" regardless of compartment. - :data:
MARKER_PANEL_HGNC— the default per-clone GEX scoring panel. Widened (#303) to the union of the functional panels above (plus CD4/CD8 lineage) so every panel is computable from a single extraction pass. Not a single-intent signature (so it is intentionally absent from :data:T_CELL_SIGNATURES); it is the union display panel shown per clone.til_selectre-exports it asMARKER_GENES_DEFAULTfor back-compat.
Compartment note (#303): the panels mean different things by compartment. AIM markers track in-vitro culture expansion; cytolytic and exhaustion track in-vivo TIL state/expansion; the expansion core tracks both. Detectability (% of cells expressing) is not the right yardstick — CD69 is the most detectable activation marker yet anti-correlates with expansion, while sparse TNFRSF9 works.
:data:T_CELL_SIGNATURES is a snake-case name → tuple dict for
convenient iteration.
EFFECTOR_GENES_HGNC
module-attribute
¶
ACTIVATION_GENES_HGNC
module-attribute
¶
NAIVE_STEM_GENES_HGNC
module-attribute
¶
ANTIGEN_RESPONSE_GENES_HGNC
module-attribute
¶
AIM_GENES_HGNC
module-attribute
¶
EXHAUSTION_GENES_HGNC
module-attribute
¶
TUMOR_REACTIVE_GENES_HGNC
module-attribute
¶
EXPANSION_CORE_GENES_HGNC
module-attribute
¶
EXPANSION_CORE_GENES_HGNC: tuple[str, ...] = ('MKI67', 'TNFRSF9', 'EGR2', 'IFNG', 'CXCL13', 'HAVCR2')
MARKER_PANEL_HGNC
module-attribute
¶
MARKER_PANEL_HGNC: tuple[str, ...] = ('CD4', 'CD8A', 'CD8B', 'MKI67', 'TNFRSF9', 'TNFRSF4', 'IL2RA', 'TNFRSF18', 'NR4A1', 'NR4A2', 'EGR2', 'CTLA4', 'PRF1', 'GZMB', 'GZMK', 'NKG7', 'GNLY', 'IFNG', 'PDCD1', 'TOX', 'LAG3', 'HAVCR2', 'TIGIT', 'ENTPD1', 'CXCL13', 'ITGAE')
T_CELL_SIGNATURES
module-attribute
¶
T_CELL_SIGNATURES: dict[str, tuple[str, ...]] = {'effector': EFFECTOR_GENES_HGNC, 'activation': ACTIVATION_GENES_HGNC, 'naive_stem': NAIVE_STEM_GENES_HGNC, 'antigen_response': ANTIGEN_RESPONSE_GENES_HGNC, 'aim': AIM_GENES_HGNC, 'cytolytic': CYTOLYTIC_GENES_HGNC, 'exhaustion': EXHAUSTION_GENES_HGNC, 'tumor_reactive': TUMOR_REACTIVE_GENES_HGNC, 'expansion_core': EXPANSION_CORE_GENES_HGNC}
signature_methods ¶
Gene-expression signatures as synthetic selection methods.
The selection language (:mod:tcrsift.selection) is method-agnostic — a
"method" is just a value in the per-(clone, sample) table carrying a
tier + frequency. A method need not be a physical sort: this module scores
cells on a gene set, calls signature-positive cells, and emits them as a
synthetic method so the rules can use a signature like a sort.
Signatures are signed (genes_up − genes_down, so loss-of-naive
axes work). Canonical scoring (defaults): per gene, the z-score across
cells of log(1 + TPM), then the mean of those z-scores across the
signature (combine="mean" instead averages the values directly).
Small "focal" and larger "broad" panels are offered per axis.
NOTE: a signature's meaning still depends on the sample source — e.g.
TumorReactive (CD39/CD103/TOX) only reads "chronic in-situ antigen
exposure" in fresh tissue TILs, not in PBMC culture. That caveat lives in
each signature's description; it is documentation, not an enforced
guard, so you can still score any signature on any data (and should, when
exploring). Gene memberships are literature-backed defaults for tuning.
SIGNATURES
module-attribute
¶
SIGNATURES: dict[str, Signature] = {(s.name): s for s in (_INVARIANT_SIGNATURES + _COMPOSITE_SIGNATURES)}
NEOANTIGEN_SIGNATURES
module-attribute
¶
NEOANTIGEN_SIGNATURES: dict[str, Signature] = {'MANAscore': Signature('MANAscore', MANASCORE_UP_HGNC, MANASCORE_DOWN_HGNC, panel='focal', units='log1p', method='weighted_z', citation='Zeng/Smith, Nat Commun 2025 (PMID 39900903)', description='Transparent signed-z proxy for MANAscore: (z(CXCL13)+z(ENTPD1)-z(IL7R))/sqrt(3) on log1p CP10K. The published model is a trained RF+linear ensemble with NO closed-form per-gene weights; only the gene directions (+CXCL13,+ENTPD1,-IL7R) and input units (log-normalized) are reproducible, and that is what this scores. The faithful pickled ensemble is deliberately NOT shipped (deserializing a third-party pickle is an arbitrary-code-execution risk).'), 'NeoTCR8': Signature('NeoTCR8', NEOTCR8_GENES_HGNC, panel='broad', units='ranks', method='geneset_enrichment', citation='Lowery/Rosenberg, Science 2022 (PMID 35113651)', description="243-gene CD8 neoantigen-reactive set (Table S10). An UNWEIGHTED gene set scored by rank enrichment (scGSEA/score_genes) — 'per-gene weight' is not a meaningful question for it."), 'NeoTCR4': Signature('NeoTCR4', NEOTCR4_GENES_HGNC, panel='broad', units='ranks', method='geneset_enrichment', citation='Lowery/Rosenberg, Science 2022 (PMID 35113651)', description='40-gene CD4 neoantigen-reactive set (Table S10). An UNWEIGHTED gene set scored by rank enrichment (scGSEA/score_genes).'), 'NeoTCR_PBL': Signature('NeoTCR_PBL', NEOTCRPBL_GENES_HGNC, panel='broad', units='ranks', method='geneset_enrichment', citation='Yossef/Rosenberg, Cancer Cell 2023 (PMID 38039963)', description="151-gene signature of circulating (peripheral-blood) neoantigen-reactive CD8 T cells (cluster C9, avg_log2FC>=0.5; Table S2D). An UNWEIGHTED gene set scored by rank enrichment. Distinct from Lowery's TIL-derived NeoTCR8/4 — a separate blood-derived signature.")}
Signature
dataclass
¶
A named, optionally-signed gene signature.
genes_down subtracts (loss-of-naive etc.). panel is a
"focal"/"broad" label for the small-vs-large variants. description
carries the biological caveat (e.g. which sample source it's meaningful
in) as prose.
units, method and citation (#309) record how a published
signature is meant to be scored, so heterogeneous signatures
(MANAscore-style signed-z proxies vs. unweighted rank-enrichment gene
sets) can live in one registry and be dispatched correctly by
:func:score_by_name rather than all being treated as weighted sums:
units— the input space the score is defined on:"log1p"(log1p CP10K, the default),"cp10k","scaled"or"ranks".method— the scoring rule:"zscore"(mean of per-gene z-scores, the default and what :func:score_signaturecomputes),"weighted_z"(signed-sum of per-gene z-scores /sqrt(n)— the transparent MANAscore proxy), or"geneset_enrichment"(rank-based set score for unweighted published sets like NeoTCR4/8).citation— the source paper (free text, incl. PMID).
Defaults keep every pre-existing signature a plain zscore /
log1p signature, so this is backward compatible.
Source code in tcrsift/signature_methods.py
score_by_name ¶
score_by_name(data, name, *, registry: dict[str, Signature] | None = None, log1p: bool | None = None, background: DataFrame | None = None, groups: Series | None = None, on_missing: str = 'warn', layer: str | None = None, key_added: str | None = None) -> pd.Series
Score a registered signature on cells, dispatched by its method (#309).
data is either an AnnData (per-cell scores from .X / layer,
symbol resolution via the shared resolver) or a cells × genes symbol-keyed
DataFrame. name is a registry key (case-insensitive) or a
:class:Signature. Routing:
weighted_z→ :func:score_weighted_z(MANAscore-style signed-z),zscore/mean→ :func:score_signature,geneset_enrichment→ scanpyscore_geneson an AnnData (faithful rank enrichment), or a mean-z proxy on a bare frame.
log1p defaults from the signature's units (applied for
log1p/cp10k inputs, skipped for scaled/ranks); pass a bool
to override. key_added (AnnData only) writes the score to
adata.obs[key_added] in addition to returning it.