tcri 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tcri-0.9.0/LICENSE +21 -0
- tcri-0.9.0/PKG-INFO +206 -0
- tcri-0.9.0/README.md +161 -0
- tcri-0.9.0/pyproject.toml +79 -0
- tcri-0.9.0/setup.cfg +4 -0
- tcri-0.9.0/tcri/__init__.py +33 -0
- tcri-0.9.0/tcri/_compute/__init__.py +15 -0
- tcri-0.9.0/tcri/_compute/_distance.py +60 -0
- tcri-0.9.0/tcri/_compute/_joint.py +189 -0
- tcri-0.9.0/tcri/_compute/_tables.py +410 -0
- tcri-0.9.0/tcri/_compute/_xp.py +62 -0
- tcri-0.9.0/tcri/_state/__init__.py +6 -0
- tcri-0.9.0/tcri/_state/keys.py +95 -0
- tcri-0.9.0/tcri/_state/schemas.py +108 -0
- tcri-0.9.0/tcri/_state/storage.py +277 -0
- tcri-0.9.0/tcri/_stats/__init__.py +23 -0
- tcri-0.9.0/tcri/_stats/_compare.py +90 -0
- tcri-0.9.0/tcri/_stats/_core.py +135 -0
- tcri-0.9.0/tcri/datasets/__init__.py +20 -0
- tcri-0.9.0/tcri/datasets/_simulate.py +648 -0
- tcri-0.9.0/tcri/diagnostics/__init__.py +19 -0
- tcri-0.9.0/tcri/diagnostics/_ppc.py +259 -0
- tcri-0.9.0/tcri/diagnostics/_training.py +78 -0
- tcri-0.9.0/tcri/get.py +142 -0
- tcri-0.9.0/tcri/model/__init__.py +4 -0
- tcri-0.9.0/tcri/model/_callbacks.py +148 -0
- tcri-0.9.0/tcri/model/_classifier.py +23 -0
- tcri-0.9.0/tcri/model/_model.py +738 -0
- tcri-0.9.0/tcri/model/_module.py +424 -0
- tcri-0.9.0/tcri/model/_priors.py +149 -0
- tcri-0.9.0/tcri/model/_training.py +272 -0
- tcri-0.9.0/tcri/plotting/__init__.py +24 -0
- tcri-0.9.0/tcri/plotting/_base.py +447 -0
- tcri-0.9.0/tcri/plotting/_colors.py +95 -0
- tcri-0.9.0/tcri/plotting/_delta.py +56 -0
- tcri-0.9.0/tcri/plotting/_entropy.py +30 -0
- tcri-0.9.0/tcri/plotting/_flux.py +28 -0
- tcri-0.9.0/tcri/plotting/_mutual_information.py +19 -0
- tcri-0.9.0/tcri/plotting/_sankey.py +46 -0
- tcri-0.9.0/tcri/preprocessing/__init__.py +4 -0
- tcri-0.9.0/tcri/preprocessing/_preprocessing.py +61 -0
- tcri-0.9.0/tcri/tools/__init__.py +27 -0
- tcri-0.9.0/tcri/tools/_delta.py +171 -0
- tcri-0.9.0/tcri/tools/_entropy.py +126 -0
- tcri-0.9.0/tcri/tools/_flux.py +118 -0
- tcri-0.9.0/tcri/tools/_joint.py +225 -0
- tcri-0.9.0/tcri/tools/_mutual_information.py +82 -0
- tcri-0.9.0/tcri/utils/__init__.py +10 -0
- tcri-0.9.0/tcri/utils/_utils.py +401 -0
- tcri-0.9.0/tcri.egg-info/PKG-INFO +206 -0
- tcri-0.9.0/tcri.egg-info/SOURCES.txt +72 -0
- tcri-0.9.0/tcri.egg-info/dependency_links.txt +1 -0
- tcri-0.9.0/tcri.egg-info/requires.txt +23 -0
- tcri-0.9.0/tcri.egg-info/top_level.txt +1 -0
- tcri-0.9.0/tests/test_contract_conformance.py +232 -0
- tcri-0.9.0/tests/test_guide_concentration.py +139 -0
- tcri-0.9.0/tests/test_helpers.py +203 -0
- tcri-0.9.0/tests/test_layout.py +204 -0
- tcri-0.9.0/tests/test_metrics_contract_conformance.py +388 -0
- tcri-0.9.0/tests/test_model_classifier.py +130 -0
- tcri-0.9.0/tests/test_model_contract_conformance.py +425 -0
- tcri-0.9.0/tests/test_model_determinism.py +79 -0
- tcri-0.9.0/tests/test_model_guardrails.py +208 -0
- tcri-0.9.0/tests/test_model_knobs.py +399 -0
- tcri-0.9.0/tests/test_model_setup.py +131 -0
- tcri-0.9.0/tests/test_model_smoke.py +65 -0
- tcri-0.9.0/tests/test_pyro_params.py +52 -0
- tcri-0.9.0/tests/test_real_data_yost.py +200 -0
- tcri-0.9.0/tests/test_recovery.py +314 -0
- tcri-0.9.0/tests/test_removal_ledger.py +130 -0
- tcri-0.9.0/tests/test_session_round_trip.py +137 -0
- tcri-0.9.0/tests/test_shared_defaults.py +81 -0
- tcri-0.9.0/tests/test_training_contract_conformance.py +162 -0
- tcri-0.9.0/tests/test_training_invariants.py +412 -0
tcri-0.9.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2026 Memorial Sloan Kettering Cancer Center
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tcri-0.9.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tcri
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Information theoretic metrics for single cell RNA and TCR sequencing.
|
|
5
|
+
Author-email: Nicholas Ceglia <nickceglia@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nceglia/tcri
|
|
8
|
+
Project-URL: Repository, https://github.com/nceglia/tcri
|
|
9
|
+
Project-URL: Issues, https://github.com/nceglia/tcri/issues
|
|
10
|
+
Project-URL: Documentation, https://tcri.readthedocs.io
|
|
11
|
+
Keywords: single-cell,TCR,scRNA-seq,immunology,information-theory,bayesian
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.26.1
|
|
24
|
+
Requires-Dist: pandas>=2.2.2
|
|
25
|
+
Requires-Dist: scipy>=1.14.1
|
|
26
|
+
Requires-Dist: scikit-learn>=1.1
|
|
27
|
+
Requires-Dist: anndata>=0.11.3
|
|
28
|
+
Requires-Dist: scanpy>=1.9.5
|
|
29
|
+
Requires-Dist: torch>=2.4.1
|
|
30
|
+
Requires-Dist: pyro-ppl>=1.9.1
|
|
31
|
+
Requires-Dist: scvi-tools>=1.3.0
|
|
32
|
+
Requires-Dist: umap-learn>=0.5.6
|
|
33
|
+
Requires-Dist: matplotlib>=3.9.2
|
|
34
|
+
Requires-Dist: seaborn>=0.13.2
|
|
35
|
+
Requires-Dist: tqdm>=4.66.5
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
38
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
39
|
+
Provides-Extra: notebook
|
|
40
|
+
Requires-Dist: jupytext>=1.16; extra == "notebook"
|
|
41
|
+
Requires-Dist: nbclient>=0.10; extra == "notebook"
|
|
42
|
+
Requires-Dist: nbformat>=5.10; extra == "notebook"
|
|
43
|
+
Requires-Dist: ipykernel>=6; extra == "notebook"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<a href="https://tcri.readthedocs.io">
|
|
48
|
+
<img src="https://raw.githubusercontent.com/nceglia/tcri/main/docs/images/tcri_logo_hero.png" alt="TCRi" width="420">
|
|
49
|
+
</a>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<p align="center">
|
|
53
|
+
<b>Information-theoretic analysis of paired single-cell RNA + TCR sequencing.</b>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p align="center">
|
|
57
|
+
<a href="https://tcri.readthedocs.io">Documentation</a> ·
|
|
58
|
+
<a href="https://tcri.readthedocs.io/en/latest/tutorials/index.html">Tutorials</a> ·
|
|
59
|
+
<a href="https://www.biorxiv.org/content/10.1101/2022.10.01.510457v1">Paper</a> ·
|
|
60
|
+
<a href="https://github.com/nceglia/tcri/issues">Issues</a>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<p align="center">
|
|
64
|
+
<a href="https://github.com/nceglia/tcri/actions/workflows/tests.yml"><img src="https://github.com/nceglia/tcri/actions/workflows/tests.yml/badge.svg" alt="tests"></a>
|
|
65
|
+
<a href="https://tcri.readthedocs.io"><img src="https://readthedocs.org/projects/tcri/badge/?version=latest" alt="docs"></a>
|
|
66
|
+
<a href="https://github.com/nceglia/tcri/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license: MIT"></a>
|
|
67
|
+
<img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="python 3.10+">
|
|
68
|
+
<img src="https://img.shields.io/badge/built%20on-scverse-%231f9e16.svg" alt="built on scverse">
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
TCRi quantifies how **T-cell clonotypes** relate to **transcriptional phenotypes** in
|
|
74
|
+
paired single-cell data. It fits a hierarchical Bayesian model (on top of
|
|
75
|
+
[scvi-tools](https://scvi-tools.org) / [Pyro](https://pyro.ai)) that ties each clone's
|
|
76
|
+
phenotype distribution to gene expression, then reads out **information-theoretic
|
|
77
|
+
summaries** — clonotypic and phenotypic entropy, clone↔phenotype mutual information, and
|
|
78
|
+
phenotypic flux across conditions — over the learned clone × phenotype joint distribution.
|
|
79
|
+
|
|
80
|
+
It is built on the [scverse](https://scverse.org) stack
|
|
81
|
+
([AnnData](https://anndata.readthedocs.io), [scanpy](https://scanpy.readthedocs.io),
|
|
82
|
+
scvi-tools) and works on a standard `AnnData` carrying both a clonotype label and a
|
|
83
|
+
gene-expression matrix.
|
|
84
|
+
|
|
85
|
+
## Key capabilities
|
|
86
|
+
|
|
87
|
+
- **Joint model of expression + repertoire** — a hierarchical VAE (`TCRIModel`) that
|
|
88
|
+
learns each clone's phenotype distribution while reconstructing counts with a ZINB
|
|
89
|
+
decoder.
|
|
90
|
+
- **Information-theoretic metrics** — clonotypic entropy `H(c|φ)`, phenotypic entropy
|
|
91
|
+
`H(φ|c)`, normalized mutual information `I(c;φ)`, and phenotypic flux between conditions,
|
|
92
|
+
all in bits over the posterior joint.
|
|
93
|
+
- **Uncertainty-aware** — every metric can be reported as a posterior mean ± HDI by
|
|
94
|
+
drawing from the fitted Dirichlet posterior (`n_samples > 0`).
|
|
95
|
+
- **Group comparisons** — paired/unpaired contrasts across cohorts (e.g. responders vs
|
|
96
|
+
non-responders) with direction probabilities.
|
|
97
|
+
- **Diagnostics** — posterior-predictive checks, phenotype calibration, and permutation
|
|
98
|
+
nulls for the metrics.
|
|
99
|
+
- **Contract-governed** — the public API, the generative model, the metric definitions,
|
|
100
|
+
and the training plan are each frozen by a machine-checked contract.
|
|
101
|
+
|
|
102
|
+
## Installation
|
|
103
|
+
|
|
104
|
+
TCRi targets **Python ≥ 3.10**. Install from source:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/nceglia/tcri.git
|
|
108
|
+
cd tcri
|
|
109
|
+
pip install .
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The heavy scientific stack (PyTorch, Pyro, scvi-tools, scanpy) is pulled in
|
|
113
|
+
automatically. A GPU is optional but speeds up model fitting. See the
|
|
114
|
+
[installation guide](https://tcri.readthedocs.io/en/latest/usage/installation.html) for
|
|
115
|
+
details.
|
|
116
|
+
|
|
117
|
+
## Quickstart
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import tcri
|
|
121
|
+
|
|
122
|
+
# 1. register clonotype / phenotype / covariate / batch columns on your AnnData
|
|
123
|
+
tcri.ml.TCRIModel.setup_anndata(
|
|
124
|
+
adata,
|
|
125
|
+
layer="counts",
|
|
126
|
+
clonotype_key="clone_id",
|
|
127
|
+
phenotype_key="phenotype",
|
|
128
|
+
covariate_key="timepoint",
|
|
129
|
+
batch_key="patient",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# 2. fit the model and write learned quantities back onto the AnnData
|
|
133
|
+
model = tcri.ml.TCRIModel(adata)
|
|
134
|
+
model.train(max_epochs=200, batch_size=512)
|
|
135
|
+
model.to_anndata(adata)
|
|
136
|
+
|
|
137
|
+
# 3. read out information-theoretic metrics (bits)
|
|
138
|
+
mi = tcri.tl.mutual_information(adata, covariate="pre", normalize_mode="average")
|
|
139
|
+
ce = tcri.tl.clonotypic_entropy(adata, covariate="pre")
|
|
140
|
+
flux = tcri.tl.phenotypic_flux(adata, cov_from="pre", cov_to="post")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
No paired data yet? `tcri.datasets.simulate_tcri()` returns a synthetic AnnData whose
|
|
144
|
+
true mutual information is known in closed form — the basis for the
|
|
145
|
+
[tutorials](https://tcri.readthedocs.io/en/latest/tutorials/index.html).
|
|
146
|
+
|
|
147
|
+
## Framework
|
|
148
|
+
|
|
149
|
+
TCRi models the joint distribution of clonotype and phenotype hierarchically: a
|
|
150
|
+
clonotype-level prior `p_c` over phenotypes, a covariate-specific `p_ct` anchored to it,
|
|
151
|
+
and a per-cell phenotype that fuses a classifier on the latent embedding `z` with the
|
|
152
|
+
clone's local prior. The metrics are then pure functions of the learned clone × phenotype
|
|
153
|
+
joint. The full generative model and its plate diagram are documented under
|
|
154
|
+
[Concepts](https://tcri.readthedocs.io/en/latest/concepts/index.html).
|
|
155
|
+
|
|
156
|
+
<p align="center">
|
|
157
|
+
<img src="https://raw.githubusercontent.com/nceglia/tcri/main/docs/images/framework.png" alt="TCRi framework" width="760">
|
|
158
|
+
</p>
|
|
159
|
+
|
|
160
|
+
## Governance
|
|
161
|
+
|
|
162
|
+
The public interface, the generative mathematics, the metric definitions and the training
|
|
163
|
+
plan are each frozen by a machine-checked contract. A published number only means something
|
|
164
|
+
if the definition behind it is stable, so a metric cannot be silently redefined and the model
|
|
165
|
+
the package claims to implement is the model it does implement.
|
|
166
|
+
|
|
167
|
+
A failing conformance test means *stop and decide* — never loosen a contract to make it pass.
|
|
168
|
+
See [Governance](https://tcri.readthedocs.io/en/latest/contracts/index.html) for what each
|
|
169
|
+
contract covers, and `governance/` in the repository for the contracts themselves.
|
|
170
|
+
|
|
171
|
+
## Related tools
|
|
172
|
+
|
|
173
|
+
| Tool | Relationship |
|
|
174
|
+
|------|-------------|
|
|
175
|
+
| [scirpy](https://scirpy.readthedocs.io) | AIRR/TCR repertoire handling and analysis |
|
|
176
|
+
| [scvi-tools](https://scvi-tools.org) | probabilistic modelling backbone TCRi builds on |
|
|
177
|
+
| [scanpy](https://scanpy.readthedocs.io) / [AnnData](https://anndata.readthedocs.io) | single-cell data structures and workflows |
|
|
178
|
+
|
|
179
|
+
## Citation
|
|
180
|
+
|
|
181
|
+
If you use TCRi in your research, please cite:
|
|
182
|
+
|
|
183
|
+
> **TCRi: An Information Theoretic Framework for Paired Single Cell Gene Expression and TCR Sequencing**
|
|
184
|
+
>
|
|
185
|
+
> Nicholas Ceglia, Sohrab Salehi, et al.
|
|
186
|
+
>
|
|
187
|
+
> _bioRxiv_ 2022. doi: [10.1101/2022.10.01.510457](https://doi.org/10.1101/2022.10.01.510457)
|
|
188
|
+
|
|
189
|
+
<details>
|
|
190
|
+
<summary>BibTeX</summary>
|
|
191
|
+
|
|
192
|
+
```bibtex
|
|
193
|
+
@article{ceglia2022tcri,
|
|
194
|
+
title = {TCRi: An Information Theoretic Framework for Paired Single Cell Gene Expression and TCR Sequencing},
|
|
195
|
+
author = {Ceglia, Nicholas and Salehi, Sohrab and others},
|
|
196
|
+
journal = {bioRxiv},
|
|
197
|
+
year = {2022},
|
|
198
|
+
doi = {10.1101/2022.10.01.510457}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
</details>
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
TCRi is released under the [MIT License](LICENSE).
|
tcri-0.9.0/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://tcri.readthedocs.io">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/nceglia/tcri/main/docs/images/tcri_logo_hero.png" alt="TCRi" width="420">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Information-theoretic analysis of paired single-cell RNA + TCR sequencing.</b>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://tcri.readthedocs.io">Documentation</a> ·
|
|
13
|
+
<a href="https://tcri.readthedocs.io/en/latest/tutorials/index.html">Tutorials</a> ·
|
|
14
|
+
<a href="https://www.biorxiv.org/content/10.1101/2022.10.01.510457v1">Paper</a> ·
|
|
15
|
+
<a href="https://github.com/nceglia/tcri/issues">Issues</a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://github.com/nceglia/tcri/actions/workflows/tests.yml"><img src="https://github.com/nceglia/tcri/actions/workflows/tests.yml/badge.svg" alt="tests"></a>
|
|
20
|
+
<a href="https://tcri.readthedocs.io"><img src="https://readthedocs.org/projects/tcri/badge/?version=latest" alt="docs"></a>
|
|
21
|
+
<a href="https://github.com/nceglia/tcri/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license: MIT"></a>
|
|
22
|
+
<img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="python 3.10+">
|
|
23
|
+
<img src="https://img.shields.io/badge/built%20on-scverse-%231f9e16.svg" alt="built on scverse">
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
TCRi quantifies how **T-cell clonotypes** relate to **transcriptional phenotypes** in
|
|
29
|
+
paired single-cell data. It fits a hierarchical Bayesian model (on top of
|
|
30
|
+
[scvi-tools](https://scvi-tools.org) / [Pyro](https://pyro.ai)) that ties each clone's
|
|
31
|
+
phenotype distribution to gene expression, then reads out **information-theoretic
|
|
32
|
+
summaries** — clonotypic and phenotypic entropy, clone↔phenotype mutual information, and
|
|
33
|
+
phenotypic flux across conditions — over the learned clone × phenotype joint distribution.
|
|
34
|
+
|
|
35
|
+
It is built on the [scverse](https://scverse.org) stack
|
|
36
|
+
([AnnData](https://anndata.readthedocs.io), [scanpy](https://scanpy.readthedocs.io),
|
|
37
|
+
scvi-tools) and works on a standard `AnnData` carrying both a clonotype label and a
|
|
38
|
+
gene-expression matrix.
|
|
39
|
+
|
|
40
|
+
## Key capabilities
|
|
41
|
+
|
|
42
|
+
- **Joint model of expression + repertoire** — a hierarchical VAE (`TCRIModel`) that
|
|
43
|
+
learns each clone's phenotype distribution while reconstructing counts with a ZINB
|
|
44
|
+
decoder.
|
|
45
|
+
- **Information-theoretic metrics** — clonotypic entropy `H(c|φ)`, phenotypic entropy
|
|
46
|
+
`H(φ|c)`, normalized mutual information `I(c;φ)`, and phenotypic flux between conditions,
|
|
47
|
+
all in bits over the posterior joint.
|
|
48
|
+
- **Uncertainty-aware** — every metric can be reported as a posterior mean ± HDI by
|
|
49
|
+
drawing from the fitted Dirichlet posterior (`n_samples > 0`).
|
|
50
|
+
- **Group comparisons** — paired/unpaired contrasts across cohorts (e.g. responders vs
|
|
51
|
+
non-responders) with direction probabilities.
|
|
52
|
+
- **Diagnostics** — posterior-predictive checks, phenotype calibration, and permutation
|
|
53
|
+
nulls for the metrics.
|
|
54
|
+
- **Contract-governed** — the public API, the generative model, the metric definitions,
|
|
55
|
+
and the training plan are each frozen by a machine-checked contract.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
TCRi targets **Python ≥ 3.10**. Install from source:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/nceglia/tcri.git
|
|
63
|
+
cd tcri
|
|
64
|
+
pip install .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The heavy scientific stack (PyTorch, Pyro, scvi-tools, scanpy) is pulled in
|
|
68
|
+
automatically. A GPU is optional but speeds up model fitting. See the
|
|
69
|
+
[installation guide](https://tcri.readthedocs.io/en/latest/usage/installation.html) for
|
|
70
|
+
details.
|
|
71
|
+
|
|
72
|
+
## Quickstart
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import tcri
|
|
76
|
+
|
|
77
|
+
# 1. register clonotype / phenotype / covariate / batch columns on your AnnData
|
|
78
|
+
tcri.ml.TCRIModel.setup_anndata(
|
|
79
|
+
adata,
|
|
80
|
+
layer="counts",
|
|
81
|
+
clonotype_key="clone_id",
|
|
82
|
+
phenotype_key="phenotype",
|
|
83
|
+
covariate_key="timepoint",
|
|
84
|
+
batch_key="patient",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# 2. fit the model and write learned quantities back onto the AnnData
|
|
88
|
+
model = tcri.ml.TCRIModel(adata)
|
|
89
|
+
model.train(max_epochs=200, batch_size=512)
|
|
90
|
+
model.to_anndata(adata)
|
|
91
|
+
|
|
92
|
+
# 3. read out information-theoretic metrics (bits)
|
|
93
|
+
mi = tcri.tl.mutual_information(adata, covariate="pre", normalize_mode="average")
|
|
94
|
+
ce = tcri.tl.clonotypic_entropy(adata, covariate="pre")
|
|
95
|
+
flux = tcri.tl.phenotypic_flux(adata, cov_from="pre", cov_to="post")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
No paired data yet? `tcri.datasets.simulate_tcri()` returns a synthetic AnnData whose
|
|
99
|
+
true mutual information is known in closed form — the basis for the
|
|
100
|
+
[tutorials](https://tcri.readthedocs.io/en/latest/tutorials/index.html).
|
|
101
|
+
|
|
102
|
+
## Framework
|
|
103
|
+
|
|
104
|
+
TCRi models the joint distribution of clonotype and phenotype hierarchically: a
|
|
105
|
+
clonotype-level prior `p_c` over phenotypes, a covariate-specific `p_ct` anchored to it,
|
|
106
|
+
and a per-cell phenotype that fuses a classifier on the latent embedding `z` with the
|
|
107
|
+
clone's local prior. The metrics are then pure functions of the learned clone × phenotype
|
|
108
|
+
joint. The full generative model and its plate diagram are documented under
|
|
109
|
+
[Concepts](https://tcri.readthedocs.io/en/latest/concepts/index.html).
|
|
110
|
+
|
|
111
|
+
<p align="center">
|
|
112
|
+
<img src="https://raw.githubusercontent.com/nceglia/tcri/main/docs/images/framework.png" alt="TCRi framework" width="760">
|
|
113
|
+
</p>
|
|
114
|
+
|
|
115
|
+
## Governance
|
|
116
|
+
|
|
117
|
+
The public interface, the generative mathematics, the metric definitions and the training
|
|
118
|
+
plan are each frozen by a machine-checked contract. A published number only means something
|
|
119
|
+
if the definition behind it is stable, so a metric cannot be silently redefined and the model
|
|
120
|
+
the package claims to implement is the model it does implement.
|
|
121
|
+
|
|
122
|
+
A failing conformance test means *stop and decide* — never loosen a contract to make it pass.
|
|
123
|
+
See [Governance](https://tcri.readthedocs.io/en/latest/contracts/index.html) for what each
|
|
124
|
+
contract covers, and `governance/` in the repository for the contracts themselves.
|
|
125
|
+
|
|
126
|
+
## Related tools
|
|
127
|
+
|
|
128
|
+
| Tool | Relationship |
|
|
129
|
+
|------|-------------|
|
|
130
|
+
| [scirpy](https://scirpy.readthedocs.io) | AIRR/TCR repertoire handling and analysis |
|
|
131
|
+
| [scvi-tools](https://scvi-tools.org) | probabilistic modelling backbone TCRi builds on |
|
|
132
|
+
| [scanpy](https://scanpy.readthedocs.io) / [AnnData](https://anndata.readthedocs.io) | single-cell data structures and workflows |
|
|
133
|
+
|
|
134
|
+
## Citation
|
|
135
|
+
|
|
136
|
+
If you use TCRi in your research, please cite:
|
|
137
|
+
|
|
138
|
+
> **TCRi: An Information Theoretic Framework for Paired Single Cell Gene Expression and TCR Sequencing**
|
|
139
|
+
>
|
|
140
|
+
> Nicholas Ceglia, Sohrab Salehi, et al.
|
|
141
|
+
>
|
|
142
|
+
> _bioRxiv_ 2022. doi: [10.1101/2022.10.01.510457](https://doi.org/10.1101/2022.10.01.510457)
|
|
143
|
+
|
|
144
|
+
<details>
|
|
145
|
+
<summary>BibTeX</summary>
|
|
146
|
+
|
|
147
|
+
```bibtex
|
|
148
|
+
@article{ceglia2022tcri,
|
|
149
|
+
title = {TCRi: An Information Theoretic Framework for Paired Single Cell Gene Expression and TCR Sequencing},
|
|
150
|
+
author = {Ceglia, Nicholas and Salehi, Sohrab and others},
|
|
151
|
+
journal = {bioRxiv},
|
|
152
|
+
year = {2022},
|
|
153
|
+
doi = {10.1101/2022.10.01.510457}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
</details>
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
TCRi is released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tcri"
|
|
7
|
+
version = "0.9.0"
|
|
8
|
+
description = "Information theoretic metrics for single cell RNA and TCR sequencing."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Nicholas Ceglia", email = "nickceglia@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"single-cell",
|
|
17
|
+
"TCR",
|
|
18
|
+
"scRNA-seq",
|
|
19
|
+
"immunology",
|
|
20
|
+
"information-theory",
|
|
21
|
+
"bayesian",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"numpy>=1.26.1",
|
|
35
|
+
"pandas>=2.2.2",
|
|
36
|
+
"scipy>=1.14.1",
|
|
37
|
+
"scikit-learn>=1.1",
|
|
38
|
+
"anndata>=0.11.3",
|
|
39
|
+
"scanpy>=1.9.5",
|
|
40
|
+
"torch>=2.4.1",
|
|
41
|
+
"pyro-ppl>=1.9.1",
|
|
42
|
+
"scvi-tools>=1.3.0",
|
|
43
|
+
"umap-learn>=0.5.6",
|
|
44
|
+
"matplotlib>=3.9.2",
|
|
45
|
+
"seaborn>=0.13.2",
|
|
46
|
+
"tqdm>=4.66.5",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.optional-dependencies]
|
|
50
|
+
test = [
|
|
51
|
+
"pytest>=7",
|
|
52
|
+
"pytest-cov",
|
|
53
|
+
]
|
|
54
|
+
# Building examples/example.ipynb with its outputs embedded. Not needed to use tcri.
|
|
55
|
+
notebook = [
|
|
56
|
+
"jupytext>=1.16",
|
|
57
|
+
"nbclient>=0.10",
|
|
58
|
+
"nbformat>=5.10",
|
|
59
|
+
"ipykernel>=6",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[project.urls]
|
|
63
|
+
Homepage = "https://github.com/nceglia/tcri"
|
|
64
|
+
Repository = "https://github.com/nceglia/tcri"
|
|
65
|
+
Issues = "https://github.com/nceglia/tcri/issues"
|
|
66
|
+
Documentation = "https://tcri.readthedocs.io"
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.packages.find]
|
|
69
|
+
include = ["tcri*"]
|
|
70
|
+
|
|
71
|
+
[tool.pytest.ini_options]
|
|
72
|
+
testpaths = ["tests"]
|
|
73
|
+
python_files = ["test_*.py"]
|
|
74
|
+
python_classes = ["Test*"]
|
|
75
|
+
python_functions = ["test_*"]
|
|
76
|
+
addopts = "-v --tb=short"
|
|
77
|
+
filterwarnings = [
|
|
78
|
+
"ignore::DeprecationWarning",
|
|
79
|
+
]
|
tcri-0.9.0/setup.cfg
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError as _PackageNotFoundError, version as _version
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = _version("tcri")
|
|
5
|
+
except _PackageNotFoundError: # running from a source tree without an install
|
|
6
|
+
__version__ = "0.0.0+unknown"
|
|
7
|
+
|
|
8
|
+
from . import tools as tl # PR6: tl repointed metrics -> tools (engine-backed metrics)
|
|
9
|
+
from . import preprocessing as pp
|
|
10
|
+
from . import plotting as pl
|
|
11
|
+
from . import utils as ut
|
|
12
|
+
from . import model as ml
|
|
13
|
+
from . import diagnostics as diag # PR8
|
|
14
|
+
from . import datasets # synthetic cohorts, incl. one with a known-MI oracle
|
|
15
|
+
from . import get # accessors for cached tl results
|
|
16
|
+
|
|
17
|
+
# The unified engine, re-exported top-level for prominence.
|
|
18
|
+
from .tools import joint_distribution
|
|
19
|
+
|
|
20
|
+
import sys as _sys
|
|
21
|
+
|
|
22
|
+
_sys.modules.update({f'{__name__}.{m}': globals()[m] for m in ['tl', 'pp', 'pl', 'ut', 'ml', 'diag']})
|
|
23
|
+
|
|
24
|
+
#: The top-level surface. Without this, ``dir(tcri)`` also advertised ``sys`` and
|
|
25
|
+
#: ``PackageNotFoundError`` -- module-machinery names that leaked purely because they were
|
|
26
|
+
#: imported at module scope, and which a user could reasonably have mistaken for API. The
|
|
27
|
+
#: private aliases (``_sys``, ``_PackageNotFoundError``) keep them out of ``dir`` regardless;
|
|
28
|
+
#: this makes the intended surface explicit rather than incidental.
|
|
29
|
+
__all__ = [
|
|
30
|
+
"tl", "pp", "pl", "ut", "ml", "diag", "datasets", "get",
|
|
31
|
+
"tools", "preprocessing", "plotting", "utils", "model", "diagnostics",
|
|
32
|
+
"joint_distribution", "__version__",
|
|
33
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Private numeric + device seam for the engine (grafiti ``_compute`` parity).
|
|
2
|
+
|
|
3
|
+
- :mod:`._xp` — device dispatch (torch-first; CPU / torch-CUDA), ``asnumpy`` boundary.
|
|
4
|
+
- :mod:`._joint` — ``_joint_draws``: the batched ``[S, n_clones, P]`` engine core.
|
|
5
|
+
- :mod:`._distance` — kl / l1 / jsd kernels over phenotype distributions (was ``tcri/_distance.py``).
|
|
6
|
+
- :mod:`._tables` — the metric-table plumbing every ``tools`` metric reduces through
|
|
7
|
+
(``metric_table``, ``build_result``, ``build_stats``, ``collapse_to_replicates``); was
|
|
8
|
+
``tcri/tools/_common.py``, which put shared machinery inside one of its own consumers.
|
|
9
|
+
|
|
10
|
+
This is the LOWER layer: ``tools``/``plotting``/``diagnostics`` import down into it and it must
|
|
11
|
+
not import back up. ``_tables`` needs one symbol from ``tools._joint`` and takes it lazily inside
|
|
12
|
+
the function, never at module scope.
|
|
13
|
+
|
|
14
|
+
Nothing here is public API; GPU libs are imported lazily inside functions.
|
|
15
|
+
"""
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Distance / divergence kernels for phenotype distributions.
|
|
2
|
+
|
|
3
|
+
Single home for the KL kernel (was `metrics.dkl` + `flux.dkl_func`, two copies)
|
|
4
|
+
plus L1 and a symmetric Jensen–Shannon option. All operate on 1-D probability
|
|
5
|
+
vectors, use log base 2 (bits), and share one eps floor. ``phenotype_distance``
|
|
6
|
+
is the string→callable dispatcher used by ``phenotypic_flux(distance_metric=)``.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
EPS = 1e-12
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _normalize(p):
|
|
16
|
+
p = np.clip(np.asarray(p, float), EPS, None)
|
|
17
|
+
return p / p.sum()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def kl_divergence(p, q, *, base: float = 2.0, eps: float = EPS) -> float:
|
|
21
|
+
"""KL(p ‖ q) in bits (base 2). Asymmetric."""
|
|
22
|
+
p = np.clip(np.asarray(p, float), eps, None); p = p / p.sum()
|
|
23
|
+
q = np.clip(np.asarray(q, float), eps, None); q = q / q.sum()
|
|
24
|
+
return float(np.sum(p * (np.log(p / q) / np.log(base))))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def l1_distance(p, q) -> float:
|
|
28
|
+
"""L1 (Manhattan) distance between two normalized distributions, in [0, 2]."""
|
|
29
|
+
return float(np.abs(_normalize(p) - _normalize(q)).sum())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def jensen_shannon(p, q, *, base: float = 2.0, eps: float = EPS) -> float:
|
|
33
|
+
"""Jensen–Shannon divergence: symmetric, bounded [0, 1] bit — the recommended
|
|
34
|
+
symmetric shift measure."""
|
|
35
|
+
p = _normalize(p)
|
|
36
|
+
q = _normalize(q)
|
|
37
|
+
m = 0.5 * (p + q)
|
|
38
|
+
return float(0.5 * kl_divergence(p, m, base=base, eps=eps)
|
|
39
|
+
+ 0.5 * kl_divergence(q, m, base=base, eps=eps))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_REGISTRY = {
|
|
43
|
+
"l1": l1_distance,
|
|
44
|
+
"kl": kl_divergence,
|
|
45
|
+
"dkl": kl_divergence,
|
|
46
|
+
"js": jensen_shannon,
|
|
47
|
+
"jsd": jensen_shannon,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def phenotype_distance(metric):
|
|
52
|
+
"""Resolve ``distance_metric`` (a name or a callable ``f(p, q) -> float``)."""
|
|
53
|
+
if callable(metric):
|
|
54
|
+
return metric
|
|
55
|
+
key = str(metric).lower()
|
|
56
|
+
if key not in _REGISTRY:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"distance_metric must be a callable or one of {sorted(_REGISTRY)}; got {metric!r}"
|
|
59
|
+
)
|
|
60
|
+
return _REGISTRY[key]
|