# Reference-mapping

#### Compiled: 28/04/2026

# Contents

## 1 Dataset

To illustrate the `Coralysis` reference-mapping method, we will use in this vignette two pancreatic islet datasets distributed through the `scRNAseq` `R` package: MuraroPancreasData (Muraro et al., 2016) and GrunPancreasData (Grun et al., 2016). The former dataset will be the reference and latter the query, i.e., the dataset that will be mapped against the reference.

- MuraroPancreasData (reference): 2,122 cells with cell type labels

- GrunPancreasData (query): 1,064 cells without cell type labels

The datasets were imported below with the function `MuraroPancreasData()` from `GrunPancreasData()` package as a `SingleCellExperiment` object, the object (class) required by most functions in `Coralysis` (see Chapter 4 The SingleCellExperiment class - OSCA manual). The `SCE` object provided comprises `counts` (raw count data), cell `colData` (cell metadata), among other data layers. Both datasets were normalised and subsetted to 500 variable genes with the `scuttle`/`scran` functions `logNormCounts` and `getTopHVGs`, respectively (other preprocessing steps, such as removing unlabeled cells or applying quality control, were carried out following Chapter 3 Using single-cell references - OSCA manual).

Run the code below to import the `R` packages and data required to reproduce this vignette.

    # Packages
    library("scran")
    library("SingleR")
    library("ggplot2")
    library("scRNAseq")
    library("Coralysis")
    library("SingleCellExperiment")

    ### Objects used and QC steps from SingleR book:
    # https://bioconductor.org/books/release/SingleRBook/sc-mode.html

    ## Reference: muraro-pancreas-2016
    # Import data
    ref <- MuraroPancreasData()

    # Remove unnecessary data & labels
    altExps(ref) <- NULL
    ref <- ref[, !is.na(ref$label) & ref$label != "unclear"]

    # Normalise
    ref <- logNormCounts(ref)
    counts(ref) <- NULL

    ## Query: grun-pancreas-2016
    # Import data
    query <- GrunPancreasData()

    # Remove unnecessary data & labels
    query <- addPerCellQC(query)
    qc <- quickPerCellQC(colData(query),
        percent_subsets = "altexps_ERCC_percent",
        batch = query$donor,
        subset = query$donor %in% c("D17", "D7", "D2")
    )
    query <- query[, !qc$discard]
    altExps(query) <- NULL

    # Normalise
    query <- logNormCounts(query)
    counts(query) <- NULL

    # Feature selection with 'scran' package for reference
    nhvg <- 500
    set.seed(123)
    top.hvg <- getTopHVGs(ref, n = nhvg)

    # Subset object
    ref <- ref[top.hvg, ]

    # Filter out genes for query:
    # not required, only done to reduce query size object
    query <- query[top.hvg, ]

## 2 Train reference

The first step in performing reference mapping with `Coralysis` is training a dataset that is representative of the biological system under study. In this example, `ref` and `query` correspond to the `SingleCellExperiment` objects of the reference and query pancreatic islet datasets, MuraroPancreasData and GrunPancreasData, respectively. The reference `ref` is trained through `RunParallelDivisiveICP` function without providing a `batch.label`. In case the reference requires integration, a `batch.label` should be provided. An higher number of `threads` can be provided to speed up computing time depending on the number of cores available. For this example, the algorithm was run 10 times (`L = 10`), but generally, this number should be higher (with the default being `L = 50`). A train set can be built to improve the modeling step in `Coralysis` (by default, `build.train.set=TRUE`), with the respective named `list` of parameters provided to `build.train.params`. In this case, the number of variable genes was set to 500, as this was the number selected above (`nhvg = 500`). Next, run the function `RunPCA` to obtain the main result required for cell type prediction later on. In addition to cell type prediction, the query dataset(s) can be projected onto UMAP. To allow this, the argument `return.model` should be set to `TRUE` in both functions `RunPCA` and `RunUMAP`.

    # Train the reference
    set.seed(123)
    ref <- RunParallelDivisiveICP(
        object = ref, L = 10, k = 8,
        build.train.params = list(nhvg = 500), # default 2000
        threads = 2
    ) # runs without 'batch.label'

    ## WARNING: Setting 'divisive.method' to 'cluster' as 'batch.label=NULL'.
    ## If 'batch.label=NULL', 'divisive.method' can be one of: 'cluster', 'random'.

    ##
    ## Building training set...

    ## Training set successfully built.

    ##
    ## Computing cluster seed.

    ##
    ## Initializing divisive ICP clustering...

    ##
      |
      |                                                                      |   0%
      |
      |=======                                                               |  10%
      |
      |==============                                                        |  20%
      |
      |=====================                                                 |  30%
      |
      |============================                                          |  40%
      |
      |===================================                                   |  50%
      |
      |==========================================                            |  60%
      |
      |=================================================                     |  70%
      |
      |========================================================              |  80%
      |
      |===============================================================       |  90%
      |
      |======================================================================| 100%

    ##
    ## Divisive ICP clustering completed successfully.

    ##
    ## Predicting cell cluster probabilities using ICP models...

    ## Prediction of cell cluster probabilities completed successfully.

    ##
    ## Multi-level integration completed successfully.

    # Compute reference PCA
    ref <- RunPCA(ref, return.model = TRUE, pca.method = "stats")

    ## Divisive ICP: selecting ICP tables multiple of 3

    # Compute reference UMAP
    set.seed(123)
    ref <- RunUMAP(ref,
        return.model = TRUE,
        umap.method = "uwot", dims = 1:15,
        n_neighbors = 30, min_dist = 0.3
    )

Below is the UMAP plot for the reference sample with cell type annotations. In this example, we are using the annotations provided in the object. Ideally, the sample should be annotated after training by performing clustering and manual cluster annotation. Only the resulting manual cell type annotations should be used for prediction. For simplicity, we will use the annotations provided in the object.

    # Vizualize reference
    ref.celltype.plot <- PlotDimRed(
        object = ref,
        color.by = "label",
        dimred = "UMAP",
        point.size = 0.01,
        legend.nrow = 3,
        seed.color = 17
    ) +
        ggtitle("Reference: ground-truth labels")

    ## Warning in guide_legend(title = legend.title, nrow = legend.nrow, bycol = TRUE, : Arguments in `...` must be used.
    ## ✖ Problematic argument:
    ## • bycol = TRUE
    ## ℹ Did you misspell an argument name?

    ref.celltype.plot

## 3 Map query

Perform reference-mapping with `Coralysis` by running the function `ReferenceMapping`. This requires to provide the trained reference (`ref`) with cell type annotations intended for the prediction (`ref.label = "label"`) and the query dataset (`query`). The label in the reference aimed to be used for prediction needs to be available on `colData(ref)`. In this case, we are providing the cell type labels from the column `label` available in the reference `ref`. Since we want to project the query onto the reference UMAP, set `project.umap` as `TRUE`. The argument `dimred.name.prefix` just sets the name given as prefix of the low dimensional embeddings stored in `reducedDimNames(map)`. The `SingleCellExperiment` object `query` will contain the same information as `query`, with the predictions and embeddings mapped onto the reference. The predictions consist in `coral_probability`, `coral_labels`, and `pruned_coral_labels` stored in `colData(query)`. The `coral_labels` correspond to the cell type predictions obtained against the reference. The `coral_probability` represents the proportion of K neighbors from the winning class (`k.nn` was set to 5 - default 10); the higher the value, the better. The `pruned_coral_labels` corresponds to the pruned low-confidence predicted cell labels based on the confidence probability scores, i.e., predicted cell labels with confidence scores less than or equal to 0.5 (`label.prune.cutoff = 0.5`) are considered unclassified and set to `NA`.

    # Reference-mapping
    set.seed(1024)
    query <- ReferenceMapping(
        ref = ref, query = query, ref.label = "label",
        project.umap = TRUE, dimred.name.prefix = "ref",
        k.nn = 5, label.prune.cutoff = 0.5
    )

## 4 Prediction accuracy

Below, `SingleR` is run with the `ref`erence to predict the cell type labels for the `query`, in order to compare with the `Coralysis` predictions, since the `query` dataset used does not provide cell type labels. The accuracy of `Coralysis` reference-mapping method is presented below together with a confusion matrix between the predicted `Coralysis` labels (rows) versus predicted `SingleR` labels (columns).

### 4.1 Confusion matrix

    # Compare against SingleR
    preds.singler <- SingleR(test = query, ref = ref, labels = ref$label, de.method = "wilcox")

    ## Detected a large SingleCellExperiment as the reference dataset, consider
    ## setting 'aggr.ref = TRUE' for speed in trainSingleR(). If you know better, this
    ## hint can be disabled with 'hint.sce=FALSE'.

    colData(query) <- cbind(colData(query), preds.singler)

    # Confusion matrix 'coral_labels'
    preds_x_truth <- table(query$coral_labels, query$labels)

    # Accuracy for 'coral_labels'
    acc <- sum(diag(preds_x_truth)) / sum(preds_x_truth)

    # Print confusion matrix
    preds_x_truth

    ##
    ##               acinar alpha beta delta duct endothelial epsilon mesenchymal  pp
    ##   acinar         249     0    0     0    0           0       0           0   0
    ##   alpha            4   201    0     1    0           0       0           0   0
    ##   beta             8     1  165    10    0           0       0           0   0
    ##   delta            0     0    2    45    0           0       0           0   0
    ##   duct            22     0    9     0  299           1       0           8   0
    ##   endothelial      0     0    0     0    0           5       0           0   0
    ##   epsilon          0     0    0     0    0           0       1           0   0
    ##   mesenchymal      0     0    0     0    0           0       0          15   0
    ##   pp               0     0    0     0    0           0       0           0  18

The accuracy of `Coralysis` reference-mapping method was 93.8%.

The accuracy is higher for the pruned `Coralysis` cell labels, as the low-confidence cell labels were removed. See below the confusion matrix between the pruned `Coralysis` labels (rows) versus predicted `SingleR` labels (columns) and respective accuracy.

    # Confusion matrix for 'pruned_coral_labels'
    pruned_preds_x_truth <- table(query$pruned_coral_labels, query$labels)

    # Accuracy for 'pruned_coral_labels'
    pruned_acc <- sum(diag(pruned_preds_x_truth)) / sum(pruned_preds_x_truth)

    # Print confusion matrix
    pruned_preds_x_truth

    ##
    ##               acinar alpha beta delta duct endothelial epsilon mesenchymal  pp
    ##   acinar         245     0    0     0    0           0       0           0   0
    ##   alpha            2   201    0     0    0           0       0           0   0
    ##   beta             2     1  164    10    0           0       0           0   0
    ##   delta            0     0    2    44    0           0       0           0   0
    ##   duct            19     0    9     0  299           1       0           5   0
    ##   endothelial      0     0    0     0    0           5       0           0   0
    ##   epsilon          0     0    0     0    0           0       0           0   0
    ##   mesenchymal      0     0    0     0    0           0       0          10   0
    ##   pp               0     0    0     0    0           0       0           0  18

The accuracy of `Coralysis` reference-mapping method using the pruned cell labels was 95.1%. The accuracy is higher using the pruned labels as expected once the low-confidence cell predictions were removed.

### 4.2 DimRed

Visualize below the query cells projected onto reference UMAP and how well the predictions match the query ground-truth. The `coral_probability` is a prediction confidence score. Predictions with low scores (<0.5) should be carefully inspected.

    # Plot query and reference UMAP side-by-side # fig.height=7.25
    # with ground-truth & predicted cell labels
    use.color <- c("acinar" = "#FF7F00", "alpha" = "#CCCCCC", "beta" = "#FDCDAC",
                   "delta" = "#E6F5C9", "duct" = "#E31A1C", "endothelial" = "#377EB8",
                   "epsilon" = "#7FC97F", "mesenchymal" = "#F0027F", "pp" = "#B3CDE3")
    query.singler.plot <- PlotDimRed(
        object = query,
        color.by = "labels",
        dimred = "refUMAP",
        point.size = 0.25,
        point.stroke = 0.25,
        legend.nrow = 3,
        use.color = use.color
    ) +
        ggtitle("Query: SingleR predictions")

    ## Warning in guide_legend(title = legend.title, nrow = legend.nrow, bycol = TRUE, : Arguments in `...` must be used.
    ## ✖ Problematic argument:
    ## • bycol = TRUE
    ## ℹ Did you misspell an argument name?

    query.predicted.plot <- PlotDimRed(
        object = query,
        color.by = "coral_labels",
        dimred = "refUMAP",
        point.size = 0.25,
        point.stroke = 0.25,
        legend.nrow = 3,
        use.color = use.color
    ) +
        ggtitle("Query: Coralysis predictions")

    ## Warning in guide_legend(title = legend.title, nrow = legend.nrow, bycol = TRUE, : Arguments in `...` must be used.
    ## ✖ Problematic argument:
    ## • bycol = TRUE
    ## ℹ Did you misspell an argument name?

    query.pruned.plot <- PlotDimRed(
        object = query,
        color.by = "pruned_coral_labels",
        dimred = "refUMAP",
        point.size = 0.25,
        point.stroke = 0.25,
        legend.nrow = 3,
        use.color = use.color
    ) +
        ggtitle("Query: Coralysis predictions pruned")

    ## Warning in guide_legend(title = legend.title, nrow = legend.nrow, bycol = TRUE, : Arguments in `...` must be used.
    ## ✖ Problematic argument:
    ## • bycol = TRUE
    ## ℹ Did you misspell an argument name?

    query.confidence.plot <- PlotExpression(
        object = query,
        color.by = "coral_probability",
        dimred = "refUMAP",
        point.size = 0.25,
        point.stroke = 0.1,
        color.scale = "viridis"
    ) +
        ggtitle("Query: Coralysis confidence")
    cowplot::plot_grid(ref.celltype.plot, query.singler.plot,
        query.predicted.plot, query.pruned.plot,
        query.confidence.plot,
        ncol = 2, align = "vh"
    )

## 5 R session

    # R session
    sessionInfo()

    ## R version 4.6.0 Patched (2026-04-24 r89963)
    ## Platform: aarch64-apple-darwin23
    ## Running under: macOS Tahoe 26.3.1
    ##
    ## Matrix products: default
    ## BLAS:   /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
    ## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
    ##
    ## locale:
    ## [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
    ##
    ## time zone: America/New_York
    ## tzcode source: internal
    ##
    ## attached base packages:
    ## [1] grid      stats4    stats     graphics  grDevices utils     datasets
    ## [8] methods   base
    ##
    ## other attached packages:
    ##  [1] SingleR_2.14.0               MouseGastrulationData_1.25.0
    ##  [3] SpatialExperiment_1.22.0     scran_1.40.0
    ##  [5] ensembldb_2.36.0             AnnotationFilter_1.36.0
    ##  [7] GenomicFeatures_1.64.0       AnnotationDbi_1.74.0
    ##  [9] ComplexHeatmap_2.28.0        Coralysis_1.2.0
    ## [11] scRNAseq_2.25.0              scater_1.40.0
    ## [13] ggplot2_4.0.3                scuttle_1.22.0
    ## [15] SingleCellExperiment_1.34.0  SummarizedExperiment_1.42.0
    ## [17] Biobase_2.72.0               GenomicRanges_1.64.0
    ## [19] Seqinfo_1.2.0                IRanges_2.46.0
    ## [21] S4Vectors_0.50.0             BiocGenerics_0.58.0
    ## [23] generics_0.1.4               MatrixGenerics_1.24.0
    ## [25] matrixStats_1.5.0            dplyr_1.2.1
    ## [27] BiocStyle_2.40.0
    ##
    ## loaded via a namespace (and not attached):
    ##   [1] RcppAnnoy_0.0.23         BiocIO_1.22.0            bitops_1.0-9
    ##   [4] filelock_1.0.3           tibble_3.3.1             XML_3.99-0.23
    ##   [7] lifecycle_1.0.5          httr2_1.2.2              aricode_1.0.3
    ##  [10] doParallel_1.0.17        edgeR_4.10.0             lattice_0.22-9
    ##  [13] alabaster.base_1.12.0    magrittr_2.0.5           limma_3.68.0
    ##  [16] sass_0.4.10              rmarkdown_2.31           jquerylib_0.1.4
    ##  [19] yaml_2.3.12              metapod_1.20.0           otel_0.2.0
    ##  [22] askpass_1.2.1            reticulate_1.46.0        cowplot_1.2.0
    ##  [25] LiblineaR_2.10-24        DBI_1.3.0                RColorBrewer_1.1-3
    ##  [28] abind_1.4-8              Rtsne_0.17               purrr_1.2.2
    ##  [31] BumpyMatrix_1.20.0       RCurl_1.98-1.18          rappdirs_0.3.4
    ##  [34] circlize_0.4.18          ggrepel_0.9.8            irlba_2.3.7
    ##  [37] alabaster.sce_1.12.0     pheatmap_1.0.13          umap_0.2.10.0
    ##  [40] RSpectra_0.16-2          dqrng_0.4.1              codetools_0.2-20
    ##  [43] DelayedArray_0.38.0      shape_1.4.6.1            tidyselect_1.2.1
    ##  [46] UCSC.utils_1.8.0         farver_2.1.2             ScaledMatrix_1.20.0
    ##  [49] viridis_0.6.5            BiocFileCache_3.2.0      GenomicAlignments_1.48.0
    ##  [52] jsonlite_2.0.0           GetoptLong_1.1.1         BiocNeighbors_2.6.0
    ##  [55] iterators_1.0.14         foreach_1.5.2            tools_4.6.0
    ##  [58] Rcpp_1.1.1-1.1           glue_1.8.1               gridExtra_2.3
    ##  [61] SparseArray_1.12.0       xfun_0.57                flexclust_1.5.0
    ##  [64] GenomeInfoDb_1.48.0      HDF5Array_1.40.0         gypsum_1.8.0
    ##  [67] withr_3.0.2              BiocManager_1.30.27      fastmap_1.2.0
    ##  [70] rhdf5filters_1.24.0      bluster_1.22.0           openssl_2.4.0
    ##  [73] SparseM_1.84-2           digest_0.6.39            rsvd_1.0.5
    ##  [76] R6_2.6.1                 colorspace_2.1-2         Cairo_1.7-0
    ##  [79] dichromat_2.0-0.1        RSQLite_2.4.6            cigarillo_1.2.0
    ##  [82] h5mread_1.4.0            rtracklayer_1.72.0       class_7.3-23
    ##  [85] httr_1.4.8               S4Arrays_1.12.0          uwot_0.2.4
    ##  [88] pkgconfig_2.0.3          gtable_0.3.6             modeltools_0.2-24
    ##  [91] blob_1.3.0               S7_0.2.2                 XVector_0.52.0
    ##  [94] htmltools_0.5.9          bookdown_0.46            clue_0.3-68
    ##  [97] ProtGenerics_1.44.0      scales_1.4.0             alabaster.matrix_1.12.0
    ## [100] png_0.1-9                knitr_1.51               reshape2_1.4.5
    ## [103] rjson_0.2.23             curl_7.1.0               GlobalOptions_0.1.4
    ## [106] cachem_1.1.0             rhdf5_2.56.0             stringr_1.6.0
    ## [109] BiocVersion_3.23.1       parallel_4.6.0           vipor_0.4.7
    ## [112] ggrastr_1.0.2            restfulr_0.0.16          pillar_1.11.1
    ## [115] alabaster.schemas_1.12.0 vctrs_0.7.3              RANN_2.6.2
    ## [118] BiocSingular_1.28.0      dbplyr_2.5.2             beachmat_2.28.0
    ## [121] cluster_2.1.8.2          beeswarm_0.4.0           evaluate_1.0.5
    ## [124] magick_2.9.1             tinytex_0.59             cli_3.6.6
    ## [127] locfit_1.5-9.12          compiler_4.6.0           Rsamtools_2.28.0
    ## [130] rlang_1.2.0              crayon_1.5.3             labeling_0.4.3
    ## [133] plyr_1.8.9               ggbeeswarm_0.7.3         stringi_1.8.7
    ## [136] viridisLite_0.4.3        alabaster.se_1.12.0      BiocParallel_1.46.0
    ## [139] Biostrings_2.80.0        lazyeval_0.2.3           scrapper_1.6.0
    ## [142] Matrix_1.7-5             ExperimentHub_3.2.0      sparseMatrixStats_1.24.0
    ## [145] bit64_4.8.0              Rhdf5lib_2.0.0           KEGGREST_1.52.0
    ## [148] statmod_1.5.1            alabaster.ranges_1.12.0  AnnotationHub_4.2.0
    ## [151] igraph_2.3.0             memoise_2.0.1            bslib_0.10.0
    ## [154] bit_4.6.0

## 6 References

Amezquita R, Lun A, Becht E, Carey V, Carpp L, Geistlinger L, Marini F, Rue-Albrecht K, Risso D, Soneson C, Waldron L, Pages H, Smith M, Huber W, Morgan M, Gottardo R, Hicks S (2020). “Orchestrating single-cell analysis with Bioconductor.” Nature Methods, 17, 137-145. doi:10.1038/s41592-019-0654-x.

Aran D, Looney AP, Liu L, Wu E, Fong V, Hsu A, Chak S, Naikawadi RP, Wolters PJ, Abate AR, Butte AJ, Bhattacharya M (2019). “Reference-based analysis of lung single-cell sequencing reveals a transitional profibrotic macrophage.” Nat. Immunol., 20:163-172. doi:10.1038/s41590-018-0276-y.

Grun D, Muraro MJ, Boisset JC, Wiebrands K, Lyubimova A, Dharmadhikari G, van den Born M, van Es J, Jansen E, Clevers H, de Koning EJP, van Oudenaarden EJP (2016). “De Novo Prediction of Stem Cell Identity using Single-Cell Transcriptome Data.” Cell Stem Cell, 19(2):266–77. doi.org/10.1016/j.stem.2016.05.010

Muraro MJ, Dharmadhikari G, Grun D, Groen N, Dielen T, Jansen E, van Gurp L, Engelse MA, Carlotti F, de Koning EJP, van Oudenaarden A (2016). “A Single-Cell Transcriptome Atlas of the Human Pancreas.” Cell Systems, 3(4):385–94. doi.org/10.1016/j.cels.2016.09.002

Sousa A, Smolander J, Junttila S, Elo L (2025). “Coralysis enables sensitive identification of imbalanced cell types and states in single-cell data via multi-level integration.” bioRxiv. doi:10.1101/2025.02.07.637023

Wickham H (2016). “ggplot2: Elegant Graphics for Data Analysis.” Springer-Verlag New York.