Single-cell multiomics technologies allow simultaneous or parallel measurement of multiple cellular features — most commonly gene expression (scRNA-seq) and chromatin accessibility (scATAC-seq). Simulating multiomics data presents unique challenges because algorithms must preserve not only the distinct statistical properties of each individual modality, but also the biological coupling and regulatory relationships between them.
scSimEval is designed to evaluate simulation fidelity
across three distinct multiomics experimental designs,
each with a different level of cell-level pairing between modalities.
Understanding which design your simulated data belongs to is essential
for selecting the correct evaluation functions.
Definition: Both scRNA-seq and scATAC-seq are measured simultaneously from the exact same individual cells. Each cell barcode in the RNA matrix has a corresponding and biologically meaningful entry at the same column index in the ATAC matrix.
Technologies: 10x Chromium Multiome, SHARE-seq, SNARE-seq, Paired-seq, ASTAR-seq
Simulators: scMultiSim (Li et al., Nat Methods 2023), SymSim2+ATAC, MultiSim
What scSimEval can evaluate:
Definition: scRNA-seq and scATAC-seq are profiled from different cells derived from the same biological tissue, cell line, or sample. Cell barcodes do not correspond between modalities — column i of the RNA matrix and column i of the ATAC matrix represent entirely different cells.
Technologies: Bulk ATAC + scRNA-seq, independent 10x scATAC + scRNA experiments on the same tissue, sci-ATAC-seq paired with scRNA-seq on sibling aliquots
Simulators: Splatter (RNA only) + ArchR (ATAC only) used separately, AMULET
What scSimEval can evaluate:
evaluate_cross_modal_prediction)calc_coexpression_module_fidelity)calc_peak_coaccessibility_fidelity)calc_network_jaccard)calc_accessibility_profile_concordance)calc_foscttm)calc_cross_modal_generation)calc_atac_rna_coupling)calc_cross_modality_correlation)Note: Using
evaluate_multiomics_accuracy()directly on unpaired data is NOT recommended because it internally calls paired metrics. Instead, useevaluate_simulation_accuracy()per modality plus the population-level functions listed above, and combine results withevaluate_multiple_datasets().
Definition: A subset of cells has measurements in both modalities, while the remaining cells have measurements in only one modality. This arises from experimental designs that pool paired and unpaired samples, or from modality dropout in co-assay experiments.
Technologies: DOGMA-seq (DNA + RNA + protein + ATAC, but with high dropout), mosaic pooling designs (mixing Multiome cells with scRNA-only or scATAC-only cells), partially overlapping patient cohorts
Simulators: MultiVI (Lopez et al., Nat Methods 2023), Cobolt, MOFA+ imputation
What scSimEval can evaluate:
| Metric / Function | Paired | Unpaired | Mosaic (paired subset) |
|---|---|---|---|
evaluate_simulation_accuracy() per modality |
✅ | ✅ | ✅ |
evaluate_clustering_metrics() |
✅ | ✅ | ✅ |
evaluate_batch_metrics() |
✅ | ✅ | ✅ |
evaluate_deg_fidelity() |
✅ | ✅ | ✅ |
evaluate_trajectory_metrics() |
✅ | ✅ | ✅ |
calc_coexpression_module_fidelity() |
✅ | ✅ | ✅ |
calc_peak_coaccessibility_fidelity() |
✅ | ✅ | ✅ |
calc_accessibility_profile_concordance() |
✅ | ✅ | ✅ |
calc_network_jaccard() |
✅ | ✅ | ✅ |
evaluate_cross_modal_prediction() |
✅ | ✅ | ✅ |
calc_modality_alignment() |
✅ | ⚠️ needs integration first | ✅ on subset |
calc_foscttm() |
✅ | ❌ | ✅ on subset |
calc_cross_modal_generation() |
✅ | ❌ | ✅ on subset |
calc_atac_rna_coupling() |
✅ | ❌ | ✅ on subset |
calc_cross_modality_correlation() |
✅ | ❌ | ✅ on subset |
evaluate_multiomics_accuracy() |
✅ | ❌ direct use | ✅ on subset |
scSimEval includes a paired scRNA-seq + scATAC-seq
dataset (example_multiomics) where every column in the RNA
matrix corresponds to the same cell in the ATAC matrix:
library(scSimEval)
# Load packaged paired multiomics example dataset
data(example_multiomics)
cat("Reference RNA dimensions:", dim(example_multiomics$ref_multi$rna), "\n")
#> Reference RNA dimensions: 60 80
cat("Reference ATAC dimensions:", dim(example_multiomics$ref_multi$atac), "\n")
#> Reference ATAC dimensions: 60 80
cat("Simulated RNA dimensions:", dim(example_multiomics$sim_multi$rna), "\n")
#> Simulated RNA dimensions: 60 80
cat("Simulated ATAC dimensions:", dim(example_multiomics$sim_multi$atac), "\n")
#> Simulated ATAC dimensions: 60 80
cat("Cell types:", levels(example_multiomics$cell_types), "\n")
#> Cell types: TypeA TypeBThe dataset contains:
ref_multi: Named list rna (count matrix)
and atac (peak accessibility matrix), both measured from
the same cells.sim_multi: Simulated paired rna and
atac matrices.cell_types: Cell type annotations shared across both
modalities.batch_info: Batch labels shared across both
modalities.resource_stats: Hardware logs (runtime in seconds and
peak RAM in MiB).FOSCTTM (Fraction Of Samples Closer Than The True Match) quantifies whether paired cells are each other’s nearest neighbors in joint latent space. Valid only for paired data.
# Compute reduced representations (PCA) for RNA and ATAC
pca_rna <- prcomp(t(example_multiomics$ref_multi$rna), rank. = 5)$x
pca_atac <- prcomp(t(example_multiomics$ref_multi$atac), rank. = 5)$x
foscttm_res <- calc_foscttm(x = pca_rna, y = pca_atac)
cat("Bidirectional FOSCTTM score:", round(foscttm_res$foscttm, 4), "\n")
#> Bidirectional FOSCTTM score: 0.455
cat("Top-1 Match Rate (Match@1):", round(foscttm_res$match_at_1, 4), "\n")
#> Top-1 Match Rate (Match@1): 0.0125A score of 0.00 indicates perfect alignment; 0.50 represents random chance.
Tests whether chromatin accessibility profiles in simulated ATAC can predict cell types annotated in RNA. This metric works for all three data types because it uses population-level kNN classification:
transfer_res <- evaluate_cross_modal_prediction(
mod1_data = example_multiomics$sim_multi$atac,
cell_types = example_multiomics$cell_types
)
cat("Cross-Modal Prediction Accuracy:", round(transfer_res$cross_modal_accuracy, 4), "\n")
#> Cross-Modal Prediction Accuracy: 1
cat("Cross-Modal Macro F1 Score:", round(transfer_res$cross_modal_F1, 4), "\n")
#> Cross-Modal Macro F1 Score: 1evaluate_multiomics_accuracy()For fully paired data, the master function coordinates all 8 categories:
master_eval <- evaluate_multiomics_accuracy(
ref_multi = example_multiomics$ref_multi,
sim_multi = example_multiomics$sim_multi,
cell_types = example_multiomics$cell_types,
elapsed_time = example_multiomics$resource_stats$elapsed_time,
memory_mb = example_multiomics$resource_stats$memory_mb,
verbose = FALSE
)
#> [1] "Downloading Reference Distribution: \n"
head(master_eval$benchmark_summary_table)
#> Category Property Metric Value Modality
#> 1 Distributional Properties library_size MAD 1.500000e+01 rna
#> 2 Distributional Properties library_size KS 2.125000e-01 rna
#> 3 Distributional Properties library_size MAE 1.506250e+01 rna
#> 4 Distributional Properties library_size RMSE 1.571504e+01 rna
#> 5 Distributional Properties library_size OV 8.370258e-01 rna
#> 6 Distributional Properties library_size Bhattacharyya 1.188839e-04 rnaFor unpaired data, scRNA-seq and scATAC-seq come from different cells. The recommended workflow is to evaluate each modality independently and combine results.
library(scSimEval)
data(example_scrna) # RNA modality
data(example_scatac) # ATAC modality (different cells)
# ── Step 1: Evaluate each modality independently ──────────────────────────────
rna_eval <- evaluate_simulation_accuracy(
ref_data = example_scrna$ref,
sim_data = example_scrna$sim,
compute_bivariate = FALSE,
verbose = FALSE
)
atac_eval <- evaluate_simulation_accuracy(
ref_data = example_scatac$ref,
sim_data = example_scatac$sim,
compute_bivariate = FALSE,
verbose = FALSE
)
# ── Step 2: Population-level cross-modal metrics (no cell pairing needed) ─────
# 2a. Co-expression module fidelity (RNA only, no pairing needed)
module_fid <- calc_coexpression_module_fidelity(
ref_mat = example_scrna$ref,
sim_mat = example_scrna$sim
)
cat("Module correlation r:", round(module_fid$module_correlation_r, 4), "\n")
# 2b. Peak co-accessibility fidelity (ATAC only, no pairing needed)
coaccess_fid <- calc_peak_coaccessibility_fidelity(
ref_atac = example_scatac$ref,
sim_atac = example_scatac$sim
)
cat("RV coefficient:", round(coaccess_fid$rv_coefficient, 4), "\n")
# 2c. Cross-modal label transfer (uses ATAC features to predict RNA-based labels)
# NOTE: cell_types come from the RNA experiment; applied to ATAC data for transfer
label_transfer <- evaluate_cross_modal_prediction(
mod1_data = example_scatac$sim,
cell_types = example_scatac$cell_types
)
cat("Label transfer accuracy:", round(label_transfer$cross_modal_accuracy, 4), "\n")
# ── Step 3: Consolidate and compare across modalities ─────────────────────────
dataset_collection <- list(
"Method_A-RNA" = list(ref = example_scrna$ref, sim = example_scrna$sim),
"Method_A-ATAC" = list(ref = example_scatac$ref, sim = example_scatac$sim)
)
consolidated <- evaluate_multiple_datasets(
datasets = dataset_collection,
pair_by_prefix = TRUE,
compute_bivariate = FALSE,
verbose = FALSE
)
print(consolidated$dataset_overview)Key point: Do NOT call
calc_foscttm(),calc_cross_modal_generation(),calc_atac_rna_coupling(), orcalc_cross_modality_correlation()on unpaired data. These functions assume row/columniof the RNA matrix matches row/columniof the ATAC matrix — an assumption that is biologically false for unpaired experiments.
For mosaic data (where only some cells have both modalities), the strategy is:
evaluate_multiomics_accuracy() or individual Category 7
functions.# Suppose your mosaic simulator output looks like this:
# - sim_rna_full: RNA matrix for ALL cells (including RNA-only cells)
# - sim_atac_full: ATAC matrix for ALL cells (including ATAC-only cells)
# - paired_cell_idx: logical or integer index of cells with BOTH modalities
# ── Step 1: Identify co-assayed cell subset ───────────────────────────────────
# (This index should come from your simulator's output metadata)
# Example: first 200 cells are co-assayed, rest are single-modality
paired_idx <- 1:200
sim_rna_paired <- sim_rna_full[, paired_idx]
sim_atac_paired <- sim_atac_full[, paired_idx]
ref_rna_paired <- ref_rna_full[, paired_idx]
ref_atac_paired <- ref_atac_full[, paired_idx]
# ── Step 2: Paired coupling metrics on the co-assayed subset ──────────────────
paired_eval <- evaluate_multiomics_accuracy(
ref_multi = list(rna = ref_rna_paired, atac = ref_atac_paired),
sim_multi = list(rna = sim_rna_paired, atac = sim_atac_paired),
cell_types = cell_types_paired,
verbose = FALSE
)
# ── Step 3: Full unimodal evaluation on the complete matrices ─────────────────
rna_full_eval <- evaluate_simulation_accuracy(
ref_data = ref_rna_full,
sim_data = sim_rna_full,
compute_bivariate = FALSE,
verbose = FALSE
)
atac_full_eval <- evaluate_simulation_accuracy(
ref_data = ref_atac_full,
sim_data = sim_atac_full,
compute_bivariate = FALSE,
verbose = FALSE
)
# ── Step 4: Population-level cross-modal metrics (full matrices) ──────────────
module_fid <- calc_coexpression_module_fidelity(ref_rna_full, sim_rna_full)
coaccess <- calc_peak_coaccessibility_fidelity(ref_atac_full, sim_atac_full)scSimEval visualization functions work seamlessly across
unimodal, paired, and unpaired multiomics configurations:
# Multi-dataset consolidated benchmark bubble matrix
plot_benchmark_bubble_matrix(
data = consolidated,
title = "Benchmarking Multiomics Simulators"
)
# Evaluation summary horizontal bar matrix
plot_evaluation_summary(
data = consolidated,
title = "Multiomics Simulator Leaderboard"
)To visually examine whether simulated cells maintain the biological manifold of empirical data across modalities, researchers can project reference and simulated cells into low-dimensional UMAP, t-SNE, or PCA coordinates:
# Compute low-dimensional embeddings for the scRNA-seq modality
emb_rna <- compute_dataset_embeddings(
reference = example_multiomics$ref_multi$rna,
simulated = list("Multi_Sim" = example_multiomics$sim_multi$rna),
reduction = "pca",
n_pcs = 5,
cell_types = example_multiomics$cell_types
)
# Plot multiomics cell embeddings
plot_dataset_embeddings(
embedding_data = emb_rna,
reduction = "pca",
layout = "side_by_side",
color_by = "cell_type"
)
# Quantitative embedding quality metrics
knitr::kable(compute_embedding_quality_metrics(emb_rna), digits = 4)| Dataset | Role | Cells (N) | Mean Silhouette | ARI (Cluster Fidelity) | Mean Library Size | Mean Detected Features | Library Size Diff (%) |
|---|---|---|---|---|---|---|---|
| Reference | Reference | 80 | -0.0101 | -0.0027 | 256.3 | 49.2 | 0.00 |
| Multi_Sim | Simulated | 80 | 0.0037 | -0.0071 | 242.3 | 48.3 | 5.46 |
| Data Type | Example Technologies | evaluate_multiomics_accuracy() |
Category 7 Metrics | Recommended Function |
|---|---|---|---|---|
| Paired | 10x Multiome, SHARE-seq | ✅ Direct | All 6 metrics | evaluate_multiomics_accuracy() |
| Unpaired | Independent scRNA + scATAC | ❌ Not valid | Modularity, label transfer, network only | evaluate_simulation_accuracy() × 2 +
evaluate_multiple_datasets() |
| Mosaic | DOGMA-seq, mosaic pooling | ✅ On paired subset | All 6 metrics on paired subset | Subset first, then evaluate_multiomics_accuracy() |
The most important rule: Cell-pairing metrics (FOSCTTM, cross-modal generation, peak-to-gene coupling) are only scientifically valid when the RNA and ATAC columns correspond to the same physical cells. For unpaired and mosaic data, rely on population-level metrics and per-modality unimodal evaluation.
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] scSimEval_0.99.7 rmarkdown_2.32
#>
#> loaded via a namespace (and not attached):
#> [1] Rdpack_2.6.6 DBI_1.3.0
#> [3] httr2_1.3.0 testthat_3.3.2
#> [5] rlang_1.3.0 magrittr_2.0.5
#> [7] clue_0.3-68 ade4_1.7-24
#> [9] otel_0.2.0 e1071_1.7-17
#> [11] matrixStats_1.5.0 compiler_4.6.1
#> [13] RSQLite_3.53.3 flexmix_2.3-21
#> [15] vctrs_0.7.3 pkgconfig_2.0.3
#> [17] fastmap_1.2.0 arm_1.15-3
#> [19] dbplyr_2.6.0 XVector_0.53.0
#> [21] labeling_0.4.3 nloptr_2.2.1
#> [23] purrr_1.2.2 bit_4.6.0
#> [25] bluster_1.23.1 modeltools_0.2-25
#> [27] xfun_0.61 cachem_1.1.0
#> [29] overlapping_2.5 jsonlite_2.0.0
#> [31] blob_1.3.0 DelayedArray_0.39.8
#> [33] fpc_2.2-15 BiocParallel_1.47.0
#> [35] prabclus_2.3-5 irlba_2.4.1
#> [37] parallel_4.6.1 cluster_2.1.8.3
#> [39] R6_2.6.1 bslib_0.12.0
#> [41] RColorBrewer_1.1-3 limma_3.99.1
#> [43] boot_1.3-32 diptest_0.77-2
#> [45] brio_1.1.5 GenomicRanges_1.65.4
#> [47] jquerylib_0.1.4 Rcpp_1.1.2
#> [49] Seqinfo_1.3.2 SummarizedExperiment_1.43.0
#> [51] knitr_1.52 IRanges_2.47.5
#> [53] igraph_2.3.4 nnet_7.3-21
#> [55] Matrix_1.7-6 splines_4.6.1
#> [57] tidyselect_1.2.1 abind_1.4-8
#> [59] yaml_2.3.12 MLmetrics_1.1.3
#> [61] randtests_1.0.2 codetools_0.2-20
#> [63] curl_8.0.0 lattice_0.23-1
#> [65] tibble_3.3.1 Biobase_2.73.2
#> [67] withr_3.0.3 S7_0.2.2
#> [69] coda_0.19-4.1 evaluate_1.0.5
#> [71] waddR_1.27.0 proxy_0.4-29
#> [73] RcppParallel_6.2.1 BiocFileCache_3.3.0
#> [75] kernlab_0.9-33 mclust_6.1.3
#> [77] IsoplotR_7.0 pillar_1.11.1
#> [79] filelock_1.0.3 MatrixGenerics_1.25.0
#> [81] stats4_4.6.1 reformulas_0.4.4
#> [83] generics_0.1.4 philentropy_0.10.0
#> [85] S4Vectors_0.51.10 ggplot2_4.0.3
#> [87] scales_1.4.0 minqa_1.2.8
#> [89] class_7.3-24 glue_1.8.1
#> [91] clValid_0.7 clusterSim_0.51-6
#> [93] maketools_1.3.2 tools_4.6.1
#> [95] BiocNeighbors_2.7.3 robustbase_0.99-7
#> [97] sys_3.4.3 lme4_2.0-6
#> [99] locfit_1.5-9.12 RANN_2.6.3
#> [101] buildtools_1.0.0 grid_4.6.1
#> [103] rbibutils_2.4.1 edgeR_4.99.6
#> [105] SingleCellExperiment_1.35.2 nlme_3.1-171
#> [107] eva_0.2.7 cli_3.6.6
#> [109] S4Arrays_1.13.2 dplyr_1.2.1
#> [111] DEoptimR_1.2-2 gtable_0.3.6
#> [113] sass_0.4.10 digest_0.6.39
#> [115] BiocGenerics_0.59.12 SparseArray_1.13.4
#> [117] farver_2.1.2 memoise_2.0.1
#> [119] htmltools_0.5.9 lifecycle_1.0.5
#> [121] provenance_4.4 statmod_1.5.2
#> [123] bit64_4.8.6 MASS_7.3-66