scCertify 0.99.7
scCertify provides an explainable framework for evaluating confidence in
single-cell RNA-sequencing cell-type annotations.
The package combines several complementary sources of evidence:
scCertify supports both SingleCellExperiment and Seurat objects.
library(scCertify)
library(SingleCellExperiment)
## Loading required package: SummarizedExperiment
## Loading required package: MatrixGenerics
## Loading required package: matrixStats
##
## Attaching package: 'MatrixGenerics'
## The following objects are masked from 'package:matrixStats':
##
## colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
## colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
## colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
## colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
## colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
## colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
## colWeightedMeans, colWeightedMedians, colWeightedSds,
## colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
## rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
## rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
## rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
## rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
## rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
## rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
## rowWeightedSds, rowWeightedVars
## Loading required package: GenomicRanges
## Loading required package: stats4
## Loading required package: BiocGenerics
## Loading required package: generics
##
## Attaching package: 'generics'
## The following objects are masked from 'package:base':
##
## as.difftime, as.factor, as.ordered, intersect, is.element, setdiff,
## setequal, union
##
## Attaching package: 'BiocGenerics'
## The following objects are masked from 'package:stats':
##
## IQR, mad, sd, var, xtabs
## The following object is masked from 'package:utils':
##
## data
## The following objects are masked from 'package:base':
##
## Filter, Find, Map, Position, Reduce, anyDuplicated, aperm, append,
## as.data.frame, basename, cbind, colnames, dirname, do.call,
## duplicated, eval, evalq, get, grep, grepl, is.unsorted, lapply,
## mapply, match, mget, order, paste, pmax, pmax.int, pmin, pmin.int,
## rank, rbind, rownames, sapply, saveRDS, scale, sequence, table,
## tapply, transform, unique, unsplit, which.max, which.min
## Loading required package: S4Vectors
##
## Attaching package: 'S4Vectors'
## The following object is masked from 'package:utils':
##
## findMatches
## The following objects are masked from 'package:base':
##
## I, expand.grid, unname
## Loading required package: IRanges
## Loading required package: Seqinfo
## Loading required package: Biobase
## Welcome to Bioconductor
##
## Vignettes contain introductory material; view with
## 'browseVignettes()'. To cite Bioconductor, see
## 'citation("Biobase")', and for packages 'citation("pkgname")'.
##
## Attaching package: 'Biobase'
## The following object is masked from 'package:MatrixGenerics':
##
## rowMedians
## The following objects are masked from 'package:matrixStats':
##
## anyMissing, rowMedians
library(SummarizedExperiment)
library(Seurat)
## Loading required package: SeuratObject
## Loading required package: sp
##
## Attaching package: 'sp'
## The following object is masked from 'package:IRanges':
##
## %over%
## 'SeuratObject' was built under R 4.6.0 but the current version is
## 4.6.1; it is recomended that you reinstall 'SeuratObject' as the ABI
## for R may have changed
## 'SeuratObject' was built with package 'Matrix' 1.7.5 but the current
## version is 1.7.6; it is recomended that you reinstall 'SeuratObject' as
## the ABI for 'Matrix' may have changed
##
## Attaching package: 'SeuratObject'
## The following object is masked from 'package:SummarizedExperiment':
##
## Assays
## The following object is masked from 'package:GenomicRanges':
##
## intersect
## The following object is masked from 'package:Seqinfo':
##
## intersect
## The following object is masked from 'package:IRanges':
##
## intersect
## The following object is masked from 'package:S4Vectors':
##
## intersect
## The following object is masked from 'package:BiocGenerics':
##
## intersect
## The following objects are masked from 'package:base':
##
## intersect, t
##
## Attaching package: 'Seurat'
## The following object is masked from 'package:SummarizedExperiment':
##
## Assays
set.seed(42)
The following example creates a small simulated
SingleCellExperiment containing count data and predicted cell-type labels.
n_genes <- 50
n_cells <- 40
counts <- matrix(
rpois(n_genes * n_cells, lambda = 5),
nrow = n_genes,
ncol = n_cells,
dimnames = list(
paste0("Gene", seq_len(n_genes)),
paste0("Cell", seq_len(n_cells))
)
)
sce <- SingleCellExperiment(
assays = list(
counts = counts
)
)
colData(sce)$predicted_label <- rep(
c("T cell", "B cell"),
each = n_cells / 2
)
sce
## class: SingleCellExperiment
## dim: 50 40
## metadata(0):
## assays(1): counts
## rownames(50): Gene1 Gene2 ... Gene49 Gene50
## rowData names(0):
## colnames(40): Cell1 Cell2 ... Cell39 Cell40
## colData names(1): predicted_label
## reducedDimNames(0):
## mainExpName: NULL
## altExpNames(0):
The predicted cell labels are stored in colData().
head(colData(sce))
## DataFrame with 6 rows and 1 column
## predicted_label
## <character>
## Cell1 T cell
## Cell2 T cell
## Cell3 T cell
## Cell4 T cell
## Cell5 T cell
## Cell6 T cell
A marker database is represented as a named list, with one element for each predicted cell type.
markers <- list(
"T cell" = c("Gene1", "Gene2", "Gene3"),
"B cell" = c("Gene4", "Gene5", "Gene6")
)
markers
## $`T cell`
## [1] "Gene1" "Gene2" "Gene3"
##
## $`B cell`
## [1] "Gene4" "Gene5" "Gene6"
neighbor_score() evaluates agreement among nearby cells. For a
SingleCellExperiment, a suitable low-dimensional representation is required.
For this simulated example, a logcounts assay is created from a
log-transformed count matrix. marker_score() requires this assay to be
present, and the same log-transformed matrix is also used to calculate PCA.
log_counts <- log1p(
assay(sce, "counts")
)
assay(sce, "logcounts") <- log_counts
pca <- stats::prcomp(
t(log_counts),
center = TRUE,
scale. = TRUE
)$x[, seq_len(5), drop = FALSE]
reducedDim(sce, "PCA") <- pca
dim(
reducedDim(sce, "PCA")
)
## [1] 40 5
Marker consistency evaluates whether cells express genes expected for their predicted identity.
scCertify uses rank-based enrichment scoring through UCell.
marker_scores <- marker_score(
sce,
markers,
label_column = "predicted_label"
)
head(marker_scores)
## [1] 0.6423611 0.4270833 0.3055556 0.6701389 0.7951389 0.3784722
The resulting scores provide a cell-level measure of marker consistency.
The neighborhood score measures whether nearby cells tend to share the same predicted annotation.
neighbor_scores <- neighbor_score(
sce,
label_column = "predicted_label"
)
head(neighbor_scores)
## [1] 0.4 0.7 0.7 0.2 0.5 0.5
entropy_score() provides an uncertainty measure based on the distribution of
expression values.
entropy_values <- entropy_score(
t(assay(sce, "counts"))
)
head(entropy_values)
## Cell1 Cell2 Cell3 Cell4 Cell5 Cell6
## 3.799701 3.783424 3.811471 3.831461 3.809691 3.796189
The values can be normalized and stored in the cell metadata.
entropy_norm <- entropy_values / max(
entropy_values,
na.rm = TRUE
)
colData(sce)$entropy_norm <- entropy_norm
head(colData(sce)$entropy_norm)
## Cell1 Cell2 Cell3 Cell4 Cell5 Cell6
## 0.9849849 0.9807655 0.9880360 0.9932180 0.9875746 0.9840745
For this simulated example, doublet scores are generated as demonstration metadata.
In a real analysis, these values would normally come from a dedicated doublet detection method.
colData(sce)$doublet_score <- runif(
ncol(sce),
min = 0,
max = 1
)
head(colData(sce)$doublet_score)
## [1] 0.9899656 0.4384936 0.6999032 0.8890770 0.8341595 0.7344215
cell_certify() combines marker consistency, neighborhood agreement,
entropy-based uncertainty, and doublet information into a calibrated
confidence score.
sce <- cell_certify(
sce,
markers,
label_column = "predicted_label"
)
head(
colData(sce)[,
c(
"predicted_label",
"marker_score",
"neighbor_score",
"entropy_norm",
"doublet_score",
"confidence_score",
"confidence_class"
)
]
)
## DataFrame with 6 rows and 7 columns
## predicted_label marker_score neighbor_score entropy_norm doublet_score
## <character> <numeric> <numeric> <numeric> <numeric>
## Cell1 T cell 0.642361 0.4 0.984985 0.989966
## Cell2 T cell 0.427083 0.7 0.980766 0.438494
## Cell3 T cell 0.305556 0.7 0.988036 0.699903
## Cell4 T cell 0.670139 0.2 0.993218 0.889077
## Cell5 T cell 0.795139 0.5 0.987575 0.834159
## Cell6 T cell 0.378472 0.5 0.984074 0.734421
## confidence_score confidence_class
## <numeric> <character>
## Cell1 0.450 Low
## Cell2 0.850 High
## Cell3 0.575 Moderate
## Cell4 0.075 Low
## Cell5 0.725 Moderate
## Cell6 0.325 Low
Confidence classes can be customized using the moderate_threshold and
high_threshold arguments.
sce_custom <- cell_certify(
sce,
markers,
label_column = "predicted_label",
moderate_threshold = 0.4,
high_threshold = 0.75
)
head(
colData(sce_custom)[,
c(
"confidence_score",
"confidence_class"
)
]
)
## DataFrame with 6 rows and 2 columns
## confidence_score confidence_class
## <numeric> <character>
## Cell1 0.450 Moderate
## Cell2 0.850 High
## Cell3 0.575 Moderate
## Cell4 0.075 Low
## Cell5 0.725 Moderate
## Cell6 0.325 Low
explain_confidence() identifies factors that contributed to lower confidence
for an individual cell.
explain_confidence(
sce,
cell_id = "Cell1"
)
## [1] "Neighborhood disagreement" "High annotation uncertainty"
## [3] "Possible doublet contamination"
explain_cell() provides a concise summary containing the predicted label,
confidence score, individual evidence components, and interpretation.
explain_cell(
sce,
cell_id = "Cell1"
)
## Cell: Cell1
## Predicted Label: T cell
## Confidence Score: 0.45
## Marker Score: 0.642
## Neighbor Agreement: 0.4
## Entropy: 0.985
## Interpretation:
## - Moderate confidence annotation
## - High uncertainty detected
## - Local neighborhood disagreement
## [1] "Moderate confidence annotation" "High uncertainty detected"
## [3] "Local neighborhood disagreement"
scCertify also supports Seurat objects. The following example demonstrates a
complete evaluated Seurat workflow.
seurat_counts <- matrix(
rpois(2000, lambda = 5),
nrow = 100,
ncol = 20,
dimnames = list(
paste0("Gene", seq_len(100)),
paste0("Cell", seq_len(20))
)
)
seurat_object <- Seurat::CreateSeuratObject(
counts = seurat_counts
)
## Warning: Data is of class matrix. Coercing to dgCMatrix.
seurat_object$predicted_label <- rep(
c("T cell", "B cell"),
each = 10
)
seurat_object$entropy_norm <- runif(
ncol(seurat_object)
)
seurat_object$doublet_score <- runif(
ncol(seurat_object)
)
seurat_object
## An object of class Seurat
## 100 features across 20 samples within 1 assay
## Active assay: RNA (100 features, 0 variable features)
## 1 layer present: counts
The Seurat object is processed using its standard normalization and PCA workflow.
seurat_object <- Seurat::NormalizeData(
seurat_object,
verbose = FALSE
)
seurat_object <- Seurat::FindVariableFeatures(
seurat_object,
verbose = FALSE
)
seurat_object <- Seurat::ScaleData(
seurat_object,
verbose = FALSE
)
seurat_object <- Seurat::RunPCA(
seurat_object,
npcs = 10,
verbose = FALSE
)
## Warning in svd.function(A = t(x = object), nv = npcs, ...): You're computing
## too large a percentage of total singular values, use a standard svd instead.
seurat_object
## An object of class Seurat
## 100 features across 20 samples within 1 assay
## Active assay: RNA (100 features, 100 variable features)
## 3 layers present: counts, data, scale.data
## 1 dimensional reduction calculated: pca
The same marker definitions can then be used with scCertify.
seurat_object <- cell_certify(
seurat_object,
markers,
label_column = "predicted_label"
)
head(
seurat_object[[
c(
"predicted_label",
"marker_score",
"neighbor_score",
"entropy_norm",
"doublet_score",
"confidence_score",
"confidence_class"
)
]]
)
## predicted_label marker_score neighbor_score entropy_norm doublet_score
## Cell1 T cell 0.4812925 0.7 0.6756233 0.4636337464
## Cell2 T cell 0.7159864 0.5 0.3624238 0.9391432542
## Cell3 T cell 0.3622449 0.4 0.3981606 0.3398535226
## Cell4 T cell 0.6683673 0.5 0.2449258 0.0003825743
## Cell5 T cell 0.7993197 0.4 0.1579488 0.2173832541
## Cell6 T cell 0.3945578 0.5 0.5307416 0.0360659512
## confidence_score confidence_class
## Cell1 0.85 High
## Cell2 0.70 Moderate
## Cell3 0.15 Low
## Cell4 0.95 High
## Cell5 0.80 High
## Cell6 0.40 Low
The resulting confidence information is stored directly in the Seurat metadata.
explain_confidence(
seurat_object,
cell_id = "Cell1"
)
## [1] "Strong annotation support"
explain_cell(
seurat_object,
cell_id = "Cell1"
)
## Cell: Cell1
## Predicted Label: T cell
## Confidence Score: 0.85
## Marker Score: 0.481
## Neighbor Agreement: 0.7
## Entropy: 0.676
## Interpretation:
## - High confidence annotation
## [1] "High confidence annotation"
This vignette demonstrates the complete scCertify workflow for both
SingleCellExperiment and Seurat objects.
The workflow consists of:
Because all examples in this vignette are evaluated, the document also provides
a continuously tested demonstration that the documented workflow remains
functional with the package dependencies used by scCertify.
sessionInfo()
## R version 4.6.1 Patched (2026-06-24 r90190)
## Platform: x86_64-apple-darwin20
## Running under: macOS Ventura 13.7.8
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.6-x86_64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6-x86_64/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] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] Seurat_5.5.1 SeuratObject_5.4.0
## [3] sp_2.2-3 SingleCellExperiment_1.35.2
## [5] SummarizedExperiment_1.43.0 Biobase_2.73.2
## [7] GenomicRanges_1.65.4 Seqinfo_1.3.2
## [9] IRanges_2.47.5 S4Vectors_0.51.9
## [11] BiocGenerics_0.59.12 generics_0.1.4
## [13] MatrixGenerics_1.25.0 matrixStats_1.5.0
## [15] scCertify_0.99.7 BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] UCell_2.17.1 RColorBrewer_1.1-3 jsonlite_2.0.0
## [4] magrittr_2.0.5 spatstat.utils_3.2-4 farver_2.1.2
## [7] rmarkdown_2.32 vctrs_0.7.3 ROCR_1.0-12
## [10] spatstat.explore_3.8-2 htmltools_0.5.9 S4Arrays_1.13.0
## [13] BiocNeighbors_2.7.3 SparseArray_1.13.2 sass_0.4.10
## [16] sctransform_0.4.3 parallelly_1.48.0 KernSmooth_2.23-27
## [19] bslib_0.12.0 htmlwidgets_1.6.4 ica_1.0-3
## [22] plyr_1.8.9 plotly_4.12.1 zoo_1.9-0
## [25] cachem_1.1.0 igraph_2.3.3 mime_0.13
## [28] lifecycle_1.0.5 pkgconfig_2.0.3 Matrix_1.7-6
## [31] R6_2.6.1 fastmap_1.2.0 fitdistrplus_1.2-6
## [34] future_1.75.0 shiny_1.14.0 digest_0.6.39
## [37] patchwork_1.3.2 tensor_1.5.1 RSpectra_0.16-2
## [40] irlba_2.3.7 progressr_1.0.0 spatstat.sparse_3.2-0
## [43] httr_1.4.9 polyclip_1.10-7 abind_1.4-8
## [46] compiler_4.6.1 S7_0.2.2 BiocParallel_1.47.0
## [49] fastDummies_1.7.6 MASS_7.3-66 DelayedArray_0.39.6
## [52] tools_4.6.1 lmtest_0.9-40 otel_0.2.0
## [55] httpuv_1.6.17 future.apply_1.20.2 goftest_1.2-3
## [58] glue_1.8.1 nlme_3.1-171 promises_1.5.0
## [61] grid_4.6.1 Rtsne_0.17 cluster_2.1.8.3
## [64] reshape2_1.4.5 gtable_0.3.6 spatstat.data_3.1-9
## [67] tidyr_1.3.2 data.table_1.18.6.1 XVector_0.53.0
## [70] spatstat.geom_3.8-2 RcppAnnoy_0.0.23 ggrepel_0.9.8
## [73] RANN_2.6.3 pillar_1.11.1 stringr_1.6.0
## [76] spam_2.11-4 RcppHNSW_0.7.0 later_1.4.8
## [79] splines_4.6.1 dplyr_1.2.1 lattice_0.23-1
## [82] FNN_1.1.4.1 survival_3.8-11 deldir_2.0-4
## [85] tidyselect_1.2.1 miniUI_0.1.2 pbapply_1.7-5
## [88] knitr_1.52 gridExtra_2.3.1 bookdown_0.48
## [91] scattermore_1.2 xfun_0.60 stringi_1.8.9
## [94] yaml_2.3.12 evaluate_1.0.5 codetools_0.2-20
## [97] entropy_1.3.2 tibble_3.3.1 BiocManager_1.30.27
## [100] cli_3.6.6 uwot_0.2.5 xtable_1.8-8
## [103] reticulate_1.47.0 jquerylib_0.1.4 dichromat_2.0-1
## [106] Rcpp_1.1.2 globals_0.19.1 spatstat.random_3.5-1
## [109] png_0.1-9 spatstat.univar_3.2-0 parallel_4.6.1
## [112] ggplot2_4.0.3 dotCall64_1.2 listenv_1.0.0
## [115] viridisLite_0.4.3 scales_1.4.0 ggridges_0.5.7
## [118] purrr_1.2.2 rlang_1.3.0 cowplot_1.2.0