Metadata-Version: 2.5
Name: rrpv
Version: 0.4.0
Summary: Transition-aware gene variation analysis for single-cell count data
Project-URL: Homepage, https://github.com/xiyangwhu/rrpv
Project-URL: Documentation, https://github.com/xiyangwhu/rrpv#readme
Project-URL: Issues, https://github.com/xiyangwhu/rrpv/issues
Project-URL: Source, https://github.com/xiyangwhu/rrpv
Author: Xi Yang
License-Expression: BSD-3-Clause
License-File: LICENSE
Keywords: anndata,cell-state-transition,gene-variation,scanpy,single-cell
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.10
Requires-Dist: anndata>=0.10
Requires-Dist: matplotlib>=3.7
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scikit-learn>=1.3
Requires-Dist: scipy>=1.10
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Provides-Extra: scanpy
Requires-Dist: scanpy>=1.10; extra == 'scanpy'
Description-Content-Type: text/markdown

# RRPV

RRPV (Reliability-Regularized Projector Variation) analyzes local gene-variation
geometry from a single-cell UMI count matrix. It provides four connected workflows:

- a label-free gene-variation representation for Scanpy-compatible neighborhood analysis;
- rooted graph-geodesic pseudotime on the fitted transition geometry;
- label-free core-state and transition-atlas discovery;
- endpoint-conditioned transition topology and candidate-gene prioritization.

Cell annotations are never used during geometry fitting. Source and target states are
introduced only in the downstream transition query, so one fitted model can support
multiple biological questions.

## Installation

```bash
pip install rrpv
```

Install the optional Scanpy dependency with:

```bash
pip install "rrpv[scanpy]"
```

## Quick start

```python
import scanpy as sc
import rrpv as rp

adata = sc.read_h5ad("counts.h5ad")

# Preserve integer UMI counts before the usual expression workflow.
adata.layers["counts"] = adata.X.copy()
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000, flavor="seurat")

# Label-free fit. The default selects diffusion time from count reproducibility.
rp.tl.fit(
    adata,
    layer="counts",
    gene_mask="highly_variable",
    key_added="rrpv",
)

# Use the primary GVA representation in a standard Scanpy workflow.
sc.pp.neighbors(adata, use_rep="X_rrpv", key_added="rrpv_neighbors")
sc.tl.umap(adata, neighbors_key="rrpv_neighbors")
```

## Rooted pseudotime

Fit the geometry first, then supply a root state to orient its distance graph:

```python
rp.tl.pseudotime(
    adata,
    groupby="cell_state",
    root="progenitor",
    key_added="rrpv_pseudotime",
)

sc.pl.umap(adata, color="rrpv_pseudotime")
```

Multiple root categories or an explicit list/boolean mask of root cells are accepted.
Use `mask_obs` to restrict the calculation to a lineage or connected analysis domain.
Cells in components without a root receive `NaN` and are marked false in
`adata.obs["rrpv_pseudotime_reachable"]`.

The estimator is the multi-source shortest-path distance on
`adata.obsp["rrpv_distances"]`. It does not reduce a branching graph to radial distance
in a low-dimensional display. The root defines orientation after the graph has been
fitted; static counts alone do not identify an arrow of time.

For gene-variation analysis without transition topology, skip the Bures chart and graph:

```python
rp.tl.fit(
    adata,
    layer="counts",
    gene_mask="highly_variable",
    compute_bures=False,
)
rp.tl.gene_variation(adata, model_key="rrpv", calibrated=True)
```

## Endpoint-conditioned analysis

After fitting, define a source and target using any categorical observation column:

```python
rp.tl.transition(
    adata,
    groupby="cell_state",
    source="state_a",
    target="state_b",
    model_key="rrpv",
    key_added="state_a_to_b",
)

rp.tl.rank_transition_genes(
    adata,
    transition_key="state_a_to_b",
    model_key="rrpv",
    key_added="rank_state_a_to_b",
)

result = rp.get.rank_transition_genes_df(adata, key="rank_state_a_to_b")
print(result.head(20))
```

The ranking requires both transition-localized gene variation and an endpoint expression
effect. It is a prioritization score, not a p-value or causal estimate.

## Unsupervised transition atlas

Core states and all graph-supported pairwise transition axes can be enumerated without
cell annotations:

```python
rp.tl.discover_states(adata, n_states="auto")
rp.tl.discover_transitions(adata)

states = rp.get.discovered_states_df(adata)
atlas = rp.get.transition_atlas_df(adata)
rp.pl.transition_atlas(adata, basis="X_umap")

rp.tl.select_transition(
    adata,
    atlas_key="rrpv_transition_atlas",
    axis=atlas.iloc[0]["axis"],
    key_added="selected_axis",
)
```

Automatic state count uses the largest non-trivial eigengap of the normalized RRPV
affinity. Atlas axes have a canonical storage orientation only; biological direction is
not inferred until the analyst supplies external root or endpoint information.

## Coherent two-resolution geometry

RRPV fits one set of local tangent frames and one state-space diffusion operator. Two
resolutions are then used for different estimands:

1. A stable `q`-dimensional projector field defines `X_rrpv`, split-half reliability,
   and the optional Bures transition topology.
2. Full-dimensional tangent-frame contractions retain gene information that would be
   lost by truncating every gene score to `q` dimensions.

Both resolutions share the same counts, global basis, neighborhoods, local rank,
diffusion operator, and selected diffusion time. The distinction is explicit in the
stored metadata: `geometry_dimension` records `q`, while `gene_variation_dimension`
records the full latent dimension.

The automatic selector evaluates exactly the normalized geometry produced by the final
fit:

\[
G_i^{(t)}=(D^tG^{(0)})_i,
\qquad
\Sigma_i^{(t)}=
\frac{G_i^{(t)}}{\operatorname{tr}(G_i^{(t)})}.
\]

Binomial split halves change only the count realization. All split refits use the same
SVD random state, and the earliest diffusion time within one standard error of maximum
centered-projector reliability is selected.

The full mathematical definition is provided in the
[method specification](https://github.com/xiyangwhu/rrpv/blob/main/docs/method.md).

## Gene variation and leverage

For the full loading vector \(b_g\), local frame \(F_i\), and common local rank \(r\),
RRPV computes

\[
a_{ig}^{(0)}=\frac{\lVert F_i b_g\rVert_2^2}{r},
\qquad
v_{ig}=\sqrt{(D^{t^\ast}a_g^{(0)})_i}.
\]

Raw gene variation contains a representability factor
\(\ell_g=\lVert b_g\rVert_2^2\). For cross-gene comparisons,
`rp.tl.gene_variation(..., calibrated=True)` returns

\[
\widetilde v_{ig}=\frac{v_{ig}}{\sqrt{\ell_g+\varepsilon}}.
\]

Genes whose leverage is below the numerical identifiability floor are marked in
`adata.var["rrpv_leverage_identifiable"]` and receive `NaN` calibrated values. They are
excluded from transition ranking instead of being converted to artificial finite scores.

## Scanpy-compatible outputs

| Result | AnnData location |
|---|---|
| Primary square-root diagonal GVA representation | `adata.obsm["X_rrpv"]` |
| Explicit primary alias | `adata.obsm["X_rrpv_diagonal"]` |
| Optional Bures root chart | `adata.obsm["X_rrpv_bures"]` |
| Optional transition connectivity | `adata.obsp["rrpv_connectivities"]` |
| Global cell-state coordinates | `adata.obsm["X_rrpv_state"]` |
| Full local tangent frames | `adata.obsm["rrpv_local_frames"]` |
| Full gene loadings | `adata.varm["rrpv_loadings"]` |
| Gene leverage and identifiability | `adata.var["rrpv_leverage"]`, `adata.var["rrpv_leverage_identifiable"]` |
| Materialized calibrated variation | `adata.layers["rrpv_gene_variation"]` |
| Rooted pseudotime and reachability | `adata.obs["rrpv_pseudotime"]`, `adata.obs["rrpv_pseudotime_reachable"]` |
| Discovered core states | `adata.obs["rrpv_states_state"]`, `adata.uns["rrpv_states"]` |
| Unsupervised transition atlas | `adata.uns["rrpv_transition_atlas"]` |
| Committor and transition weight | `adata.obs["<key>_committor"]`, `adata.obs["<key>_weight"]` |
| Ranked genes | `adata.uns["rank_<key>"]` |
| Model definition and parameters | `adata.uns["rrpv"]` |

## Visualization

```python
rp.pl.diffusion_reliability(adata)
rp.pl.transition(adata, transition_key="state_a_to_b", basis="X_umap")
rp.pl.transition_quiver(adata, transition_key="state_a_to_b", basis="X_umap")
rp.pl.transition_stream(adata, transition_key="state_a_to_b", basis="X_umap")
rp.pl.transition_diagnostics(adata, transition_key="state_a_to_b", basis="X_umap")
rp.pl.gene_trends(
    adata,
    genes=["GENE_A", "GENE_B"],
    transition_key="state_a_to_b",
)
```

Quiver and stream plots display the positive local gradient of the fitted graph
committor. They are not RNA-velocity vectors and do not establish temporal or causal
direction.

## Branch comparison

```python
rp.tl.branch_contrast(
    adata,
    ranking_a="rank_state_a_to_b",
    ranking_b="rank_state_a_to_c",
    key_added="branch_b_vs_c",
)

branch_table = rp.get.branch_contrast_df(adata, key="branch_b_vs_c")
```

## Interpretation

RRPV answers four descriptive questions:

- Which cells have similar local gene-variation geometry?
- How far is each reachable cell from a supplied root along that geometry?
- Which cells lie near an endpoint-conditioned boundary on that geometry?
- Which genes combine boundary-localized variation with endpoint expression change?

Static counts do not identify lineage, arrow of time, or causal regulation. Those claims
require perturbation, lineage tracing, time-resolved sampling, or independent validation.

See the [version migration guide](https://github.com/xiyangwhu/rrpv/blob/main/docs/migration.md)
before reusing fitted objects from an older release.

## Citation and license

Citation metadata are provided in
[`CITATION.cff`](https://github.com/xiyangwhu/rrpv/blob/main/CITATION.cff). RRPV is
distributed under the BSD-3-Clause license.
