Metadata-Version: 2.4
Name: rafinat
Version: 0.2.1
Summary: Bayesian normalizations for RNA-seq
Author-email: Georgy Meshcheryakov <iam@georgy.top>
License: BSD-2-Clause
Keywords: single-cell,scRNA-seq,RNA-seq,normalization,bioinformatics,Dirichlet-multinomial,compositional data,CLR,Fisher-Rao,scikit-learn
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy
Requires-Dist: scipy
Provides-Extra: rafinat
Requires-Dist: liudist>=0.1; extra == "rafinat"
Provides-Extra: gpu
Requires-Dist: liudist>=0.1; extra == "gpu"
Provides-Extra: sklearn
Requires-Dist: scikit-learn; extra == "sklearn"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: scikit-learn; extra == "test"

# rafinat

Dirichlet-Multinomial posterior normalizations for single-cell RNA-seq count data.

Three count normalizations derived from a Dirichlet-Multinomial posterior (Liouville) view of the
latent expression rates. 

Each is a scikit-learn-style transformer: you instantiate it with its operating point, then
`.fit` / `.transform` / `.fit_transform` a count matrix — so it drops into a preprocessing pipeline.

| transformer | what it is | default operating point |
|----------|-----------|--------------------------|
| `compost`  | the DM/Liouville model | `p=0` digamma log-corner, top-2/3-trimmed per-cell centering |
| `hellnorm` | the Hellinger / Fisher-Rao spherical (sqrt) normalization | top-2/3-trimmed per-cell sqrt reference |
| `rafinat`  | the complete Fisher-Rao Liouville embedding | composition arc length (sharp `r_snr`) + radial cell-size coordinate, `beta="total"` |

All take a dense `(genes, cells)` array of raw counts (genes = features/rows, cells =
samples/columns) and return `(genes, cells)` (`rafinat` returns `(genes + 1, cells)` — the extra
row is the cell-size scale coordinate). `compost` and `hellnorm` are pure `numpy`/`scipy` (CPU).
`rafinat` uses the optional [`liudist`](liudist/) package (Fisher-Rao geometry of Liouville laws;
pulls in JAX, and benefits from a GPU JAX build) — installed via the `[rafinat]` extra.

```python
import numpy as np, rafinat
X = np.random.poisson(0.5, size=(2000, 500)).astype(float)   # genes x cells

Z = rafinat.compost().fit_transform(X)      # compost p=0, trimmed
H = rafinat.hellnorm().fit_transform(X)     # Hellnorm, trimmed reference
R = rafinat.rafinat().fit_transform(X)      # rafinat; (genes + 1) x cells
```

### fit / transform

`fit` estimates and **freezes** the two data-driven pieces — the concentration `r` and the per-cell
reference level `beta` — and `transform` re-applies that frozen normalization. Because `beta` is
per-cell, `transform` expects a matrix of the same `(genes, cells)` shape it was fitted on (re-fit
for a different gene/cell set). `fit_transform(X)` is the one-shot form.

```python
tr = rafinat.compost(p=0.5).fit(X)     # estimate & freeze tr.r_ and tr.beta_
Z  = tr.transform(X)                    # apply; == tr.fit_transform(X)

from sklearn.pipeline import Pipeline   # optional — also works without scikit-learn installed
pipe = Pipeline([("normalize", rafinat.compost())])
Z = pipe.fit_transform(X)
```

scikit-learn is an **optional** extra: if installed, the transformers inherit
`BaseEstimator` / `TransformerMixin` (full `Pipeline` / `clone` / `get_params` support); otherwise a
light built-in shim provides the same `fit` / `transform` / `fit_transform` / `get_params` API.

## Choosing the operating point

The benchmark-winning defaults are baked in, but every knob is a constructor argument:

```python
rafinat.compost(p=0.0)               # default: digamma log-corner, trimmed centering
rafinat.compost(p=0.5)               # posterior sqrt mean (order-1/2 power-mean cell size)
rafinat.compost(trim=0.0)            # ordinary (non-trimmed) per-cell centering
rafinat.compost(r="mle")             # pooled DM-MLE concentration instead of the isscr-matched r
rafinat.compost(zscore=True)         # + per-gene z-score (the optional '->Z' standardization)

rafinat.hellnorm(reference="uniform")     # classic log-map references: uniform / extrinsic / frechet
rafinat.hellnorm(trim=0.5)                # lighter top-trim

rafinat.rafinat(beta="atop10")       # simulation-leaning cell-size estimator
rafinat.rafinat(r_comp="mle")        # smoother directional concentration
rafinat.rafinat(zcomp=True)          # + per-gene z-score of the composition rows ('-> coordZ')
```

Each of these returns a transformer; call `.fit_transform(X)` (or `.fit(X)` then `.transform(X)`)
on it. After fitting, the estimated values are exposed as fitted attributes (trailing underscore):
`compost.r_` / `compost.beta_`, `rafinat.r_comp_` / `rafinat.ref_` / `rafinat.C_` /
`rafinat.beta_`.

**The optional per-gene standardization (`compost(zscore=True)`, `rafinat(zcomp=True)`) is disabled
by default**, matching the benchmark's shipped defaults.

## Install

```bash
pip install rafinat                # compost / hellnorm (numpy + scipy only)
pip install "rafinat[rafinat]"     # + the rafinat() method — adds the liudist backend (pulls in JAX)
```

`compost` and `hellnorm` need only `numpy`/`scipy`; the heavy `liudist` + JAX stack is pulled in
**only** by the `[rafinat]` extra, i.e. only if you use the `rafinat()` method. 



## Method provenance

- **compost** — `digamma(r + x)` with `r = 1/(4·alpha) + 1/2` (so `digamma(r+x) ≈ log(x + 1/(4·alpha))`), minus a per-cell location estimated on the **low-expression bulk**
  (the top high-expression genes — the biologically variable ones — are dropped from the cell-size
  estimate). 
- **hellnorm** — `sqrt(x / sum x)` minus a top-trimmed per-cell mean (the sqrt-geometry analog of
  compost's trimmed centering). Dominates the classic uniform/extrinsic/Frechet references.
- **rafinat** — the Fisher-Rao Liouville distance factors as
  `d² = d_composition² + C·(d ln β)²`; rafinat realises it as the stack
  `[ composition arc length (depth-normalized, sharp r_snr) ; sqrt(C)·ln(β_c) ]`, with the radial
  weight `C = genes · r_mle` decoupled from the (sharp) directional concentration so the cell-size
  axis stays alive at a parameter-free weight.
