mirpy-lib 3.1.0__tar.gz → 3.2.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.
Files changed (149) hide show
  1. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/.gitignore +3 -0
  2. mirpy_lib-3.2.0/BENCHMARKS.md +264 -0
  3. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/CHANGELOG.md +23 -0
  4. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/CLAUDE.md +105 -11
  5. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/PKG-INFO +38 -1
  6. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/README.md +36 -0
  7. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/SOURCES.md +1 -1
  8. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/THEORY.md +54 -0
  9. mirpy_lib-3.2.0/docs/Makefile +14 -0
  10. mirpy_lib-3.2.0/docs/api.rst +243 -0
  11. mirpy_lib-3.2.0/docs/conf.py +62 -0
  12. mirpy_lib-3.2.0/docs/index.rst +68 -0
  13. mirpy_lib-3.2.0/docs/requirements.txt +3 -0
  14. mirpy_lib-3.2.0/docs/usage.rst +154 -0
  15. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/__init__.py +1 -1
  16. mirpy_lib-3.2.0/mir/explain.py +529 -0
  17. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/repertoire.py +97 -7
  18. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/pyproject.toml +4 -3
  19. mirpy_lib-3.1.0/BENCHMARKS.md +0 -80
  20. mirpy_lib-3.1.0/appendix/.gitignore +0 -8
  21. mirpy_lib-3.1.0/appendix/.latexmkrc +0 -2
  22. mirpy_lib-3.1.0/appendix/Makefile +0 -30
  23. mirpy_lib-3.1.0/appendix/README.md +0 -40
  24. mirpy_lib-3.1.0/appendix/data/s1_diss_curves.tsv +0 -301
  25. mirpy_lib-3.1.0/appendix/data/s1_diss_hist.tsv +0 -81
  26. mirpy_lib-3.1.0/appendix/data/s1_dist_curves.tsv +0 -301
  27. mirpy_lib-3.1.0/appendix/data/s1_dist_hist.tsv +0 -81
  28. mirpy_lib-3.1.0/appendix/data/s2_gapblock.tsv +0 -8001
  29. mirpy_lib-3.1.0/appendix/data/s2_sw.tsv +0 -8001
  30. mirpy_lib-3.1.0/appendix/data/s3.tsv +0 -8001
  31. mirpy_lib-3.1.0/appendix/data/theory_stats.txt +0 -11
  32. mirpy_lib-3.1.0/appendix/diagram_embedding.dot +0 -27
  33. mirpy_lib-3.1.0/appendix/diagram_embedding.pdf +0 -0
  34. mirpy_lib-3.1.0/appendix/fig_s1_distributions.gp +0 -33
  35. mirpy_lib-3.1.0/appendix/fig_s1_distributions.pdf +0 -0
  36. mirpy_lib-3.1.0/appendix/fig_s2_correlation.gp +0 -23
  37. mirpy_lib-3.1.0/appendix/fig_s2_correlation.pdf +0 -0
  38. mirpy_lib-3.1.0/appendix/fig_s3_prototype_source.gp +0 -16
  39. mirpy_lib-3.1.0/appendix/fig_s3_prototype_source.pdf +0 -0
  40. mirpy_lib-3.1.0/appendix/gen_theory_data.py +0 -105
  41. mirpy_lib-3.1.0/appendix/refs.bib +0 -1124
  42. mirpy_lib-3.1.0/appendix/tcremp_theory.pdf +0 -0
  43. mirpy_lib-3.1.0/appendix/tcremp_theory.tex +0 -2331
  44. mirpy_lib-3.1.0/experiments/_cohort.py +0 -121
  45. mirpy_lib-3.1.0/experiments/_covid.py +0 -122
  46. mirpy_lib-3.1.0/experiments/_hf.py +0 -72
  47. mirpy_lib-3.1.0/experiments/analyze_epitope_variability.py +0 -117
  48. mirpy_lib-3.1.0/experiments/analyze_pc_decomposition.py +0 -98
  49. mirpy_lib-3.1.0/experiments/analyze_prototype_counts.py +0 -73
  50. mirpy_lib-3.1.0/experiments/benchmark_ann.py +0 -96
  51. mirpy_lib-3.1.0/experiments/benchmark_clustering.py +0 -85
  52. mirpy_lib-3.1.0/experiments/benchmark_codec_chains.py +0 -68
  53. mirpy_lib-3.1.0/experiments/benchmark_codec_losslessness.py +0 -95
  54. mirpy_lib-3.1.0/experiments/benchmark_density_ankspond.py +0 -88
  55. mirpy_lib-3.1.0/experiments/benchmark_density_tcrnet.py +0 -100
  56. mirpy_lib-3.1.0/experiments/benchmark_density_vdjdb.py +0 -212
  57. mirpy_lib-3.1.0/experiments/benchmark_density_yfv.py +0 -86
  58. mirpy_lib-3.1.0/experiments/benchmark_igh_shm.py +0 -53
  59. mirpy_lib-3.1.0/experiments/benchmark_lossless_depth.py +0 -76
  60. mirpy_lib-3.1.0/experiments/benchmark_lossless_kpc.py +0 -106
  61. mirpy_lib-3.1.0/experiments/benchmark_pgen_variants.py +0 -51
  62. mirpy_lib-3.1.0/experiments/benchmark_repertoire_agediverge.py +0 -157
  63. mirpy_lib-3.1.0/experiments/benchmark_repertoire_aging.py +0 -113
  64. mirpy_lib-3.1.0/experiments/benchmark_repertoire_cmvhla.py +0 -143
  65. mirpy_lib-3.1.0/experiments/benchmark_repertoire_covidbatch.py +0 -134
  66. mirpy_lib-3.1.0/experiments/benchmark_repertoire_covidhla.py +0 -89
  67. mirpy_lib-3.1.0/experiments/benchmark_repertoire_covidpaired.py +0 -105
  68. mirpy_lib-3.1.0/experiments/benchmark_repertoire_covidstatus.py +0 -102
  69. mirpy_lib-3.1.0/experiments/benchmark_repertoire_depth.py +0 -78
  70. mirpy_lib-3.1.0/experiments/benchmark_repertoire_hla.py +0 -156
  71. mirpy_lib-3.1.0/experiments/benchmark_repertoire_hlapanel.py +0 -74
  72. mirpy_lib-3.1.0/experiments/benchmark_repertoire_spikein.py +0 -110
  73. mirpy_lib-3.1.0/experiments/benchmark_repertoire_yfv.py +0 -96
  74. mirpy_lib-3.1.0/experiments/benchmark_vdjdb.py +0 -60
  75. mirpy_lib-3.1.0/experiments/reproduce_supplementary.py +0 -66
  76. mirpy_lib-3.1.0/experiments/train_forward_encoder.py +0 -52
  77. mirpy_lib-3.1.0/experiments/train_inverse_decoder.py +0 -58
  78. mirpy_lib-3.1.0/experiments/train_pgen_regressor.py +0 -58
  79. mirpy_lib-3.1.0/experiments/train_set_encoder.py +0 -66
  80. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/LICENSE +0 -0
  81. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/REPERTOIRE_EMBEDDING.md +0 -0
  82. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/REPERTOIRE_LESSONS.md +0 -0
  83. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/SQRT_D_MIGRATION.md +0 -0
  84. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/assets/mirpy_dark.png +0 -0
  85. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/assets/mirpy_dark.svg +0 -0
  86. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/assets/mirpy_light.png +0 -0
  87. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/assets/mirpy_light.svg +0 -0
  88. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/environment.yml +0 -0
  89. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/aliases.py +0 -0
  90. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/alleles.py +0 -0
  91. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/bench/__init__.py +0 -0
  92. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/bench/metrics.py +0 -0
  93. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/bench/theory.py +0 -0
  94. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/bench/vdjdb.py +0 -0
  95. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/density.py +0 -0
  96. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/distances/__init__.py +0 -0
  97. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/distances/germline.py +0 -0
  98. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/distances/junction.py +0 -0
  99. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/embedding/__init__.py +0 -0
  100. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/embedding/pca.py +0 -0
  101. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/embedding/presets.py +0 -0
  102. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/embedding/prototypes.py +0 -0
  103. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/embedding/tcremp.py +0 -0
  104. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/__init__.py +0 -0
  105. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/bundle.py +0 -0
  106. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/codec.py +0 -0
  107. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/decoder.py +0 -0
  108. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/encoder.py +0 -0
  109. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/set_encoder.py +0 -0
  110. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/tokenize.py +0 -0
  111. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/ml/train.py +0 -0
  112. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/py.typed +0 -0
  113. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/__init__.py +0 -0
  114. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/gene_library/README.md +0 -0
  115. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/gene_library/__init__.py +0 -0
  116. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/gene_library/build_gene_library.py +0 -0
  117. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/gene_library/build_region_annotations.py +0 -0
  118. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/gene_library/imgt_gene_library.txt +0 -0
  119. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/gene_library/olga_gene_library.txt +0 -0
  120. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/gene_library/region_annotations.txt +0 -0
  121. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/build_germline_dist.py +0 -0
  122. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_IGH.npz +0 -0
  123. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_IGK.npz +0 -0
  124. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_IGL.npz +0 -0
  125. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRA.npz +0 -0
  126. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRB.npz +0 -0
  127. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRD.npz +0 -0
  128. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/human_TRG.npz +0 -0
  129. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_IGH.npz +0 -0
  130. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_IGK.npz +0 -0
  131. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_IGL.npz +0 -0
  132. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRA.npz +0 -0
  133. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRB.npz +0 -0
  134. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRD.npz +0 -0
  135. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/germline_dist/mouse_TRG.npz +0 -0
  136. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/.gitkeep +0 -0
  137. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/generate_prototypes.py +0 -0
  138. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_IGH.tsv +0 -0
  139. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_IGK.tsv +0 -0
  140. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_IGL.tsv +0 -0
  141. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRA.tsv +0 -0
  142. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRB.tsv +0 -0
  143. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRD.tsv +0 -0
  144. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/human_TRG.tsv +0 -0
  145. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/manifest.json +0 -0
  146. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/mouse_TRA.tsv +0 -0
  147. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/mir/resources/prototypes/mouse_TRB.tsv +0 -0
  148. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/requirements.txt +0 -0
  149. {mirpy_lib-3.1.0 → mirpy_lib-3.2.0}/setup.sh +0 -0
@@ -194,3 +194,6 @@ experiments/runs/
194
194
 
195
195
  # obsidian-memory-sync marker (machine-local absolute paths)
196
196
  .claude/obsidian-sync.json
197
+
198
+ # git worktrees live inside the repo dir; never commit them as nested checkouts
199
+ .claude/worktrees/
@@ -0,0 +1,264 @@
1
+ # Benchmark results — regression baseline
2
+
3
+ Recorded results for the sample-level (repertoire) embedding benchmarks (`experiments/benchmark_repertoire_*.py`),
4
+ so a re-run can be checked for regression. All numbers are **repeated 50-fold CV mean±std** unless noted, on
5
+ arda-native coordinates. Theory/interpretation: `THEORY.md` T7. Data provenance: `SOURCES.md`.
6
+
7
+ - **Env**: conda `mirpy` (Python 3.12), Apple M3 (32 GB). Seeds fixed (`seed=0`). `RUN_BENCHMARK` not required
8
+ for these (they are experiment scripts, not the guarded pytest tier).
9
+ - **Recorded**: 2026-07-14. Re-run and compare; a drop beyond ~1 std on the headline metric is a regression.
10
+ - **Reproducibility**: all load **local data first, HF fallback** — `aging`/`airr_hip` via `_hf.fetch`
11
+ (`hf_hub_download`), `airr_covid19` via `_covid.covid_path` (local `~/hf/airr_covid19` else `isalgo/airr_covid19`).
12
+
13
+ ## Sample-level embedding — established regime (airr_benchmark aging, airr_hip)
14
+
15
+ | Benchmark | Command | Headline metric | Value |
16
+ |---|---|---|---|
17
+ | Depth-robustness (`prop:kme`) | `benchmark_repertoire_depth.py` | log–log slope `‖ΔΦ₁‖` vs `n_eff` (theory −0.5) | **−0.55** |
18
+ | Age regression (n=79) | `benchmark_repertoire_aging.py` | \|Spearman(age,pred)\|: diversity / kernel-mean / k-mer | **0.76** / 0.58 / 0.28 |
19
+ | CMV serostatus (n=240) | `benchmark_repertoire_cmvhla.py` | AUC: diversity / Φ blocks / learned | **0.83±0.05** / 0.59–0.63 / 0.49 |
20
+ | CMV age-matched control | `benchmark_repertoire_cmvhla.py` | AUC: diversity / age-only (decade-matched) | **0.83** / 0.45 |
21
+ | HLA-A\*02 (n=500, 25k) | `benchmark_repertoire_hla.py 250 25000` | AUC: second-moment / diversity | **0.623±0.048** / 0.460±0.048 |
22
+ | HLA panel (airr_hip, n=300) | `benchmark_repertoire_hla*panel` | second-moment AUC: B\*08 / B\*07 / B\*44 | **0.82** / 0.76 / 0.66 |
23
+ | Spike-in recovery | `benchmark_repertoire_spikein.py` | recall @N≤3k / FPR (clean P_gen null) | **~35–50%** / ~1.2% |
24
+
25
+ ## Aging divergence — `benchmark_repertoire_agediverge.py 500000 30` (58 donors @500k)
26
+
27
+ Divergence rises with age at depth but is **diversity-coupled, not an independent axis**.
28
+
29
+ | Metric | Value | Note |
30
+ |---|---|---|
31
+ | ρ(¹D, divergence) biased KME | −0.153 | biased V-stat `1/n_eff` artifact |
32
+ | ρ(¹D, divergence) **unbiased** KME | **−0.052** | `mmd_matrix(unbiased=True)`; artifact removed |
33
+ | ρ(¹D, divergence) overlap −logF | −0.680 | clonal-expansion driven |
34
+ | ρ(age, divergence overlap F) | 0.704 | (0.24 at 250k — depth-dependent) |
35
+ | **partial ρ(age, divergence \| ¹D)** | **0.067** (p=0.64) | n.s. — no independent axis (same at 40k/250k) |
36
+
37
+ ## COVID cohort (airr_covid19, 300 donors, ≤20k reads)
38
+
39
+ ### Batch cookbook — `benchmark_repertoire_covidbatch.py 300 20000` → **PASS**
40
+
41
+ | signal | naive AUC | batch-residualized |
42
+ |---|---|---|
43
+ | batch identity (OvR) | 0.777 | **0.032** |
44
+ | COVID status (⟂̸ batch) | **0.660** | 0.409 |
45
+ | HLA-A\*02 (⟂ batch) | 0.596 | **0.609** |
46
+
47
+ within-mixed-batch COVID (honest) **0.539**; MMD offset:biology ratio **1.05**.
48
+
49
+ ### HLA imprint class I+II — `benchmark_repertoire_covidhla.py 300 20000` (TRB)
50
+
51
+ second-moment > diversity in direction **15/17** alleles; class-II present **8/9**.
52
+
53
+ | allele | class | 2nd-moment AUC | Δ vs diversity |
54
+ |---|---|---|---|
55
+ | DRB1\*07:01 | II | **0.758** | +0.204 |
56
+ | B\*07:02 | I | 0.702 | +0.154 |
57
+ | DRB1\*15:01 | II | 0.702 | +0.110 |
58
+ | DQB1\*05:01 | II | 0.619 | +0.152 |
59
+ | A\*02:01 | I | 0.628 | +0.035 |
60
+
61
+ ### Paired α+β — `benchmark_repertoire_covidpaired.py 300 20000`
62
+
63
+ **TRA carries the stronger HLA imprint**; paired concat sits between (noisier β dilutes).
64
+
65
+ | allele | β (TRB) | α (TRA) | α+β paired |
66
+ |---|---|---|---|
67
+ | A\*02 | 0.538 | **0.636** | 0.612 |
68
+ | B\*07 | 0.646 | **0.701** | 0.664 |
69
+ | DRB1\*15 | 0.749 | **0.810** | 0.796 |
70
+
71
+ ### COVID biomarker — honest **negative** (`covidstatus.py` / `covidpaired.py`)
72
+
73
+ | test | β | α | paired |
74
+ |---|---|---|---|
75
+ | COVID status, naive | 0.673 | 0.685 | **0.702** |
76
+ | COVID status, batch-resid | 0.488 | 0.512 | **0.522** (chance) |
77
+ | witness rediscovery of paper's clones | 0.373 | **0.450** | — |
78
+
79
+ Convalescent (long-past) COVID leaves **no batch-robust bulk clonotype-identity signal** at RNA-seq depth; the
80
+ naive 0.66–0.72 was batch confound. Ground truth is 87% α (4393 α vs 567 β).
81
+
82
+ ---
83
+
84
+ ## 2026-07-14 (pm) — spectral block, COVID witness, TCGA survival
85
+
86
+ ### Spectral interaction block — `benchmark_repertoire_spectral.py 300 20000`
87
+
88
+ The opt-in top-`r` **eigenvalue** compaction of the second-moment block (new `n_eigs=` on `fit_repertoire_space`)
89
+ is **lossy for the HLA imprint**: HLA-A\*02 carriage lives in *which* public clones co-occur (directional), and
90
+ a rotation-invariant eigenvalue spectrum discards it. Default (`n_eigs=None`) keeps the full upper triangle,
91
+ unchanged.
92
+
93
+ | representation | block dim | HLA-A\*02 AUC |
94
+ |---|---|---|
95
+ | diversity (baseline) | 4 | 0.560±0.066 |
96
+ | D₂=512 top-16 eigvals | 16 | 0.545±0.093 |
97
+ | D₂=512 top-128 eigvals | 128 | 0.536±0.092 |
98
+ | D₂=256 full upper-tri | 32896 | 0.562±0.078 |
99
+ | **D₂=512 full upper-tri** | 131328 | **0.593±0.070** |
100
+
101
+ ### COVID witness — `benchmark_repertoire_covidwitness.py 300 20000`
102
+
103
+ Why per-clonotype Fisher-significant clones "vanish" in the bulk embedding: **cohort breadth**, not read depth.
104
+
105
+ | level | finding |
106
+ |---|---|
107
+ | Fisher genome-wide BH (150–300 donors) | **0** clones pass at any depth (20k or 120k reads) |
108
+ | Fisher full cohort (~1137 donors, user tmp scan) | **39 β / 4 α** clones pass BH (min q≈5e-4) |
109
+ | witness β: whole-cohort → **mixed-batch** | 0.510 → **0.752** (batch control is the lever) |
110
+ | witness β: HLA-strat median / best | 0.532 / 0.712 (best = max-selection; median ≈ whole-cohort) |
111
+ | witness α: whole / mixed / strat-best | 0.463 / 0.424 / 0.473 (no recovery — α witness fails) |
112
+
113
+ Batch control (mixed-batch contrast, where COVID+healthy share runs) recovers the paper's β clones; per-allele
114
+ HLA stratification adds more noise than signal at these donor counts. HLA+α+β is **not** the missing key — breadth is.
115
+
116
+ ### COVID motif recovery at full breadth — `benchmark_repertoire_covidmotif.py 0 60000`
117
+
118
+ Using the tool that *works* at breadth (Emerson incidence Fisher) on the **full 1137-donor cohort** (761 COVID /
119
+ 376 healthy, 60 k reads), both chains, to actually *find COVID motifs*:
120
+
121
+ | chain | genome-wide clones q<0.05 | GT-true recovered | + HLA-restricted |
122
+ |---|---|---|---|
123
+ | **α** | 7 | **4** | +0 |
124
+ | β | 0 | 0 | +0 |
125
+
126
+ The 4 recovered α clones are one coherent public family — **CAG·NYGGSQGNLIF** (single-residue variants), all
127
+ members of the paper's COVID-associated α **cluster 31** (27 GT clones, all `has_covid_association`). **HLA
128
+ restriction adds nothing** because these α clones are already *public* (present across HLA backgrounds) —
129
+ per-allele restriction only loses power.
130
+
131
+ **WS1 (2026-07-15) — the α/β loop closed, and it is NOT depth (refutes the earlier "β is depth-limited" note).**
132
+ Running β at **full *native* depth** (no downsampling) over the full 1137-donor cohort
133
+ (`benchmark_repertoire_covidmotif.py 0 0`) still passes **1** feature genome-wide at any `min_incidence∈{2,3,5}`
134
+ and recovers **0** GT-β clones — full depth does not rescue β. Nor does the *restricting allele*: a per-stratum
135
+ sweep (`benchmark_repertoire_covidmotif.py native_beta`, 12 class-II/B HLA carrier groups, 117–346 carriers each
136
+ at **native depth** ~3.5 M reads/donor) recovers **0** GT-β in every stratum — refuting "β needs full depth +
137
+ the restricting allele." And it is **not absence**: **253/256 GT-β clones
138
+ are present** in the cohort (median incidence **9** donors, max **110**). The reason is that the paper's β
139
+ "COVID-associated" clones are short **public bystanders** (`CASSx…YEQYF`, TRBJ2-7 — e.g. `CASSYSYEQYF` in 110
140
+ donors) occurring in COVID **and** healthy donors alike, so they do not discriminate status by *incidence*.
141
+ **The α/β asymmetry is which clones are status-enriched vs public bystanders — not depth or breadth:** α's
142
+ recovered `CAG·NYGGSQGNLIF` family is a genuine status-enriched public expansion; β's GT set is dominated by
143
+ non-discriminating convergent publics. *Caveat:* the incidence Fisher tests presence-breadth, not clonal-
144
+ expansion magnitude — a frequency/abundance-based test could still recover β clones enriched in *size* not
145
+ *breadth* (untested). Net: the recoverable "find-motifs" signal is the **public α compartment** (breadth-
146
+ powered, no HLA needed); the breadth-starved bulk MMD witness recovered none of it.
147
+
148
+ ### TCGA — `benchmark_repertoire_tcga.py <chains> BRCA,LUAD,KIRC 50000`
149
+
150
+ Tumor-type separation is **depth-dependent** (deep IG light chains ≫ shallow TR); **survival adds nothing** over
151
+ clinical covariates. Base Cox C-index (age+sex+stage+log reads): BRCA 0.72, LUAD 0.66, KIRC 0.73.
152
+
153
+ | chain | tumor-type macro-OvR AUC | survival ΔC (BRCA / LUAD / KIRC) |
154
+ |---|---|---|
155
+ | TRB (median ~24 clonotypes) | 0.523 | +0.003 / −0.003 / +0.002 |
156
+ | IGH | 0.500 | +0.002 / +0.001 / −0.004 |
157
+ | **IGK (deepest chain)** | **0.666** | −0.001 / −0.004 / −0.001 |
158
+ | concat TRB+IGH | 0.538 | −0.002 / +0.001 / +0.001 |
159
+
160
+ IGK's 0.67 shows the method works when clonotype depth suffices (the deep IG compartment carries tissue signal);
161
+ the flat ΔC across every chain says the tumour-infiltrating repertoire adds **no prognostic value beyond clinical
162
+ covariates** at TCGA RNA-seq depth.
163
+
164
+ ### TCGA survival — biology-grounded features — `benchmark_repertoire_tcga_survival.py`
165
+
166
+ The clonotype *embedding* adds nothing, but interpretable **biology axes** (isotype from `c_call`, infiltration /
167
+ hot-vs-cold, atypicality = gene-usage divergence from the tumour-type centroid, clonal expansion — modelled on an
168
+ internal AIRR-tissue EDA) do, cancer-specifically. Base Cox = age+sex+stage+log reads; **bold** = best gain per row.
169
+
170
+ C-index gain over clinical (5-fold CV):
171
+
172
+ | cancer | C base | isotype | infiltration | atypicality | clonality | all-AIRR |
173
+ |---|---|---|---|---|---|---|
174
+ | SKCM (melanoma) | 0.609 | +0.008 | +0.034 | −0.001 | +0.031 | **+0.036** |
175
+ | KIRP (renal pap.) | 0.717 | −0.008 | **+0.030** | −0.003 | +0.014 | +0.015 |
176
+ | LGG (glioma) | 0.764 | −0.006 | **+0.019** | +0.008 | +0.016 | +0.011 |
177
+ | KIRC (renal cc.) | 0.721 | +0.005 | **+0.009** | +0.002 | +0.002 | +0.006 |
178
+
179
+ (LUAD / STAD / OV / BLCA: AIRR gain ≈ 0 or negative.)
180
+
181
+ KM median-split log-rank p (the EDA's stratification test — surfaces threshold effects the linear C-index misses;
182
+ **bold** = p<0.05):
183
+
184
+ | cancer | infiltration | IgA fraction | atypicality |
185
+ |---|---|---|---|
186
+ | KIRC | **0.002** | 0.089 | 0.880 |
187
+ | SKCM | **0.000** | **0.026** | 0.716 |
188
+ | KIRP | **0.020** | 0.750 | 0.555 |
189
+ | OV | **0.044** | 0.581 | 0.691 |
190
+ | LGG | **0.022** | 0.076 | **0.001** |
191
+ | BLCA | 0.676 | **0.010** | 0.243 |
192
+
193
+ **Read:** infiltration (hot/cold) stratifies survival in **5/8** cancers (melanoma, both renal, ovarian, glioma)
194
+ and adds C-index in melanoma/KIRP; **IgA** (isotype / mucosal) stratifies **bladder** + melanoma; **atypicality**
195
+ stratifies **glioma** — each matching known immune biology. Prognosis lives in infiltration magnitude + isotype +
196
+ typicality, **not** clonotype identity, and is cancer-specific — the reason the identity embedding's ΔC was flat.
197
+
198
+ ## Repertoire embedding for TME & survival — pan-cancer (33 TCGA types)
199
+
200
+ The reframing: the prognostic axes above are **channels of one TME-aware, multi-chain repertoire embedding**
201
+ Φ(S) (`_tcga_embedding.py`: per-chain identity ‖ diversity ‖ coverage/infiltration, + isotype + composition
202
+ + atypicality). `benchmark_repertoire_tcga_pancancer.py ALL` fits Φ once over 9 425 OS-annotated samples
203
+ (78-dim, 5 embeddable chains + all 7 in the composition channel) and, per cancer, reports the CV **ΔC-index**
204
+ of clinical+Φ over clinical (age+sex+stage+log reads) and a **likelihood-ratio p** for the Φ block.
205
+
206
+ Cancers where Φ is robustly prognostic (**both** LR p<0.05 **and** CV ΔC>0; bold):
207
+
208
+ | cancer | n | events | C base | C+Φ | ΔC | LR p | top channel |
209
+ |---|---|---|---|---|---|---|---|
210
+ | **SKCM** (melanoma) | 460 | 219 | 0.609 | 0.647 | **+0.039** | **0.000** | coverage |
211
+ | **BLCA** (bladder) | 411 | 182 | 0.650 | 0.675 | **+0.025** | **0.002** | identity |
212
+ | **HNSC** (head & neck) | 500 | 218 | 0.617 | 0.639 | **+0.022** | **0.012** | coverage |
213
+ | **LGG** (glioma) | 423 | 112 | 0.764 | 0.779 | **+0.016** | **0.000** | coverage |
214
+
215
+ Strong effect-size positives (ΔC>0, LR n.s. — power-limited): SARC +0.038 (isotype), KIRP +0.034 (atypicality),
216
+ LUAD +0.009, KIRC +0.008, BRCA +0.007. Immune-cold / small cohorts are flat-to-negative (overfit): LIHC −0.029,
217
+ STAD −0.026, PAAD −0.029, UCEC −0.072 (n=181/35ev).
218
+
219
+ **Pan-cancer (20 evaluable):** mean ΔC ≈ 0 (median +0.003), Φ significant in 5/20 — most-informative channel
220
+ tally **coverage 5 · atypicality 5 · composition 4 · isotype 3 · identity 2 · diversity 1**. The signal is
221
+ cancer-specific and lives in the **TME channels (infiltration, atypicality, composition), not clonotype
222
+ identity**; the pan-cancer mean is ≈0 because immune-cold cohorts overfit and cancel the immune-hot wins —
223
+ so the honest claim is *Φ adds significant prognostic value in immunologically active cancers* (melanoma,
224
+ glioma, head&neck, bladder), not universally.
225
+
226
+ ### TME states (unsupervised) — `benchmark_repertoire_tcga_tme.py ALL 6`
227
+
228
+ KMeans (k=6) on Φ's TME channels (9 425 samples) recovers coherent, interpretable microenvironment states
229
+ (z-scored channel means; **bold** = defining extreme). The UMAP (`experiments/figures/umap_tcga_tme`) shows a
230
+ clean **infiltration gradient** (hot → cold) organising the pan-cancer cohort.
231
+
232
+ | state | n | infiltration | T-vs-B | switch | diversity | death | HR (vs ref) | enriched cancers |
233
+ |---|---|---|---|---|---|---|---|---|
234
+ | cold-humoral | 1565 | −0.53 | −0.36 B | +0.39 | −0.40 | 0.33 | **1.14** (p=0.011) | BRCA, BLCA, PRAD |
235
+ | hot-diverse | 2090 | **+1.03** | −0.70 B | +0.35 | **+0.92** | 0.31 | 0.92 (p=0.072) | BRCA, LUAD, STAD |
236
+ | T-balanced | 1277 | −0.10 | **+0.86 T** | +0.15 | +0.06 | 0.29 | 1.09 | KIRC, PRAD, BRCA |
237
+ | cold-T unsw. | 883 | **−1.58** | **+1.72 T** | **−2.20** | −1.66 | 0.30 | 1.05 | LGG, LIHC, SARC |
238
+ | cold | 871 | −1.27 | +0.74 T | −0.70 | −1.13 | 0.28 | 1.08 | THCA, LIHC, LGG |
239
+ | warm-B (ref) | 2739 | +0.48 | −0.45 B | +0.37 | +0.40 | 0.31 | — | BRCA, LUAD, HNSC |
240
+
241
+ Stratified multivariate log-rank across states (blocking tumour type) **p=0.0038** — the states are prognostic
242
+ *beyond* cancer type. The **cold-humoral** low-infiltration state carries the **worst** outcome (HR 1.14, p=0.011);
243
+ the **hot-diverse** state trends protective (HR 0.92). Glioma (LGG) falls in the cold, strongly T-skewed states,
244
+ matching its known low-but-T-biased infiltrate. The repertoire embedding organises the TME unsupervised.
245
+
246
+ ### In-silico evolution — perturb infiltration, decode the coupled response — `benchmark_repertoire_tcga_insilico.py ALL`
247
+
248
+ `mir.repertoire.sample_descriptor` makes every metric a **smooth derivable coordinate** (infiltration = log-mass,
249
+ diversity = log n_eff, clonality = Σw², identity = kernel mean), so the cohort's joint distribution is a
250
+ generative **manifold** and moving a sample along the infiltration axis (hot↔cold) while staying on it predicts
251
+ how the other metrics respond. Gaussian-manifold conditional slopes d(metric)/d(infiltration), pan-cancer mean:
252
+
253
+ | response as tumour gets hotter | slope | reading |
254
+ |---|---|---|
255
+ | diversity | **+0.84** | hot ⇒ more diverse (universal, all cancers) |
256
+ | class-switch | +0.52 | hot ⇒ more class-switched B cells |
257
+ | IgG | +0.49 | hot ⇒ more IgG (mature humoral) |
258
+ | T-vs-B | −0.63 | hot ⇒ more B-skewed (TLS-like) |
259
+ | atypicality | −0.23 | hot ⇒ less atypical / more convergent |
260
+
261
+ **In-silico "make this tumour hotter"** (move cold→hot on-manifold, read the CoxPH Δlog-HR): predicted
262
+ **protective in 12/20** cancers (SKCM −0.49, HNSC −0.63, LUAD −0.44, LAML −0.96 …) — TIL-is-good — but correctly
263
+ **adverse in glioma** (LGG **+1.19**, GBM +0.47) and renal (KIRC/KIRP +0.3), matching the known "immune-hot glioma
264
+ is worse" biology. The embedding *simulates* the hot↔cold survival axis; the couplings are learned, not imposed.
@@ -3,6 +3,29 @@
3
3
  All notable changes to `mirpy-lib` (import `mir`). This project follows semantic versioning; the v3 line is a
4
4
  greenfield ML/embedding rewrite (the classical v1.x/v2 toolkit is frozen on branch `legacy-v2`).
5
5
 
6
+ ## 3.2.0 — 2026-07-17
7
+
8
+ Minor: one new public module, nothing removed or changed.
9
+
10
+ ### Added
11
+
12
+ - **`mir.explain`** (T7) — explainable readouts over any repertoire feature matrix.
13
+ `ChannelSpec` / `ChannelBuilder` / `stack_embeddings` attach the name→column map that `Φ.vector`
14
+ does not carry (`stack_embeddings` is exact: `X[i] == embs[i].vector`, names only, no transform);
15
+ `channel_report` ablates each named channel under a caller-supplied scorer (leave-one-in by
16
+ default; `mode="both"` adds the conditional half that exposes *redundant* channels — high `delta`,
17
+ `delta_out≈0`; optional row-permutation p-values); `channel_drivers` hops from a winning
18
+ **kernel-mean** channel to the clonotypes driving it via `class_witness`, and refuses channels with
19
+ no clonotype pre-image (a Hill number's "drivers" are a category error, not an open question).
20
+ Scorer-agnostic by design — the library never sees the labels and ships no scorers, so a Cox
21
+ C-index and a CV AUC both plug in. **No existing module changed.**
22
+
23
+ ## 3.1.1 — 2026-07-14
24
+
25
+ Maintenance re-release of 3.1.0 with no functional changes — 3.1.0 was withdrawn from PyPI, and
26
+ this version restores the package under a fresh, clean version. The API, coordinates, and behaviour
27
+ are identical to 3.1.0 (see below).
28
+
6
29
  ## 3.1.0 — 2026-07-14
7
30
 
8
31
  The Part-2 feature tier on top of the 3.0 consolidated embedding core: neural codecs, continuous-density
@@ -6,6 +6,28 @@ library** for immune receptors (TCR/BCR). A greenfield, slim rewrite: prototype
6
6
  (TCREMP) now, neural codecs + density methods next (Part 2). The classical v1.x/v2 toolkit is
7
7
  frozen on branch **`legacy-v2`** (`mirpy-lib` 2.x) — do not develop there.
8
8
 
9
+ ## Repo split (2026-07-16) — three homes
10
+ This repo is now **the library + its CI/test data only**. Analysis and manuscript live elsewhere:
11
+ - **`~/vcs/code/mirpy`** (here): `mir/` library, unit + fast/slow CI tests (`tests/`, on slim bundled
12
+ data), plans for new features, docs, and **slim tabular benchmark results** for README/docs. Benchmark
13
+ *scripts* and the theory *appendix* have moved out (below).
14
+ - **`~/vcs/projects/2026-mirpy-analysis`**: all **benchmark scripts** (`benchmarks/` — local + aldan3),
15
+ full result docs (BENCHMARKS/THEORY/…), figures, run outputs, dataset catalog. Run the mirpy library
16
+ from here; refresh numbers-of-record here.
17
+ - **`~/vcs/manuscripts/2026-mirpy-ms`**: the **theory appendix** (`appendix/tcremp_theory.tex`) + the paper
18
+ (`manuscript-latex/`). (Earlier this appendix lived here in the code repo; it migrated on 2026-07-16.)
19
+
20
+ Compute: heavy benchmarks run on the **aldan3** cluster (env `mirpy`, `sbatch`); light jobs local. See the
21
+ analysis repo.
22
+
23
+ ## Worktrees — REQUIRED for Claude sessions
24
+ **Work in a git worktree for this repo, never directly on the main checkout.** Multiple Claude sessions
25
+ edit mirpy concurrently (the 2026-07-16 split was done across parallel sessions and hit collisions), so at
26
+ the start of any session that will modify this repo, call **`EnterWorktree`** (isolated worktree under
27
+ `.claude/worktrees/`, fresh branch off `origin/master`); commit + push from there; merge via PR/fast-forward.
28
+ This keeps concurrent sessions from clobbering each other's working trees. The same applies to the sibling
29
+ repos (`2026-mirpy-analysis`, `2026-mirpy-ms`).
30
+
9
31
  ## Reuse, don't duplicate — the ecosystem
10
32
  mirpy v3 has **no AIRR data-model layer of its own**; it works on `vdjtools` polars frames and
11
33
  delegates:
@@ -46,13 +68,13 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
46
68
  PCs; diverse chains (IGH/TR*) 2000 / ~65 PCs (95%), ~220–300 PCs (99%, for codec reconstruction).
47
69
  - `bench/` — `vdjdb.py` (loader), `metrics.py` (`cluster(method=…)` DBSCAN default | HDBSCAN | OPTICS,
48
70
  kneedle eps, F1/retention), `theory.py` (S1–S3 + T5 + T6 `tcrnet_convergence` + `codec_losslessness`).
49
- Needs `[bench]`. Clustering is a precision/coverage trade-off (`experiments/benchmark_clustering.py`):
71
+ Needs `[bench]`. Clustering is a precision/coverage trade-off (`benchmarks/benchmark_clustering.py`):
50
72
  DBSCAN tightest/purest (paper regime), HDBSCAN ~3× coverage at lower F1 (variable-density), OPTICS
51
73
  dominated, KMeans no noise-rejection.
52
74
  - `density.py` — continuous-density TCRNET/ALICE (T6): `fit_density_space` (one shared PCA basis),
53
75
  `neighbor_enrichment` (balloon adaptive-radius Poisson/binomial + water-level calibration;
54
76
  `backend=` **exact** BallTree default | **kdtree** exact scipy cKDTree 5–9× faster | **ann**
55
- pynndescent ~30× at ≥1e5, recall<1 conservative — `experiments/benchmark_ann.py`),
77
+ pynndescent ~30× at ≥1e5, recall<1 conservative — `benchmarks/benchmark_ann.py`),
56
78
  `enriched_mask`, `denoise_and_cluster`, `generate_background` (vdjtools P_gen, lazy). Torch-free
57
79
  (scipy/sklearn). Prefer a **biological control** as background (differential) over P_gen.
58
80
  **Abundance-aware** (T6 sec:dens-abund): pass `abundance=` (clone sizes) + `weight="log1p"`/`anscombe`
@@ -63,6 +85,18 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
63
85
  clonotype-cloud PCA + RFF basis, prototype-hash-verified `RepertoireSpace`), `sample_embedding`
64
86
  (`Φ(S)` = RFF kernel mean ‖ coverage-standardized Hill ‖ second-moment Fisher; `n_eff=(Σw²)⁻¹`),
65
87
  `mmd_distance`/`mmd_matrix`/`hla_stratified_mmd`, `class_witness` (supervised MMD motif finder). Torch-free.
88
+ Opt-in `fit_repertoire_space(n_eigs=r)` swaps the second-moment block's full `D₂(D₂+1)/2` upper-triangle
89
+ for its top-`r` eigenvalues (rotation-invariant spectrum; default `None` = upper-tri, unchanged — but
90
+ lossy for the *directional* HLA imprint, so the full triangle stays the recommended block; `benchmark_repertoire_spectral.py`).
91
+ - `explain.py` — **explainable readouts over any feature matrix** (T7). `ChannelSpec`/`ChannelBuilder`/
92
+ `stack_embeddings` attach the name→column map `Φ.vector` does not carry (per-chain blocks merge by
93
+ name); `channel_report(X, spec, scorer, base=, mode="in"|"out"|"both", n_permutations=)` ablates each
94
+ channel under a **caller-supplied** scorer — the library never sees `y` and ships no scorers, so a Cox
95
+ C-index and a CV AUC both plug in. `mode="in"` (default) is marginal, because the scorers reduce their
96
+ input and leave-one-*out* is near-blind to narrow channels; `"both"` adds the conditional half, and
97
+ high `delta` + `delta_out≈0` is the **redundancy** signature. `channel_drivers` hops channel→clonotypes
98
+ via `class_witness`, but **only** for a channel declared `attributable` (a kernel mean); a Hill number
99
+ has no clonotype pre-image and it raises. Depends one-way on `repertoire.py`; nothing there changed.
66
100
  - `ml/` — Part 2 (torch), neural codecs + `set_encoder.py` (learned repertoire track: Set-Transformer/DeepRC
67
101
  attention pooling + `SetEncoderBundle`).
68
102
  - `resources/` — `prototypes/` (TSVs + manifest), `gene_library/` (region_annotations.txt),
@@ -73,8 +107,8 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
73
107
  (pure-Python hatchling; no C build). Extras: `[bench] [annotate] [build] [ml] [docs] [dev]`.
74
108
  - Tests: `python -m pytest tests/ -q` (78 pass; `-m "not integration"` for the ~5s fast tier —
75
109
  the pynndescent ANN parity test carries a one-time JIT cost). All self-contained on bundled resources.
76
- - Experiments: `python experiments/reproduce_supplementary.py` (theory S1–S3),
77
- `python experiments/benchmark_vdjdb.py` (Table S1). Analyses: `analyze_prototype_counts.py`
110
+ - Experiments: `python benchmarks/reproduce_supplementary.py` (theory S1–S3),
111
+ `python benchmarks/benchmark_vdjdb.py` (Table S1). Analyses: `analyze_prototype_counts.py`
78
112
  (geometry saturates by K≈100 — T.1/S4), `analyze_pc_decomposition.py` (V/J η² ≈0.44/0.49,
79
113
  CDR3-length η² 0.13 & R²=0.95; germline low-rank ~13 PCs — T.4). See `THEORY.md`.
80
114
 
@@ -100,7 +134,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
100
134
  **PCA-compacted junction embedding** (T3: 1000-D junction → ~51 PCs @95% var on arda coords,
101
135
  99% for reconstruction), fit train-only.
102
136
  V/J stay exact germline lookups (nothing to learn); the codec's job is the junction part.
103
- Results on M3 MPS (`experiments/train_{forward_encoder,inverse_decoder,pgen_regressor}.py`),
137
+ Results on M3 MPS (`benchmarks/train_{forward_encoder,inverse_decoder,pgen_regressor}.py`),
104
138
  **re-pinned on the arda-native coords** (2026-07-13):
105
139
  - **DONE forward codec** (`tokenize,encoder,train`): seq → compact code, reconstruction
106
140
  cosine **0.9984** (n=8k; paper 0.887). DNN inference is K-independent. Geometry is fine at
@@ -111,7 +145,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
111
145
  (over-compacted); 99% (284 PCs) restores 0.40.** The T5 chain-adaptive lesson now bites TRB
112
146
  too, not just IGH/TRD — the decoder default is 99%.
113
147
  - **DONE Pgen regressor** (`train.train_pgen_regressor`): seq → log10 Pgen(1mm), r **0.967**,
114
- **~190× faster** than the native DP. Breakdown (`experiments/benchmark_pgen_variants.py`):
148
+ **~190× faster** than the native DP. Breakdown (`benchmarks/benchmark_pgen_variants.py`):
115
149
  r ranks marginalized > J > V > V&J (a CDR3-only regressor best predicts the pure-CDR3
116
150
  marginalized Pgen; V&J-conditional depends on unseen genes) and 1mm > exact (smoother ball).
117
151
  - **DONE unified codec** (`codec.py`): jointly train encoder+decoder with a geometry-anchor
@@ -121,9 +155,9 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
121
155
  rotation match.** `CodecBundle` serializes the PCA transform + a prototype hash + weights;
122
156
  `load` refuses a prototype-hash mismatch so incomparable embeddings can't be mixed. Any
123
157
  trained codec MUST be shipped as a bundle, never bare weights.
124
- - Per-chain/species breakdown: `experiments/benchmark_codec_chains.py` (forward cos 0.997–0.999
158
+ - Per-chain/species breakdown: `benchmarks/benchmark_codec_chains.py` (forward cos 0.997–0.999
125
159
  universal; inverse chain-dependent — short κ/λ/γ/mouse easy, IGH/TRD hard).
126
- - **DONE lossless-recon depth/K/data sweep** (`experiments/benchmark_lossless_{depth,kpc}.py`,
160
+ - **DONE lossless-recon depth/K/data sweep** (`benchmarks/benchmark_lossless_{depth,kpc}.py`,
127
161
  real held-out TRB from `isalgo/airr_control` vs arda landmarks): exact-match is **training-data-
128
162
  limited, not architecture-limited** — the old "~0.40 ceiling" was n≈8k starvation. Same one-shot
129
163
  decoder: n=20k→50k→100k drives exact **0.885→0.941→0.958** (token 0.996→0.998) at K=2000/PC=400,
@@ -140,7 +174,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
140
174
  is V/J/CDR1/CDR2). Isotype is a low-cardinality categorical (~9 IGH classes ≈ 3 bits) *independent*
141
175
  of V/J/CDR3 ⇒ not reconstructable ⇒ **carry `c_call` as an exact stored column, never embed it**
142
176
  (same as v_call/j_call metadata). No codec change.
143
- - **DONE T5 (SHM/IGH)** (`bench.theory.shm_embedding_drift`, `experiments/benchmark_igh_shm.py`):
177
+ - **DONE T5 (SHM/IGH)** (`bench.theory.shm_embedding_drift`, `benchmarks/benchmark_igh_shm.py`):
144
178
  SHM embedding drift is ~linear/sublinear in mutation load (bounded; IGH 104/mut < TRB 128/mut
145
179
  — IGH lowest slope, robust to SHM). IGH's hard reconstruction is **over-compaction, not the
146
180
  frame**: on arda coords 95% code (95 PCs) → exact 0.115, 99% (422 PCs) → 0.356 (> old 0.152;
@@ -151,7 +185,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
151
185
  enrichment `E(z)=f_obs/f_gen` in embedding space; Poisson (ALICE, P_gen bg) or binomial
152
186
  (TCRNET, control bg) + BH q; water-level calibration for the naive regime. Theory
153
187
  `tcrnet_convergence` confirms the r→0 graph limit (ρ 0.37→−0.05 as radius grows). Benchmarks
154
- `experiments/benchmark_density_{yfv,ankspond,tcrnet,vdjdb}.py` on HF
188
+ `benchmarks/benchmark_density_{yfv,ankspond,tcrnet,vdjdb}.py` on HF
155
189
  `isalgo/airr_{yfv19,ankspond,benchmark,control}` + VDJdb slim.
156
190
  **Key lesson**: real repertoires are pervasively convergent, so a P_gen background flags ~40% of
157
191
  clones — use a *biological control* (differential: day15-vs-day0, B27±, CMV-vs-control) for
@@ -167,7 +201,7 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
167
201
  HLA-stratified; `class_witness` = supervised MMD motif finder; learned co-equal Set-Transformer/DeepRC in
168
202
  `mir.ml.set_encoder`. Reuse: `TCREmp.embed`, `density.{_WEIGHTS,fit_density_space,calibrate_radius}`,
169
203
  `vdjtools.stats.inext`, `preprocess.downsample`, `ml.bundle` hashing. Tests
170
- `tests/test_{repertoire,set_encoder}.py`; benchmarks `experiments/benchmark_repertoire_{aging,depth,cmvhla,hla}.py`
204
+ `tests/test_{repertoire,set_encoder}.py`; benchmarks `benchmarks/benchmark_repertoire_{aging,depth,cmvhla,hla}.py`
171
205
  (shared `_cohort.py`; `airr_benchmark` aging 79, `airr_hip` Emerson 2017 786). **Key empirical lesson**
172
206
  (`THEORY.md` T7; **all adversarially verified — two over-claims caught & corrected**): depth-robustness holds
173
207
  (`prop:kme`, slope −0.55) but is a **generic KME/MMD Monte-Carlo rate**, not embedding-specific. **Age & CMV
@@ -197,4 +231,64 @@ fix were added to `vdjtools` under the owner's direction (this is that owner's e
197
231
  the paper's clones (0.37β/0.45α, GT is 87% α). Long-past exposure has no batch-robust bulk signal at RNA-seq depth.
198
232
  - **Parallelism/GPU documented** (README "Performance & parallelism"): `TCREmp(threads=0)` all-core; density
199
233
  `backend="kdtree"` multicore exact; `pick_device` **CUDA→MPS→CPU** (was MPS-only) + `MIR_DEVICE`.
234
+ - **Sample-level refinements + TCGA — 2026-07-14 (pm)** (`BENCHMARKS.md` "2026-07-14 (pm)"; all numbers recorded):
235
+ - **WS1 spectral block** (`fit_repertoire_space(n_eigs=r)`, `benchmark_repertoire_spectral.py`): opt-in top-`r`
236
+ eigenvalues of the second-moment covariance. **Lossy for HLA** — HLA-A\*02 lives in *which* clones co-occur
237
+ (directional), eigenvalues are rotation-invariant → top-r ≤0.55 while full upper-tri reaches **0.593** (D₂=512).
238
+ Default `None` = upper-tri (unchanged); keep the full triangle. Test `test_spectral_second_block_top_r_eigvals`.
239
+ - **WS2 COVID witness** (`benchmark_repertoire_covidwitness.py`, vdjtools `biomarker.fisher_association`): answers
240
+ "Fisher passes but embedding vanishes — why?" → **breadth, not depth**. Genome-wide Fisher passes 0 clones at
241
+ 150–300 donors (any depth 20k/120k); full ~1137-donor cohort passes 39β/4α (user tmp scan). Sample-level lever
242
+ is **batch control** (β witness whole 0.51 → mixed-batch **0.75**); per-allele HLA-stratification adds noise
243
+ (median≈whole). HLA+α+β is not the key — breadth is.
244
+ - **WS2b COVID motif recovery** (`benchmark_repertoire_covidmotif.py`, full 1137-donor incidence Fisher, both
245
+ chains): the *breadth-powered* way to find motifs. **α genome-wide recovers 4 GT-true clones = a coherent
246
+ public family CAG·NYGGSQGNLIF (paper cluster 31)**; β recovers 0 (rarer/HLA-restricted). **HLA restriction
247
+ adds 0** — the recoverable α clones are already public. **WS1 (2026-07-15) closed the α/β loop and REFUTED
248
+ the "β is depth/HLA-limited" guess**: β at full *native* depth genome-wide, and `native_beta` across 12
249
+ class-II/B carrier strata at native depth, both recover **0** GT-β. β's GT clones are short **public
250
+ bystanders** (`CASSx…YEQYF` TRBJ2-7, present in COVID *and* healthy; 253/256 present, median incidence 9),
251
+ so they don't discriminate status by *incidence* — the α/β split is **status-enriched vs public-bystander,
252
+ not depth/breadth/HLA**. (Untested caveat: an abundance/size-based test could still catch β clones enriched
253
+ in *magnitude* not *breadth*.)
254
+ - **WS3 UMAP** (`plot_sample_umap.py {covid,aging,hip}`): faceted UMAP of Φ₁ (=MMD geometry) by age/HLA/batch/
255
+ COVID/CMV → `benchmarks/figures/`. covid19 visibly clusters by **batch** in raw Φ₁, dissolves after
256
+ `residualize` (the `prop:batch` story made visual).
257
+ - **WS4 TCGA** (`_tcga.py` local-first loader, untars once; `benchmark_repertoire_tcga.py`; `lifelines` in
258
+ `[bench]`): 9591 samples, 7 chains, OS survival. **Tumor-type separation is depth-dependent** — deepest chain
259
+ **IGK 0.67** ≫ shallow TRB 0.52 / IGH 0.50 (IG light chains carry tissue signal). **Survival = honest negative**:
260
+ ΔC-index ≈0 for every chain over a clinical Cox (age+sex+stage+log reads, base C 0.66–0.73) — the tumour-
261
+ infiltrating repertoire adds no prognostic value at RNA-seq depth. No grade column (used stage); PFS empty.
262
+ - **WS4b TCGA survival via biology features** (`_tcga_features.py` isotype/infiltration/atypicality/clonality;
263
+ `benchmark_repertoire_tcga_survival.py`) — the prognosis the *identity embedding* misses lives in interpretable
264
+ axes (modelled on an internal AIRR-tissue EDA). **Infiltration (hot/cold Z-axis) stratifies survival in 5/8
265
+ cancers** (KIRC/SKCM/KIRP/OV/LGG log-rank p<0.05) and adds C-index in melanoma (+0.036) / KIRP (+0.030);
266
+ **IgA isotype** stratifies bladder (mucosal, p=0.010) + melanoma; **atypicality** stratifies glioma (p=0.001)
267
+ — all matching known immunobiology. Isotype/atypicality show via KM stratification, not linear C-index. Net:
268
+ tissue prognosis = infiltration magnitude + isotype + typicality, **not clonotype identity**.
269
+ - **REFRAME → TME-aware repertoire embedding, pan-cancer** (`_tcga_embedding.py` = per-chain identity ‖
270
+ diversity ‖ coverage/infiltration ‖ isotype ‖ composition ‖ atypicality — the biology axes recast as Φ(S)
271
+ channels; `benchmark_repertoire_tcga_{pancancer,tme}.py`; THEORY.md T7 "Repertoire embeddings for the TME",
272
+ BENCHMARKS.md "Repertoire embedding for TME & survival"). One Φ over 9425 OS samples (78-dim, 5 embeddable
273
+ chains + all 7 via composition). **Robustly prognostic** (LR p<0.05 & CV ΔC>0) in **SKCM +0.039 / BLCA
274
+ +0.025 / HNSC +0.022 / LGG +0.016**; effect-size positives SARC (isotype) / KIRP (atypicality). Top channel
275
+ = **coverage/atypicality/composition, ~never identity**; immune-cold cohorts overfit → pan-cancer mean ΔC≈0
276
+ masks the immune-hot wins. **Paradigm lesson (T7 #6): the same Φ(S) that reads infection/HLA in blood via
277
+ its identity channels stratifies the TME + survival in tissue via its non-identity channels.** Unsupervised
278
+ Φ clustering → TME states (`benchmark_repertoire_tcga_tme.py`, UMAP `benchmarks/figures/umap_tcga_tme`).
279
+ - **Derivable descriptor + in-silico evolution** (`mir.repertoire.sample_descriptor`/`RepertoireDescriptor`/
280
+ `decode_metrics`; `benchmark_repertoire_tcga_insilico.py`; THEORY.md T7 lesson 7): **mass-preserving** smooth
281
+ descriptor — infiltration=log-mass, diversity=log n_eff, clonality=Σw², identity=kernel mean are all smooth
282
+ coordinates (keeps the mass Φ normalises away). The cohort coordinate distribution = a generative manifold;
283
+ perturbing infiltration + conditioning (Gaussian) = **in-silico evolution**: hotter ⇒ diversity +0.84 /
284
+ switch +0.52 / T-vs-B −0.63; CoxPH "make hotter" protective 12/20 cancers, adverse in glioma (LGG +1.19) —
285
+ learned couplings match immunobiology. Test `test_descriptor_metrics_derivable_smooth_and_decodable`.
286
+ **TODO**: per-cancer n_pc; Thorsson immune-subtype validation; a learned (flow/VAE) generative manifold for
287
+ full sequence-level simulation. "Promote the multi-chain descriptor" is **partially done** (2026-07-17):
288
+ `build_embedding` was four things — (a) per-locus space fitting, (b) channel assembly + registry,
289
+ (c) TME feature engineering, (d) impute/z-score. `mir.explain` promotes **(b)+(d)**; **(c)** is correctly
290
+ analysis-local (isotype/composition/atypicality are tissue features, not library concerns — though
291
+ atypicality, being a Φ-geometry op, would belong in `repertoire.py` as `centroid_atypicality` if ever
292
+ wanted). **Remaining**: (a) — "fit one `RepertoireSpace` per locus over a cohort" is construction, so it
293
+ belongs in `mir.repertoire` as a separate, smaller follow-up.
200
294
  - Full plan: `~/.claude/plans/i-want-to-completely-crystalline-lake.md`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mirpy-lib
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: ML-oriented embeddings for immune receptor repertoires (TCR/BCR)
5
5
  Project-URL: Homepage, https://github.com/antigenomics/mirpy
6
6
  Project-URL: Repository, https://github.com/antigenomics/mirpy
@@ -31,6 +31,7 @@ Requires-Dist: vdjmatch; extra == 'annotate'
31
31
  Provides-Extra: bench
32
32
  Requires-Dist: huggingface-hub; extra == 'bench'
33
33
  Requires-Dist: kneed; extra == 'bench'
34
+ Requires-Dist: lifelines; extra == 'bench'
34
35
  Requires-Dist: matplotlib; extra == 'bench'
35
36
  Requires-Dist: pynndescent; extra == 'bench'
36
37
  Requires-Dist: seaborn; extra == 'bench'
@@ -179,6 +180,42 @@ At whole-repertoire scale, pass `neighbor_enrichment(..., backend="kdtree")` (ex
179
180
  5–9× faster than the default BallTree) or `backend="ann"` (approximate pynndescent, ~30× faster
180
181
  past ~10⁵ clones, trading a small conservative undercount); see `experiments/benchmark_ann.py`.
181
182
 
183
+ ## Sample-level (repertoire) embedding (`mir.repertoire`)
184
+
185
+ One fixed vector `Φ(S)` per **repertoire** — an order-invariant multiset of clonotypes with clone
186
+ sizes — depth-robust into the low-coverage bulk-RNA-seq regime (Theory §T.7). `Φ(S)` sketches the
187
+ empirical measure `ρ_S = Σ_σ w_σ δ_{φ(σ)}` (concave frequency weights, so one hyperexpanded clone
188
+ can't dominate) in three blocks: an RFF **kernel mean** (depth-robust, codebook-free — no `K`, no
189
+ clustering), a coverage-standardized **Hill diversity** profile, and a **second-moment** Fisher
190
+ vector carrying clonotype co-occurrence (HLA-linked public structure). Repertoire distance is the
191
+ **MMD** `‖Φ₁(S) − Φ₁(S')‖`.
192
+
193
+ ```python
194
+ from mir.repertoire import fit_repertoire_space, sample_embedding, mmd_matrix, class_witness
195
+ from mir.embedding.tcremp import TCREmp
196
+ import polars as pl
197
+
198
+ model = TCREmp.from_defaults("human", "TRB", n_prototypes=1000)
199
+ space = fit_repertoire_space(model, pl.concat(samples)) # ONE basis for the whole cohort
200
+ embs = [sample_embedding(space, s) for s in samples] # Φ(S): mean ‖ diversity ‖ second moment
201
+ D = mmd_matrix(embs, unbiased=True) # pairwise repertoire distance (unbiased MMD²)
202
+ motifs = class_witness(space, pos_samples, neg_samples, candidates) # public clones separating two groups
203
+ ```
204
+
205
+ **Comparability invariant** (as with the codecs / density): every sample in a cohort must be
206
+ embedded through *one* prototype set and *one* PCA+RFF basis, or the measures are incomparable —
207
+ `fit_repertoire_space` fits that basis once and `RepertoireSpace` refuses a prototype-hash mismatch.
208
+
209
+ Use the **unbiased** MMD (`unbiased=True`) whenever samples differ in depth/diversity — the biased
210
+ V-statistic's `1/n_eff` self-term otherwise inflates low-diversity samples and fakes a signal. When a
211
+ nuisance batch is present, compare *within-batch* contrasts (residualize `Φ` on the batch indicator):
212
+ a batch offset is first-order and cancels, while a batch-orthogonal signal (e.g. HLA) survives. The
213
+ empirical rule of thumb — **diversity for how-even, the embedding for which-clones**: clone-size
214
+ phenotypes (age, CMV) are a diversity summary's turf, while clonotype identity (HLA — strongest in
215
+ TRA and class II) lives in the second moment / witness. A learned co-equal set encoder
216
+ (Set-Transformer / DeepRC) is in `mir.ml.set_encoder` (`[ml]` extra). See
217
+ `experiments/benchmark_repertoire_*.py`, `BENCHMARKS.md`, and `THEORY.md` T7.
218
+
182
219
  ## Reproduce the paper
183
220
 
184
221
  ```bash
@@ -122,6 +122,42 @@ At whole-repertoire scale, pass `neighbor_enrichment(..., backend="kdtree")` (ex
122
122
  5–9× faster than the default BallTree) or `backend="ann"` (approximate pynndescent, ~30× faster
123
123
  past ~10⁵ clones, trading a small conservative undercount); see `experiments/benchmark_ann.py`.
124
124
 
125
+ ## Sample-level (repertoire) embedding (`mir.repertoire`)
126
+
127
+ One fixed vector `Φ(S)` per **repertoire** — an order-invariant multiset of clonotypes with clone
128
+ sizes — depth-robust into the low-coverage bulk-RNA-seq regime (Theory §T.7). `Φ(S)` sketches the
129
+ empirical measure `ρ_S = Σ_σ w_σ δ_{φ(σ)}` (concave frequency weights, so one hyperexpanded clone
130
+ can't dominate) in three blocks: an RFF **kernel mean** (depth-robust, codebook-free — no `K`, no
131
+ clustering), a coverage-standardized **Hill diversity** profile, and a **second-moment** Fisher
132
+ vector carrying clonotype co-occurrence (HLA-linked public structure). Repertoire distance is the
133
+ **MMD** `‖Φ₁(S) − Φ₁(S')‖`.
134
+
135
+ ```python
136
+ from mir.repertoire import fit_repertoire_space, sample_embedding, mmd_matrix, class_witness
137
+ from mir.embedding.tcremp import TCREmp
138
+ import polars as pl
139
+
140
+ model = TCREmp.from_defaults("human", "TRB", n_prototypes=1000)
141
+ space = fit_repertoire_space(model, pl.concat(samples)) # ONE basis for the whole cohort
142
+ embs = [sample_embedding(space, s) for s in samples] # Φ(S): mean ‖ diversity ‖ second moment
143
+ D = mmd_matrix(embs, unbiased=True) # pairwise repertoire distance (unbiased MMD²)
144
+ motifs = class_witness(space, pos_samples, neg_samples, candidates) # public clones separating two groups
145
+ ```
146
+
147
+ **Comparability invariant** (as with the codecs / density): every sample in a cohort must be
148
+ embedded through *one* prototype set and *one* PCA+RFF basis, or the measures are incomparable —
149
+ `fit_repertoire_space` fits that basis once and `RepertoireSpace` refuses a prototype-hash mismatch.
150
+
151
+ Use the **unbiased** MMD (`unbiased=True`) whenever samples differ in depth/diversity — the biased
152
+ V-statistic's `1/n_eff` self-term otherwise inflates low-diversity samples and fakes a signal. When a
153
+ nuisance batch is present, compare *within-batch* contrasts (residualize `Φ` on the batch indicator):
154
+ a batch offset is first-order and cancels, while a batch-orthogonal signal (e.g. HLA) survives. The
155
+ empirical rule of thumb — **diversity for how-even, the embedding for which-clones**: clone-size
156
+ phenotypes (age, CMV) are a diversity summary's turf, while clonotype identity (HLA — strongest in
157
+ TRA and class II) lives in the second moment / witness. A learned co-equal set encoder
158
+ (Set-Transformer / DeepRC) is in `mir.ml.set_encoder` (`[ml]` extra). See
159
+ `experiments/benchmark_repertoire_*.py`, `BENCHMARKS.md`, and `THEORY.md` T7.
160
+
125
161
  ## Reproduce the paper
126
162
 
127
163
  ```bash
@@ -33,7 +33,7 @@ Phenotype-labelled repertoire cohorts for the §T.7 sample-level embedding bench
33
33
  | `airr_hip` | HF `isalgo/airr_hip` = **Emerson et al. 2017** (*Nat Genet*, PMID 28369038, doi:10.1038/ng.3822) | manifest `metadata.txt`: `file_name, sample_id, age, race, sex, cmv, hla`; **786 subjects**; TCRβ + **CMV serostatus** (`+`/`-`/`NA` = 340/421/25) + **HLA-A/B typing** (comma-sep `HLA-A*02,…`; A*02 most common, n=294). Sample files are the `file_name` path (`corr/HIP*.txt.gz`), full-depth (~10⁵ clonotypes) | `hf_hub_download(repo_id="isalgo/airr_hip", filename="metadata.txt", repo_type="dataset")` then per-sample `file_name`. Downsample per-sample; **age-match CMV±** (CMV rises with age, a diversity confound) |
34
34
  | SRA shallow RNA-seq | HF `isalgo/airr_benchmark/sra/` (`meta.tsv`, `samples.tar.gz`) | `PMID Run BioProject Sample`; 2993 rows; all PMID 30830871, BioProject PRJNA511467 | low-coverage bulk-RNA-seq stress set (the `10²–10⁴` clonotype/chain regime); accession manifest only |
35
35
  | `airr_covid19` | HF `isalgo/airr_covid19` = **Vlasova, Nekrasova, Komkov et al. 2026** (*Genome Med* 18, 20; DNA-multiplex FMBA cohort — cite per the dataset card, ⚠ verify before appendix use) | `metadata.tsv`: `file_name, reads, batch_id, sample_id, COVID_status` (COVID/healthy/precovid/unknown), `COVID_IgG/IgM/PCR`, full **4-digit HLA class I+II both alleles** (`HLA-{A,B,C}_{1,2}`, `HLA-{DPB1,DQB1,DRB1}_{1,2}`, `HLA-DRB{3,4,5}_1`), `donor_id`; **1258 donors**, **paired TRA+TRB**. **9 real sequencing batches** (`2020/09…2021/01_FMBA_NovaSeq*`, 103–185 donors each), partly status-confounded (NovaSeq4≈all-healthy, NovaSeq9=all-precovid; NovaSeq5/6/7 mixed). Ships `covid_associated_clonotypes.csv` (114 CDR3 clusters, `has_covid_association` T/F — COVID ground-truth motifs). **No `age`** (age+sex live in the sibling `isalgo/airr_covid19_vacc`). Files: **local git-LFS checkout** at `~/hf/airr_covid19/<file_name>` (vdjtools cols `count/freq/cdr3aa/v/j`), *not* the HF hub cache — load by local path, not `hf_hub_download` | local `~/hf/airr_covid19/`; `git clone git@hf.co:datasets/isalgo/airr_covid19` (LFS). Downsample per-sample. **Batch is a strong nuisance ⇒ prop:batch test bed**; HLA ⟂ batch (donor genetics) vs COVID-status ⟂̸ batch (confounded) |
36
- | `airr_tcga` | HF `isalgo/airr_tcga` = **TCGA** tumour bulk RNA-seq → AIRR (BostonGene *eurynome* pipeline; RNA-seq AIRR method Bolotin et al. *Nat Biotechnol* 2017, PMID 29020005) | `metadata.tsv`: `sample_id` (`TCGA-XX-XXXX.N`), `subject_id`, `cancer_type`/`disease`/`study_id` (**33 cancer types**), `sex/race/age`, `tumor_stage`, `therapy/response`, `OS/OS_event/PFS/PFS_event`, **`total_reads`** (raw FASTQ total, GDC realigned-BAM `Total_Reads`, verified ≠ aligned; 99.6% present) + `aligned_reads`; **9591 samples**, **TCR+BCR but IG-dominant** (~97% IG: IGK/IGL/IGH; ~3% TR), median **634** clonotypes/sample (RNA-seq regime). Caveats: 487 lack clinical, 42 lack `total_reads` | **local git-LFS checkout** at `~/hf/airr_tcga/` (`git clone git@hf.co:datasets/isalgo/airr_tcga`) or `hf_hub_download(repo_id="isalgo/airr_tcga", filename="samples.tar.gz"/"metadata.tsv", repo_type="dataset")` + `load.py`. Per-sample AIRR in `samples.tar.gz` (`samples/<sample_id>.tsv`, AIRR cols). Separate **`metadata.hla.tsv`** = donor-level **HLA class-I** keyed by `subject_id` (`HLA-{A,B,C}_{1,2}`, 4-digit, + `hla_source`), union of TCGA **PanImmune** (Thorsson et al. *Immunity* 2018, PMID 29628290; primary, 7649) + **OptiType** fill (Szolek et al. *Bioinformatics* 2014, PMID 25143287; 1474); **9123/9450 donors = 96.5%**, → 9263 samples. **Tissue depth↔infiltration test bed** (`sec:samp-norm`, `prop:infiltration`): `total_reads` is the technical denominator for the receptor read-fraction infiltration proxy; HLA enables the `prop:hla` stratification |
36
+ | `airr_tcga` | HF `isalgo/airr_tcga` = **TCGA** tumour bulk RNA-seq → AIRR (RNA-seq AIRR extraction method Bolotin et al. *Nat Biotechnol* 2017, PMID 29020005) | `metadata.tsv`: `sample_id` (`TCGA-XX-XXXX.N`), `subject_id`, `cancer_type`/`disease`/`study_id` (**33 cancer types**), `sex/race/age`, **stage** (`cancer_stage` S1–S4 + `tumor_stage` i–iv; no grade column), `therapy/response`, `OS`+`OS_event` (days + 0/1 death, ~9510 usable; **`PFS`/`PFS_event` are present-but-empty — unusable**), **`total_reads`** (raw FASTQ total, GDC realigned-BAM `Total_Reads`, verified ≠ aligned; 99.6% present) + `aligned_reads`; **9591 samples**, **TCR+BCR but IG-dominant** (~97% IG: IGK/IGL/IGH; ~3% TR), median **634** clonotypes/sample (RNA-seq regime). Caveats: 487 lack clinical, 42 lack `total_reads` | **local git-LFS checkout** at `~/hf/airr_tcga/` (`git clone git@hf.co:datasets/isalgo/airr_tcga`) or `hf_hub_download(repo_id="isalgo/airr_tcga", filename="samples.tar.gz"/"metadata.tsv", repo_type="dataset")` + `load.py`. Per-sample AIRR in `samples.tar.gz` (`samples/<sample_id>.tsv`, AIRR cols). Separate **`metadata.hla.tsv`** = donor-level **HLA class-I** keyed by `subject_id` (`HLA-{A,B,C}_{1,2}`, 4-digit, + `hla_source`), union of TCGA **PanImmune** (Thorsson et al. *Immunity* 2018, PMID 29628290; primary, 7649) + **OptiType** fill (Szolek et al. *Bioinformatics* 2014, PMID 25143287; 1474); **9123/9450 donors = 96.5%**, → 9263 samples. **Tissue depth↔infiltration test bed** (`sec:samp-norm`, `prop:infiltration`): `total_reads` is the technical denominator for the receptor read-fraction infiltration proxy; HLA enables the `prop:hla` stratification |
37
37
 
38
38
  ## Theory appendix (`appendix/`)
39
39