Metadata-Version: 2.4
Name: toc-cluster
Version: 0.6.2
Summary: Trust Orbit Computation: clustering with confidence
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.20
Requires-Dist: scikit-learn>=1.0
Requires-Dist: scipy>=1.7
Provides-Extra: gpu
Requires-Dist: torch>=2.0; extra == "gpu"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"

# toc-cluster

**Trust Orbit Computation** - a clustering algorithm that tells you not just which cluster each point belongs to, but *how confident* that assignment is.

## Install

```bash
pip install toc-cluster
```

## Quick Start

```python
from toc import TOC
import numpy as np

X = np.random.randn(1000, 50)   # your data, shape (n_samples, n_features)

model = TOC(n_clusters=5)
model.fit(X)

print(model.labels_)          # cluster label for every point, shape (1000,)
print(model.states_)          # 'MERGE', 'ORBIT', or 'ESCAPE' per point
print(model.orbit_percent_)   # ORBIT% per cluster — measures boundary ambiguity
model.summary()               # print full results table
```

## The Three States

| State | Meaning | What it tells you |
|-------|---------|-------------------|
| MERGE | Confidently inside a cluster | High-quality assignment |
| ORBIT | Genuinely between clusters | Boundary — assignment uncertain |
| ESCAPE | Outlier | Far from any cluster structure |

## Benchmarks

ARI, mean ± sd across 5 seeds, v0.6.1. Two TOC numbers per dataset: **unsupervised** (`model.fit(X)`) and **semi-supervised** (`model.fit(X, y=...)`).

| Dataset | N | K-Means | TOC unsup | TOC semi | FlowSOM |
|---|---|---|---|---|---|
| Samusik | 514,386 | 0.551 ± 0.026 | 0.815 ± 0.008 | 0.889 ± 0.039 | 0.763 (mean, 3 seeds) |
| Levine32 | 104,184 | 0.675 ± 0.016 | 0.872 ± 0.020 | 0.943 ± 0.052 | 0.913 (mean, 3 seeds) |
| Levine13 | 81,747 | 0.460 ± 0.009 | 0.504 ± 0.010 | 0.529 ± 0.007 | — |
| Samusik_01 | 53,173 | 0.473 ± 0.024 | 0.842 ± 0.004 | 0.670 ± 0.060 | 0.562 ± 0.011 |
| Bodenmiller BCR-XL | 172,791 | 0.510 ± 0.052 | 0.586 ± 0.002 | 0.616 ± 0.0002 | 0.207 ± 0.030 |
| Bone marrow | 263,159 | 0.380 ± 0.009 | 0.396 ± 0.006 | 0.542 ± 0.024 | — |
| PBMC 10k* | 11,627 | 0.685 ± 0.019 | 0.655 ± 0.071 | 0.897 ± 0.000 | — |

*PBMC 10k's ground truth is Leiden clustering on the same PCA space `X` comes from, not real biology — treat it as a stability check, not an accuracy claim. TOC's unsupervised result there is genuinely seed-sensitive (range 0.62–0.78).

**Beats K-Means** on 6 of 7 CyTOF/bone-marrow datasets above. **Loses to K-Means** on 3 scRNA-seq datasets not shown here (WormNeuron, MouseES, PBMC4k) — root cause: the preliminary K-Means seeding step splits a large, diffuse true class into multiple working clusters (a known K-Means failure mode this algorithm inherits), which also degrades confidence calibration on those same datasets. `model._orbit_calibration_flag_` (True when ORBIT population < ~12%) flags this without needing ground truth.

FlowSOM was run on its native input (`arcsinh(x/5)`, unstandardized); it scores much lower on the standardized matrix TOC uses. TOC beats FlowSOM's mean on Samusik, is tied on Levine32, and wins clearly on the 3 newer CyTOF sets.

## Preprocessing

CyTOF-style raw marker panels need standardization for TOC to work well — without it, TOC can score *below* K-Means on the same data. Pass `_auto_standardize=True` if your input isn't already standardized:

```python
model = TOC(n_clusters=K, _auto_standardize=True)  # for raw, unnormalized marker panels only
```

Leave it off (default) for PCA/embedding-space input — z-scoring PCA components erases their variance-ranked ordering and badly hurts results (measured: 19-96% ARI drop on scRNA-seq benchmarks). Only use it on genuinely raw, unnormalized feature panels.

## When to Use TOC

**Good fit:** real, expert-labeled flow/mass cytometry data (CyTOF), where you want per-point confidence, not just a partition.

**Not a good fit (yet):** PCA-embedded scRNA-seq data — TOC's preliminary-seeding weakness shows up there specifically (see Benchmarks). If your ground truth is itself derived from another clustering algorithm (e.g. Leiden) rather than real annotation, treat any ARI comparison as a stability check, not an accuracy measurement.

## GPU Support

TOC automatically uses GPU if CUDA is available. No code changes needed.

## What ORBIT Points Converge To

Each ORBIT point moves by a fixed-size step per trusted neighbour: `s_ij = pi * T_ij * alpha_fixed`. Summed over neighbours, this is one step of gradient descent on the trust-weighted sum of distances to those neighbours — it converges to the **trust-weighted geometric median (Fermat–Weber point)** of the point's local neighbourhood, verified against an independent optimizer.

With only one or two dominant neighbours, the objective has no smooth interior minimum, so the point locks into a stable 2-cycle instead — oscillation half-width `r* = (pi * alpha_fixed) / 2` (0.12 by default). This is the single/two-neighbour limit, not general ORBIT behavior; with the default `k_trust=25`, most points converge cleanly to their local median.

## ORBIT% — The Novel Output

ORBIT% per cluster measures how many of its members sit at a boundary with other clusters — high ORBIT% means an ambiguous cluster, low means well-defined. On human bone marrow, progenitor cell types (e.g. MPP-MyLy, 89% ORBIT) show much higher ORBIT% than terminally differentiated ones (e.g. Plasma Cells, 0.9%) — the qualitative pattern this output is meant to surface.

## Citation

[Paper link — coming soon]

## License

MIT
