Comparison · container format
SingleCellExperiment vs Seurat: Which One Should You Use?
Both objects hold the same counts, metadata and embeddings, the real choice is which ecosystem of functions you want opening the box.
By Ming "Tommy" Tang, Director of Bioinformatics in Big Pharma · Reviewed September 2026 · 4 min read
The verdict
Default to Seurat if you're the person this site is written for: a wet-lab biologist or early-career analyst who needs one object type to carry a dataset from raw counts to a UMAP with clusters on it. The whole path, NormalizeData()/SCTransform(), FindVariableFeatures(), ScaleData(), RunPCA(), RunUMAP(), FindNeighbors()/FindClusters(), runs inside a single Seurat object with dense, task-specific documentation for each step, and it's the object most published tutorials and lab pipelines already assume.
Switch to SingleCellExperiment when the tool you actually need only speaks SCE. That's scran's QC (perCellQCMetrics(), quickPerCellQC()) and deconvolution normalization (quickCluster() + computeSumFactors()), DropletUtils for raw droplet processing, scater for its plotting functions, or iSEE for interactive exploration. Don't reimplement scran's deconvolution normalization in Seurat or fight to recreate Seurat's integration inside SCE, convert with as.Seurat()/as.SingleCellExperiment() at the seam between the two ecosystems and let each tool do the part it's built for.
Seurat and SingleCellExperiment (SCE) are containers for the same handful of things: a count matrix, cell metadata, gene metadata, and low-dimensional embeddings. The real difference is how tightly those pieces are welded together, and whose functions expect to open the box. Seurat keeps the expression matrix, cell metadata, PCA results, clustering info, and UMAP coordinates bundled into one S4 object built around the Seurat workflow. SCE splits the same information into named components: assays for raw and normalized counts, rowData for gene annotations, colData for cell metadata, reducedDims for PCA/UMAP/t-SNE, plus a slot for alternative experiments like spike-ins or protein counts.
That split determines who else can read your object without conversion. SCE is the class scran, scater, DropletUtils, and iSEE all build on, if your QC or normalization step lives in one of those packages, your object is already SCE and stays that way through the pipeline. Seurat is its own ecosystem: NormalizeData(), FindVariableFeatures(), ScaleData(), RunPCA(), RunUMAP(), FindNeighbors()/FindClusters() (or SCTransform() as a one-call shortcut for the first three) all expect and return a Seurat object.
They also disagree on how you're supposed to reach into them. Seurat uses @ to grab a slot directly, seurat_obj@meta.data, seurat_obj[["RNA"]]@counts. SCE pushes you toward accessor functions like colData(sce) and rowData(sce), which don't require you to know the exact internal slot name and are the documented, safer path for S4 objects.
Head to head
| Criterion | SingleCellExperiment | Seurat | Edge |
|---|---|---|---|
| What the object actually stores | Assays (raw and normalized counts), rowData (gene annotations), colData (cell metadata), and reducedDims (PCA/UMAP/t-SNE) as distinct, named components. | Gene expression matrix, cell metadata, PCA results, clustering information, and UMAP coordinates bundled into a single S4 object. | Tie Design philosophy, not a defect on either side. |
| Accessing data safely | `colData(sce)` and `rowData(sce)` accessor functions are the documented way in, and don't depend on knowing an internal slot name. | `seurat_obj@meta.data` and `seurat_obj[["RNA"]]@counts` require direct @ slot access, which breaks if a slot name changes between versions. | SingleCellExperiment |
| Subsetting integrity | Automatically synchronizes all metadata and assays when you subset cells or features, by design. | Packet does not document an equivalent explicit synchronization guarantee for Seurat subsetting. | SingleCellExperiment Absence of documentation isn't proof Seurat lacks it, just that OSCA states the guarantee explicitly for SCE and Seurat's own docs don't make the same claim in the sources checked. |
| Normalization defaults | scran's deconvolution-based normalization: `quickCluster()` to find initial clusters, then `computeSumFactors()`. | `NormalizeData()` for standard log-normalization, or `SCTransform()` to combine normalization and variance stabilization in one call. | Tie Different statistical approaches, both well documented for their own object type. |
| Built-in QC tooling | `perCellQCMetrics()` and `quickPerCellQC()` give per-cell QC metrics and filtering in scran/scuttle. | Packet's Seurat workflow (NormalizeData through FindClusters) doesn't name an equivalent single-call QC function. | SingleCellExperiment |
| Multi-modal data (e.g. CITE-seq) | Stores a second modality as an alternative experiment (altExp), kept synchronized with the main assay. | Native multi-assay objects for simultaneous RNA and ADT (protein) analysis. | Tie |
| Native ecosystem | The standard class across scran, scater, DropletUtils, and iSEE, no conversion needed inside Bioconductor pipelines. | Its own ecosystem of NormalizeData/FindVariableFeatures/ScaleData/RunPCA/RunUMAP/FindNeighbors/FindClusters, plus SCTransform. | Tie a wins inside Bioconductor-native pipelines, b wins if you're staying entirely in Seurat. |
| Multi-dataset integration | Packet does not name an SCE-native integration function. | `IntegrateLayers()` in Seurat v5 runs five integration methods in low-dimensional space, natively inside the object. | Seurat |
| Interoperability between the two | `as.SingleCellExperiment(seurat_object)` converts a Seurat object to SCE. | `as.Seurat(sce_object, counts = 'counts', data = 'logcounts')` converts SCE to Seurat, naming which assay is which. | Tie |
| Documentation style | The OSCA book documents the class and its scran/scater workflows conceptually, with statistical grounding. | Dense task-specific vignettes (essential commands, sctransform, integration, spatial) that read like recipes. | Tie a is stronger for understanding why, b is stronger for copy-pasteable how. |
Use SingleCellExperiment when
- Your QC and normalization step specifically calls scran's `perCellQCMetrics()`, `quickPerCellQC()`, or the `quickCluster()`+`computeSumFactors()` deconvolution workflow.
- You're reading raw droplet output through DropletUtils and want the filtered result to land directly in the object type your next Bioconductor step expects.
- You need the automatic synchronization guarantee across every assay and metadata slot when subsetting cells or features.
- You're building or contributing to a Bioconductor package and have to accept or return the class that scran, scater, and iSEE expect.
- You want to store spike-ins or protein abundance as an alternative experiment kept in sync with the main assay, rather than bolting on a second object.
Use Seurat when
- You want the full path from raw counts to clusters and UMAP documented start to finish in one object type.
- You're integrating multiple datasets or batches and want `IntegrateLayers()` with a choice of five methods without leaving the object.
- You're running CITE-seq and need one object holding RNA and ADT assays together for joint analysis.
- You want `SCTransform()` to collapse normalization and variance stabilization into a single call instead of three separate steps.
- You're following a lab's existing pipeline or a published tutorial, since most of those are written against Seurat.
Switching between them
Converting is bidirectional: as.SingleCellExperiment(seurat_object) and as.Seurat(sce_object, counts = 'counts', data = 'logcounts'). The second call forces you to explicitly name which assay becomes counts and which becomes the normalized data layer, get that mapping wrong and downstream functions will silently operate on the wrong matrix.
Access syntax changes on conversion: code written against seurat_obj@meta.data or seurat_obj[["RNA"]]@counts has to become colData(sce) and assay(sce, "counts") (or the reverse), so a straight object swap in an existing script will break unless you rewrite the accessor calls too.
Normalization isn't recomputed on conversion, it's carried over as-is. If you normalized with Seurat's SCTransform() and convert to SCE, you get SCTransform's output, not scran's deconvolution-based computeSumFactors() result, the two use different statistical models, so QC thresholds or clustering parameters tuned on one won't necessarily transfer to the other. The packet does not document exactly which Seurat-specific slots (graphs, command history, SCT model parameters) survive conversion, so after converting, check what actually came across rather than assuming full parity.
Pitfalls with either
- Using `@` on a SingleCellExperiment out of habit from Seurat, fix: use `colData(sce)`/`rowData(sce)` instead of `sce@colData`, since accessor functions are the documented safer path for S4 objects.
- Assuming `as.Seurat()`/`as.SingleCellExperiment()` carries over every slot untouched, fix: explicitly name which assay becomes `counts` vs `data` in the call, then check what didn't transfer, since the packet doesn't confirm full parity across conversion.
- Treating SCTransform output and scran's deconvolution-normalized output as interchangeable, fix: they're different normalization models, so don't apply QC thresholds tuned on one framework's normalized values to the other's.
- Hardcoding `@` slot access into a script and having it break after a Seurat version upgrade, fix: run `slotNames(seurat_obj)` after upgrading to confirm slot names before trusting old `@` calls.
- Defaulting to SingleCellExperiment for a standard clustering/UMAP job just because it's the Bioconductor-native class, fix: use Seurat's single-vignette pipeline for that case, and convert to SCE only when you need a specific scran/scater/DropletUtils function.
Questions people ask
- Can I convert between Seurat and SingleCellExperiment?
Yes, both directions are supported:
as.SingleCellExperiment(seurat_object)andas.Seurat(sce_object, counts = 'counts', data = 'logcounts'). You have to name which assay becomes counts and which becomes the normalized data layer during conversion, so check that the right matrices land where you expect.- Which one do scran and scater work with?
Both are built on SingleCellExperiment. If your QC or normalization step calls
perCellQCMetrics(),quickPerCellQC(), orcomputeSumFactors(), your object needs to be an SCE, not a Seurat object.- Is Seurat or SingleCellExperiment better for CITE-seq or multi-modal data?
Both handle it. Seurat supports multi-assay objects for simultaneous RNA and ADT (protein) analysis in one object. SCE stores a second modality as an alternative experiment (altExp), which stays synchronized with the main assay during subsetting.
- Why does SingleCellExperiment use functions like colData() instead of $ or @?
Because it's an S4 object, not a simple list,
$generally doesn't work the way it does on a data frame. Accessor functions likecolData(sce)androwData(sce)are the documented, safer way in versus reaching forsce@colDatadirectly.- Do I need to learn both object types?
If your pipeline mixes DropletUtils or scran QC with Seurat-based clustering and visualization, a very common combination, yes. That's exactly why the bidirectional conversion functions exist.
Related pages
- Compare · SingleCellExperiment vs AnnData: Which One Should You Use?
- Convert · How to Convert Seurat object to SingleCellExperiment (Without Losing Your Metadata)
- Convert · How to Convert SingleCellExperiment to Seurat object (Without Losing Your Metadata)
- Compare · Seurat vs DESeq2: Which One Should You Use?
- Compare · Seurat vs Scanpy: Which One Should You Use?
- Glossary · Count matrix
- Glossary · CITE-seq
Related reading on the blog
Sources
- The SingleCellExperiment class — SCE component structure: assays, rowData, colData, reducedDims, altExps, and subsetting synchronization
- Chapter 24 Interoperability - Orchestrating Single-Cell Analysis with Bioconductor — Seurat described as very widely used for droplet-based datasets
- Interoperability between single-cell object formats • Seurat — as.SingleCellExperiment() and as.Seurat() bidirectional conversion
- Seurat Essential Commands — Standard Seurat workflow functions and multi-assay CITE-seq support
- Integrative analysis in Seurat v5 — IntegrateLayers() and the five integration methods
- Quality Control - Basics of Single-Cell Analysis with Bioconductor — perCellQCMetrics() and quickPerCellQC() for SCE-based QC
- Using scran to perform basic analyses of single-cell RNA-seq data — quickCluster() + computeSumFactors() deconvolution normalization
- scater: Single-Cell Expression Analysis Tools • Bioconductor — plotColData() for SCE-based QC visualization