CycleVI: Isolating cell cycle variation with an interpretable deep generative model
Project description
CycleVI
CycleVI is a deep generative model that isolates cell cycle variation in single-cell RNA-seq data. It learns a disentangled latent space where two dimensions capture the cell's circular position in the cell cycle (z_cycle) and the remaining dimensions capture everything else (z_other) — so your downstream analyses (clustering, UMAP, integration) are no longer confounded by the cell cycle.
In practice, CycleVI gives you two things for every cell:
- a cell cycle angle — a continuous coordinate describing where the cell sits in the cycle, and
- a cell cycle-free embedding — a representation you can use in place of PCA for everything downstream.
Preprint: CycleVI: Isolating cell cycle variation with an interpretable deep generative model
Installation
pip install cyclevi
This installs the cyclevi command-line tool and the Python package. To run from a clone of the repository without installing:
python -m cyclevi
Dependencies (installed automatically): PyTorch, scvi-tools, anndata, scanpy, scikit-learn, scipy, click, numpy, pandas. A GPU is optional but strongly recommended — see Runtime below.
What your input data needs
Before running anything, make sure your data meets these expectations:
- Raw counts, not normalized or log-transformed values. CycleVI models counts directly; the matrix should contain integer-like UMI/read counts.
- Cells as rows, genes as columns (the standard AnnData orientation). If your matrix is the other way round, add
--transpose. - Gene names as
var_names— either gene symbols (e.g.MKI67) or Ensembl IDs (e.g.ENSG00000148773) work automatically, and CycleVI detects which one you and matches the correct built-in cell cycle marker list. Human data works out of the box; for other organisms, or other gene ID schemes, supply your own marker lists (seeprepare).
Standard quality-control filtering (removing low-count cells and rarely-expressed genes) before running CycleVI is recommended, just as for any scRNA-seq workflow.
See Supported input formats for the file types you can load.
Quickstart
One command runs the full pipeline — load data, compute the cell cycle initialization, train the model, and save outputs:
cyclevi run --input data.h5ad --output results/
That's all most users need. The four files written to results/ are described next.
Understanding the output
Every command that produces results writes these files to the output directory:
| File | What it contains | How to use it |
|---|---|---|
cycle_angles.csv |
One angle per cell, in radians (−π to π). The cell's inferred position on the cell cycle. | This is the main result. Treat it like a cell cycle "pseudotime" — sort cells by it, color a UMAP by it, or test genes for cycle-dependent expression. |
latent_other.csv |
A per-cell embedding (z_1 … z_N) with the cell cycle removed. |
Use this in place of PCA for clustering, UMAP, and integration, so the cell cycle no longer drives your results. |
latent_cycle.csv |
The 2D coordinates (z_cycle_x, z_cycle_y) that the angle is computed from. |
Plot z_cycle_y against z_cycle_x to see cells arranged on a circle (the cell cycle); the angle in cycle_angles.csv is arctan2(z_cycle_y, z_cycle_x). |
model/ |
The trained model. | Reload later with CycleVI.load or cyclevi extract to get representations for new cells without retraining. |
Each CSV is indexed by cell barcode, so you can join it straight back onto your AnnData with adata.obs.
Which command should I use?
- Just want results? Use
cyclevi run— it does everything in one step. - Want to inspect the cell cycle initialization, or reuse it across several training runs? Split the work with
cyclevi preparethencyclevi train. - Already have a trained model and want representations for more cells? Use
cyclevi extract.
Step-by-step workflow
For more control, or to reuse the prepared file across multiple training runs:
# Step 1: compute the cell cycle initialization, save a prepared file
cyclevi prepare --input data.h5ad --output prepared.h5ad
# Step 2: train (layer and column names are read from the file automatically)
cyclevi train --input prepared.h5ad --output results/
The prepared file records all the settings it needs internally, so cyclevi train requires no extra flags.
Runtime
CycleVI trains a neural network and benefits greatly from a GPU. It automatically uses an NVIDIA (CUDA) or Apple Silicon (MPS) GPU if one is available, and prints which device it is using when training starts. On CPU, training still works but is considerably slower — consider lowering --n-epochs for a quick test run, or use Google Colab for free GPU access.
Supported input formats
All commands that accept --input support the following:
| Format | Description |
|---|---|
.h5ad |
AnnData HDF5 file |
.h5 |
10x Genomics HDF5 file (Cell Ranger output) |
.loom |
Loom file (e.g. from velocyto) |
.mtx |
MatrixMarket sparse matrix (cells as rows by default) |
| directory | 10x Genomics MTX folder (matrix.mtx, barcodes.tsv, features.tsv) or Single Cell Expression Atlas folder (*.mtx, *.mtx_cols, *.mtx_rows) — detected automatically |
.csv / .tsv |
Delimited text (cells as rows, genes as columns by default) |
For .mtx and .csv/.tsv files where genes are rows instead of columns, add --transpose.
Commands
cyclevi run
End-to-end pipeline in one step — prepare, train, and save all outputs.
cyclevi run --input data.h5ad --output results/
Accepts every option from prepare and train combined (see below). Outputs are described in Understanding the output.
cyclevi prepare
Compute the cell cycle initialization and save a prepared .h5ad file ready for training.
CycleVI infers cell cycle position itself during training — this step provides the initial guesses the model starts from. It scores cells against known S and G2/M marker genes, derives a continuous angle from those scores, and quantile-transforms it so cells are spread evenly around the circle.
cyclevi prepare --input data.h5ad --output prepared.h5ad
Options:
| Option | Default | Description |
|---|---|---|
--input |
— | Input file or directory |
--output |
— | Path to save the prepared .h5ad file |
--gene-id-type |
auto |
Gene identifier type: auto detects from var_names, or set ensembl / symbol explicitly. Ignored when --s-genes and --g2m-genes are provided. |
--s-genes |
— | Text file with S-phase marker genes, one per line. Overrides the bundled gene list (must be paired with --g2m-genes). |
--g2m-genes |
— | Text file with G2/M-phase marker genes, one per line. Must be provided together with --s-genes. |
--var-names |
gene_symbols |
For 10x MTX directories: use gene_symbols or gene_ids as var_names. |
--transpose |
off | Transpose matrix after loading (for MTX/CSV with genes as rows). |
--counts-layer |
counts |
Layer name for storing a copy of the raw counts. |
--phase-key |
phase |
adata.obs key for discrete phase labels (G1 / S / G2M). |
--angle-key |
cycle_angle |
adata.obs key for the raw arctan2 angle. |
--uniform-angle-key |
cycle_angle_uniform |
adata.obs key for the quantile-transformed angle (used for training). |
Custom marker genes (non-human data)
By default CycleVI uses the bundled Regev-lab human marker genes (Ensembl IDs or gene symbols, detected automatically). For a different organism, supply your own plain-text lists with one gene name per line:
cyclevi prepare --input data.h5ad --output prepared.h5ad \
--s-genes my_s_genes.txt \
--g2m-genes my_g2m_genes.txt
Both flags must be given together, and the gene names must match your dataset's var_names. prepare reports how many of your marker genes were found in the data — check this number is reasonable.
Columns added to adata.obs
| Column | Description |
|---|---|
phase |
Initial phase label: G1, S, or G2M |
S_score |
Continuous S-phase score |
G2M_score |
Continuous G2/M-phase score |
cycle_angle |
Raw angle: arctan2(G2M_score, S_score) in [0, 2π] |
cycle_angle_uniform |
Quantile-transformed angle, uniformly distributed in [0, 2π] |
These column names are stored in adata.uns["cyclevi"] so that cyclevi train picks them up automatically.
cyclevi train
Train a CycleVI model from a prepared .h5ad file.
cyclevi train --input prepared.h5ad --output results/
If the file came from cyclevi prepare, no extra flags are needed — the counts layer and column names are read from the file automatically.
Options:
| Option | Default | Description |
|---|---|---|
--input |
— | Prepared .h5ad file |
--output |
— | Output directory |
--batch-key |
None |
adata.obs column for experimental batch (enables batch correction). |
--labels-key |
None |
adata.obs column for cell type labels. |
--cycle-label-key |
auto | adata.obs column for phase labels — read from the file if prepared with cyclevi prepare. |
--cycle-angle-key |
auto | adata.obs column for cycle angle — read from the file if prepared with cyclevi prepare. |
--layer |
auto | AnnData layer with raw counts — read from the file if prepared with cyclevi prepare. |
--n-latent |
10 |
Total latent dimensions (the first 2 are always z_cycle). |
--n-hidden |
128 |
Hidden units per encoder/decoder layer. |
--n-layers |
1 |
Number of encoder/decoder layers. |
--n-epochs |
400 |
Training epochs. |
--batch-size |
128 |
Mini-batch size. |
--lr |
1e-3 |
Learning rate. |
Outputs are described in Understanding the output.
cyclevi extract
Extract latent representations from a previously saved model — useful for applying a trained model to new cells without retraining.
cyclevi extract \
--input data.h5ad \
--model results/model \
--output results/
Writes the same three CSV files as train.
| Option | Description |
|---|---|
--input |
Input .h5ad file |
--model |
Directory of a saved CycleVI model |
--output |
Directory to write output CSVs |
Python API
Prefer working in a notebook or script? The same pipeline is available directly:
import numpy as np
from cyclevi import CycleVI
from cyclevi.prepare import load_adata, compute_phase_initialization
# 1. Load data (raw counts in .X) and compute the cell cycle initialization
adata = load_adata("data.h5ad")
compute_phase_initialization(adata)
# For a non-human organism, supply your own marker lists instead:
# compute_phase_initialization(adata, s_genes_file="s.txt", g2m_genes_file="g2m.txt")
# 2. Train
CycleVI.setup_anndata(
adata,
layer="counts",
cycle_initiation_label_key="phase",
cycle_initiation_angle_key="cycle_angle_uniform",
)
model = CycleVI(adata)
model.train(max_epochs=400)
# 3. Extract representations
z = model.get_latent_representation(adata)
z_cycle = z[:, :2] # 2D circular cell cycle coordinates
z_other = z[:, 2:] # cell cycle-free embedding
angles = np.arctan2(z[:, 1], z[:, 0]) # cell cycle angle per cell
For a full walkthrough, see Tutorial.ipynb or Tutorial_colab.ipynb (ready to run on Google Colab).
Feedback
Questions or comments? Contact Gustavo S. Jeuken or open an issue on GitHub.
License
BSD 3-Clause License
Citation
If you use CycleVI in a publication, please cite:
CycleVI: Isolating cell cycle variation with an interpretable deep generative model
Pia Mozdzanowski, Marcel Tarbier, Gustavo S. Jeuken
bioRxiv 2025.11.04.686009; doi: https://doi.org/10.1101/2025.11.04.686009
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cyclevi-0.2.1.tar.gz.
File metadata
- Download URL: cyclevi-0.2.1.tar.gz
- Upload date:
- Size: 3.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1c2cc17f8243178a6bf1d65d7280b7e8fb263debba4a4fb621531c7941ad729
|
|
| MD5 |
13f823589cfe419064bfbaaee9e578ed
|
|
| BLAKE2b-256 |
7525859ad1f8f400adb302d9d5e0eae1c2a6909e9a72bc506ebce4ebb24c0d9c
|
File details
Details for the file cyclevi-0.2.1-py3-none-any.whl.
File metadata
- Download URL: cyclevi-0.2.1-py3-none-any.whl
- Upload date:
- Size: 32.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c9799b9cf2a9b2360067989ba6a321d694f42d315750878e435f1800817d753f
|
|
| MD5 |
ec93406444dd3fb01ce0f6ae813dea82
|
|
| BLAKE2b-256 |
27972a527ea76149a620f15a329053dd8f9a006d5221cdfef75258d50d7d3aac
|