| name | singlecellexperiment |
| description | SingleCellExperiment (SCE) — the canonical Bioconductor S4 container for single-cell genomics. Covers SCE construction, assay/colData/rowData/reducedDims/altExps accessors, sizeFactors and labels, iteration via applySCE, and the standard scater + scran preprocessing pipeline (QC → normalization → HVG → PCA → clustering → markers). Sister container to Seurat — pick SCE when integrating with the Bioconductor ecosystem (scran, scater, scDblFinder, miloR, MAST, batchelor). |
| license | GPL-3.0 |
| metadata | null |
SingleCellExperiment: Bioconductor scRNA-seq Container
Overview
SingleCellExperiment (SCE) is the canonical Bioconductor S4 class for storing scRNA-seq data — the equivalent of Seurat's Seurat object or scanpy's AnnData. It extends RangedSummarizedExperiment with single-cell-specific slots: reducedDims, altExps (alternative experiments — spike-ins, hashtags, ADT), and sizeFactors.
The SCE object is the data backbone for the entire Bioconductor scRNA-seq ecosystem:
| Step | Package | What it does |
|---|
| QC + visualization | scater | Per-cell QC metrics, normalization, plotting |
| Normalization + HVG | scran | Deconvolution sizeFactors, modelGeneVar |
| Clustering / DE | scran, bluster | Graph clustering + marker discovery |
| Doublets | scDblFinder | Doublet calling |
| Integration | batchelor | MNN, Harmony, etc. |
| Trajectory | TSCAN, tradeSeq | Pseudotime + gene-along-trajectory tests |
| DA | miloR | Neighborhood differential abundance |
Most of these read sce → ... → return sce, so the SCE flows through the whole pipeline as a single object.
When to Use This Skill
- You're already in the Bioconductor ecosystem (DESeq2, edgeR, limma, multi-omics tools)
- You need clean integration with scran's deconvolution sizeFactors (statistically principled for very sparse data)
- Working with spike-ins, hashtags (HTOs), or ADT data — SCE's
altExps handles these natively
- Building a package — SCE is the reference S4 class for scRNA-seq data
- Reading h5ad / Seurat .rds INTO Bioconductor for downstream analyses (e.g. miloR for DA)
Not for:
- Pure exploratory analysis where Seurat or scanpy is faster — both have richer interactive defaults
- Multimodal CITE-seq where Seurat's WNN is the cleaner toolkit
- ATAC-seq — use
archr (Bioconductor-aware) or snapatac2
Prerequisites
- R ≥ 4.6
- BiocManager
- The standard scRNA-seq Bioconductor stack: scater, scran, BiocSingular, BiocParallel
- 16-32 GB RAM for ~100k cells; 64+ GB for ~500k+
Installation
if (!require("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install(c(
"SingleCellExperiment",
"scater",
"scran",
"scDblFinder",
"batchelor",
"DropletUtils",
"BiocSingular",
"BiocParallel"
))
For multi-sample comparison and DA: BiocManager::install(c("miloR", "MAST")).
For trajectory: BiocManager::install(c("TSCAN", "tradeSeq", "slingshot")).
The SCE Class — Structure
SingleCellExperiment
┌──────────────────────────────────────────────────┐
assays(sce) [counts, logcounts, ...] one named matrix per assay
rowData(sce) ──── features × n_features_cols gene metadata
colData(sce) ──── cells × n_cell_cols cell metadata
reducedDims(sce) ──── list of cells × N matrices PCA, UMAP, tSNE
altExps(sce) ──── named list of mini SCEs spike-ins, HTOs, ADT
metadata(sce) ──── list of misc study-level info
sizeFactors(sce) ──── vector of length n_cells normalization factors
└──────────────────────────────────────────────────┘
Rows = features. Columns = cells. (Same convention as SummarizedExperiment.)
Quick Start — Build an SCE
From a count matrix + metadata
library(SingleCellExperiment)
library(S4Vectors)
counts <- as(matrix(rpois(100*50, lambda=3), nrow=100, ncol=50),
"dgCMatrix")
rownames(counts) <- paste0("Gene", 1:100)
colnames(counts) <- paste0("Cell", 1:50)
sce <- SingleCellExperiment(
assays = list(counts = counts),
colData = DataFrame(label = sample replace
rowData DataFrame rnorm
metadata study
sce
From 10X cellranger output
library(DropletUtils)
sce <- read10xCounts("/path/to/filtered_feature_bc_matrix/")
rownames(sce) <- uniquifyFeatureNames(rowData(sce)$ID, rowData(sce)$Symbol)
sce
read10xCounts reads either the filtered or raw (with empty droplets) matrices.
From a Seurat object
library(Seurat)
seurat_obj <- readRDS("pbmc3k.rds")
sce <- as.SingleCellExperiment(seurat_obj)
sce <- as.SingleCellExperiment(seurat_obj, assay = "RNA")
From a scanpy h5ad
library(zellkonverter)
sce <- readH5AD("data.h5ad")
Accessors
counts(sce)
logcounts(sce)
assay(sce, "name")
assays(sce)
colData(sce)
rowData(sce)
metadata(sce)
reducedDim(sce, "PCA")
reducedDims(sce)
reducedDimNames(sce)
altExp(sce, "spike-ins")
altExps(sce)
altExpNames(sce)
sizeFactors(sce)
colLabels(sce
Setters mirror getters:
counts(sce) <- new_count_matrix
colData(sce)$cell_type <- predicted_labels
reducedDim(sce, "PCA") <- pca_coords
altExp(sce, "ADT") <- adt_sce
sizeFactors(sce) <- size_factors
colLabels(sce) <- cluster_ids
Standard scRNA-seq Pipeline — scater + scran
library(scater)
library(scran)
library(scDblFinder)
library(BiocParallel)
sce <- read10xCounts("/path/to/filtered_feature_bc_matrix/")
rownames(sce) <- uniquifyFeatureNames(rowData(sce)$ID, rowData(sce)$Symbol)
is_mt <- grepl("^MT-", rownames(sce))
sce <- addPerCellQC(sce, subsets = list(MT = is_mt))
plotColData(sce, x = "Sample", y
plotColDatasce x y colour_by
qc quickPerCellQCsce sub.fields
sce sce qcdiscard
message ncolsce
sce scDblFindersce
sce sce scescDblFinder.class
clust quickClustersce
sce computeSumFactorssce clusters clust
sce logNormCountssce
dec modelGeneVarsce
hvgs getTopHVGsdec n
libraryBiocSingular
set.seed
sce runPCAsce subset_row hvgs ncomponents
BSPARAM IrlbaParam
sce runUMAPsce dimred
librarybluster
scecluster clusterCellssce use.dimred
BLUSPARAM NNGraphParamcluster.fun
colLabelssce scecluster
markers findMarkerssce groups scecluster
direction lfc
top_markers lapplymarkers x headrownamesx
plotUMAPsce colour_by text_by
plotUMAPsce colour_by
plotHeatmapsce features unlisttop_markers
order_columns_by center
plotExpressionsce features x
saveRDSsce
Convenience: Rscript scripts/build_sce.R --input data/filtered_feature_bc_matrix --out sce.rds.
Alternative Experiments — Spike-ins, HTOs, ADT
When you have additional features that aren't endogenous mRNA (ERCC spike-ins, CITE-seq antibody capture, hashtag oligos), store them as altExps so they don't pollute the main assay.
is_ercc <- grepl("^ERCC-", rownames(sce))
is_adt <- grepl("^A-", rownames(sce))
sce <- splitAltExps(sce,
f = ifelse(is_ercc, "ERCC",
ifelse(is_adt, "ADT", "Gene"))
)
sce
altExp(sce, "ERCC")
altExp(sce, "ADT")
After splitting, subsetting sce[, cell_subset] automatically subsets all altExps too — they stay in sync with the cell columns.
Iteration with applySCE
For functions you want to apply to both the main experiment and each altExp:
total_counts <- function(x) colSums(counts(x))
totals <- applySCE(sce, FUN = total_counts)
totals <- applySCE(sce, FUN = total_counts, multiplier = 10)
totals <- applySCE(
sce, FUN = total_counts,
multiplier = 10,
ALT.ARGS = list(
ERCC = list(subset.row = 2),
ADT = list(subset.row = 3:5)
)
)
When FUN returns an SCE, the default SIMPLIFY = TRUE reorganizes the results back into a single SCE.
Multi-Sample Integration with batchelor
library(batchelor)
sce1$batch <- "donor1"
sce2$batch <- "donor2"
sce3$batch <- "donor3"
sce_list <- lapply(list(sce1, sce2, sce3), function(s) {
s <- logNormCounts(s)
s
})
merged <- fastMNN(sce_list, k = 20, d = 50)
merged <- runUMAP(merged, dimred = "corrected")
plotUMAP(merged, colour_by = "batch")
For Harmony integration:
library(harmony)
merged <- RunHarmony(merged, group.by.vars = "batch", reduction = "PCA")
Interop with Seurat / scanpy
library(Seurat)
seurat_obj <- as.Seurat(sce, counts = "counts", data = "logcounts")
sce <- as.SingleCellExperiment(seurat_obj)
library(zellkonverter)
writeH5AD(sce, "data.h5ad")
sce <- readH5AD("data.h5ad")
zellkonverter is the canonical bridge between SCE and AnnData. It preserves assays, colData, reducedDims, and altExps.
Key Parameters
addPerCellQC(subsets = ...)
- Always include the MT subset; optionally ribosomal (
"^RP[SL]" for human)
quickCluster() (before computeSumFactors)
- Pre-clustering quality affects size-factor accuracy. Use
min.size = 200 for stable results.
modelGeneVar() → getTopHVGs(n = 2000)
- 2000 is fine for most analyses. Bump to 4000 for highly heterogeneous tissues.
runPCA(BSPARAM = IrlbaParam())
- Use
IrlbaParam() for fast irlba-based PCA. Default exact PCA is slow on large datasets.
clusterCells(BLUSPARAM = NNGraphParam(...))
cluster.fun = "louvain" or "leiden". Leiden requires the leiden package + a small bit of setup.
Best Practices
- Use sparse matrices. Store counts as
dgCMatrix (Matrix package). Dense matrices for ≥ 10k cells blow up memory.
scran::computeSumFactors is statistically better than library-size normalization. Don't replace it with raw library-size sizeFactors unless you have a specific reason.
uniquifyFeatureNames(rowData$ID, rowData$Symbol) after read10xCounts — handles duplicate gene symbols (which are common).
- Always run scDblFinder on per-sample SCEs before integration. Cross-sample doublet detection can be misleading.
- Use
BiocParallel::MulticoreParam(workers = N) for parallelizable steps like findMarkers and scDblFinder — large speedups.
- For storage,
HDF5Array + DelayedArray backed SCEs keep counts on disk. Critical for atlas-scale datasets that don't fit in RAM.
- For interop,
zellkonverter::writeH5AD / readH5AD is the canonical bridge to scanpy.
Developer Notes — Subclassing SCE
If you're writing a package that extends SCE (e.g. a new modality container), use the internal field protection pattern:
setX_good <- function(sce, x) {
int_colData(sce)$mypackage <- DataFrame(X = x)
sce
}
getX <- function(sce) int_colData(sce)$mypackage$X
This "inception-style" nesting prevents collision with other packages that may want to use the same field name.
Source: Developer vignette. See references/dev_patterns.md for full subclassing patterns.
End-to-End Template
assets/sce_template.R — single parameterized script through the full scater + scran pipeline.
Convenience Scripts
scripts/build_sce.R — load 10X / .h5 / Seurat → SCE → standard pipeline → save
References