Inverse optimal transport for single-cell state transitions and pseudotime
bioIOT is an R implementation of semi-relaxed
inverse optimal transport (IOT) for single-cell trajectory analysis.
Given state-transition features, source/target state masses and observed
transitions, it learns feature weights θ such that the soft-marginal OT
plan induced by the linear cost C = -einsum(φ, θ)
reproduces the data — and turns the fit into state transition matrices,
random-walk pseudotime and ggplot2 visualisation.
The solver was developed and validated as part of a research project on treatment-resistance state transitions, and is packaged here for general use.
# from GitHub
remotes::install_github("XTSgreen/bioIOT-R")install.packages(".", repos = NULL, type = "source")
# or: R CMD build . && install.packages("bioIOT_0.2.2.tar.gz", repos = NULL)library(bioIOT)
# 1) A reproducible synthetic dataset with ground truth
sim <- simulate_iot_states(K = 6, seed = 1) # or data(demo_iot_states)
# 2) Fit feature weights: exact implicit gradients + two-stage debias +
# multi-restart. Scenarios may differ in the number of states.
fit <- fit_iot(sim$phi, sim$a, sim$b, sim$T_true, n_restart = 2)
fit; summary(fit)
# 3) Trajectory layer
Q <- transition_matrix(fit) # (K, K) transitions
pt <- pseudotime_from_transition(Q, root = "S1") # random-walk pseudotime
# 4) Visualisation
plot_transition_heatmap(Q) # annotated heatmap
plot_transition_flow(Q, sim$embedding) # CellRank-style arrows
plot_theta(fit) # weights + support
# 5) Straight from cell-level objects
res <- runIOT(sim$cell_embedding, sim$cell_state,
from = sim$cell_time == "t0", to = sim$cell_time == "t1",
root = "S1")
# SingleCellExperiment: runIOT(sce, state_col = "state", time_col = "time",
# from = "t0", to = "t1", dimred = "PCA")
# Seurat: runIOT(obj, group.by = "state", split.by = "time",
# from = "t0", to = "t1", reduction = "pca")Without observed transitions T_obs,
runIOT() solves the plan with uniform feature weights; with
T_obs (e.g. from lineage or clone data) it fits the weights
first.
The showcase directory runs the full pipeline on the human skeletal-muscle myoblast time course (HSMMSingleCell, 271 cells × 47k genes, 0/24/48/72 h; Shin et al. 2015) and benchmarks it against Slingshot:
| Pseudotime metric (Spearman) | value |
|---|---|
| bioIOT pseudotime ~ known Hours (cells) | 0.251 |
| Slingshot pseudotime ~ known Hours (cells) | 0.253 |
| bioIOT state pseudotime ~ Slingshot state pseudotime | 0.600 |
With 30 cells of sampling noise per state, fit_iot
recovers the true transition matrix ~3× more accurately than the raw
noisy transitions (mean per-row L1 0.013 vs 0.039; 50 replicates).
Provenance and licensing of all showcase data are documented in showcase/LICENSE_AUDIT.md.
| Layer | Functions |
|---|---|
| Core solver | soft_sinkhorn(), row_conditional(),
make_cost(), row_ce_loss(),
zscore_phi() |
| Fitting | fit_iot() (with print/summary
methods) |
| Trajectory | transition_matrix(),
pseudotime_from_transition(),
build_state_features() |
| Single-cell | runIOT() — matrix / SingleCellExperiment / Seurat |
| Visualisation | plot_transition_heatmap(),
plot_transition_flow(), plot_theta(),
plot_pathway_trend() |
| Demo data | simulate_iot_states(),
demo_iot_states |
| Bulk cohorts | pathway_markers, score_pathways(),
collapse_probes(), gsm_id() |
bioIOT solves
min_P <C, P> − eps·H(P) + mu·KL(col(P) ‖ b) s.t. P·1 = a
with a hard source-side row marginal and a KL-anchored column marginal:
mu → ∞ recovers hard-marginal OT (pure column features
unidentifiable);mu → 0 recovers a plain row-softmax (no
target-composition anchoring);mu interpolates the two — the paper’s working
point is mu = 0.5, eps = 1.0, lam = 0.05.A vignette is bundled with the package
(browseVignettes("bioIOT") after installation).
library(testthat); library(bioIOT)
test_dir("tests/testthat") # from the repository rootIf you use bioIOT, please cite:
@misc{dong2026bioiotr,
author = {Dong, Han},
title = {bioIOT: Inverse Optimal Transport for Single-Cell
Trajectory Analysis},
year = {2026},
howpublished = {\url{https://github.com/XTSgreen/bioIOT-R}},
note = {R package version 0.2.2}
}MIT © 2026 Han Dong (XTSgreen)