# SummarizedExperiment vs ExpressionSet: Choosing the Right Container

SummarizedExperiment is the modern Bioconductor container that stores one or more assay matrices together with row and column metadata and, optionally, genomic coordinates, while ExpressionSet is the older microarray-era container that stores a single expression matrix with phenotype and feature annotations. The rule of thumb is simple. If you are starting a new project, especially anything involving RNA-seq, single-cell data, genomic ranges, or a package released in the last several years, use SummarizedExperiment or one of its subclasses. Keep ExpressionSet for legacy microarray data, for older packages you depend on, and for datasets you download from repositories that still ship in that format.

This guide explains the difference between SummarizedExperiment and ExpressionSet in concrete terms, shows the anatomy of each class, compares them side by side, walks through worked code examples, and gives you a decision procedure you can apply to any project. It assumes you already work in R for bioinformatics at a basic level and want to stop guessing which container a function expects.

## Why the container question matters in R for bioinformatics

Bioconductor is the dominant platform for genomic data analysis in R, and its packages are built around shared data classes. Shared classes are what let one package read what another package writes. A normalization function can hand its output directly to a differential expression function because both operate on the same object, with the same slots, the same accessor functions, and the same subsetting behavior. This design principle goes back to the earliest sequencing packages. ShortRead, published in 2009, was built specifically so that high-throughput sequencing data could flow into "additional facilities for advanced [statistical analysis](/blog/guides/statistical-analysis), data transformation, visualization and integration with diverse genomic resources" within the R and Bioconductor environments [1].

Containers are the [connective tissue](/knowledge/veterinary-medicine/veterinary-anatomy-physiology/connective-tissue-types-loose-dense-and-more) of that system. Pick the wrong one and you spend your session converting objects instead of analyzing data. Pick the right one and packages interoperate without friction, which is exactly the benefit the InteractionSet authors describe: standard classes for genomic interactions let developers reuse existing infrastructure, and packages built on those classes "improve interoperability between packages" [2].

The two containers you will meet most often for matrix-shaped data, meaning rows are features such as genes and columns are samples, are ExpressionSet and SummarizedExperiment. Both are still in active use. The runibic biclustering package, for example, deliberately supports "integration with the modern Bioconductor packages such as SummarizedExperiment, ExpressionSet and biclust," which shows that package authors still treat both containers as live interfaces [3]. That said, the center of gravity has clearly moved. Newer infrastructure projects build on SummarizedExperiment, not ExpressionSet, and the reasons for that shift are the subject of this article.

## What ExpressionSet is

### History and purpose

ExpressionSet is defined in the Biobase package and became the standard container for microarray expression data in the mid-2000s. It was designed around the classic microarray workflow: raw intensity data goes through background correction, normalization, and summarization, and the analyst ends up with a matrix of expression values, typically one row per probe set and one column per sample. That workflow produced enormous bodies of published results, and a large ecosystem of packages was written to consume ExpressionSet objects.

### Anatomy of an ExpressionSet

An ExpressionSet holds four things:

1. **assayData**, which contains the expression matrix itself. The primary matrix lives in a slot accessed with `exprs()`. Some containers also allow a second matrix such as standard errors through `assayDataElement()`, but in practice most ExpressionSet objects carry exactly one matrix named `exprs`.
2. **phenoData**, an AnnotatedDataFrame describing the samples. You access it with `pData()`, and each row corresponds to a column of the expression matrix. This is where you store condition, batch, age, or any other sample-level variable.
3. **featureData**, an AnnotatedDataFrame describing the rows, accessed with `fData()`. For microarrays this typically holds probe identifiers, gene symbols, and chromosome locations as plain data frame columns.
4. **experimentData**, a MIAME object holding free-form experiment description fields such as the title, abstract, and laboratory information. You access it with `experimentData()`.

There is also `annotation()`, a short string naming the annotation package, such as `hgu95av2.db`, that maps probe identifiers to genomic metadata.

A minimal construction looks like this:

```r
library(Biobase)

mat <- matrix(rnorm(1000), nrow = 100)  # 100 genes, 10 samples
rownames(mat) <- paste0("gene_", 1:100)
colnames(mat) <- paste0("sample_", 1:10)

pd <- AnnotatedDataFrame(data.frame(
  condition = rep(c("control", "treated"), each = 5),
  row.names = colnames(mat)
))

es <- ExpressionSet(assayData = mat, phenoData = pd)
exprs(es)[1:3, 1:3]        # the matrix
pData(es)[1:3, ]           # sample metadata
fData(es)                  # feature metadata (empty here)
```

### Strengths and limits

ExpressionSet is compact, well documented, and understood by a generation of packages for microarray analysis, including many still in routine use for normalization and quality assessment of array data. It remains the natural format for legacy Gene Expression Omnibus (GEO) series that ship as ExpressionSet objects through the GEOquery package.

Its limits are structural. The primary matrix is a single `exprs` matrix stored in memory. Feature metadata is a plain data frame with no enforced link to genomic coordinates, so nothing stops the row metadata and the matrix rows from falling out of sync, and there is no native way to represent where a feature sits on the genome. As assays moved from probes on an array to genes, transcripts, exons, peaks, windows, and cells with genomic positions, those limits became binding. Methylation arrays illustrate the tension well. The Illumina 450K platform required careful handling of probe design and normalization, and the β value for a CpG site is defined as β = M / (M + U + 100), where M and U are methylated and unmethylated signal intensities [4]. Analyzing such data genome-wide, intersecting CpG positions with regulatory features, or subsetting by chromosome demands a container that knows about genomic coordinates. ExpressionSet does not provide that natively.

## What SummarizedExperiment is

### History and purpose

SummarizedExperiment was introduced in the 2010s within the GenomicRanges ecosystem to generalize the ExpressionSet pattern for sequencing-era assays. The design goal was to keep the good part of ExpressionSet, a matrix plus sample metadata, and add what sequencing data needed: multiple assay layers, row metadata anchored to genomic ranges, a formal slot for unstructured experiment metadata, and support for alternative matrix backends. It succeeded to the point where SummarizedExperiment and its subclasses are now the default input and output types for most new Bioconductor packages.

### Anatomy of a SummarizedExperiment

A SummarizedExperiment holds:

1. **assays**, a list of one or more matrices, all with identical dimensions. The first one is conventionally named `counts` or `logcounts` depending on the data type. You access them with `assay(se, "counts")` or `assays(se)` for the full list. Having several aligned matrices is a real design improvement. Raw counts, normalized values, log-transformed values, and variance-stabilized values can all live side by side in one object, guaranteed to stay row-for-row and column-for-column consistent.
2. **rowRanges** (also reachable as `rowData`), which holds per-row metadata. In the ranged form, RangedSummarizedExperiment, this is a GRanges object, so every row carries its chromosome, start, end, and strand. Any additional gene-level annotations ride along as metadata columns of that GRanges object.
3. **colData**, a DataFrame of sample metadata accessed with `colData()`, playing the same role as `pData()` in ExpressionSet.
4. **metadata**, an unstructured list for experiment-level information, accessed with `metadata()`. This replaces and generalizes the fixed MIAME fields.

Construction is explicit:

```r
library(SummarizedExperiment)

counts <- matrix(rpois(1000, 10), nrow = 100,
                 dimnames = list(paste0("gene_", 1:100),
                                 paste0("sample_", 1:10)))
gr <- GRanges(seqnames = rep("chr1", 100),
              ranges = IRanges(start = seq(1, 9901, 100),
                               width = 100))
coldata <- data.frame(condition = rep(c("control", "treated"), each = 5),
                      row.names = colnames(counts))

se <- SummarizedExperiment(assays = list(counts = counts),
                           rowRanges = gr,
                           colData = coldata)
assay(se, "counts")[1:3, 1:3]
rowRanges(se)[1:3]
colData(se)[1:3, ]
```

Subsetting keeps everything aligned. `se[1:10, 1:5]` returns a new object where the assays, the row ranges, and the column data are all consistently subset. That guarantee is one of the strongest practical arguments for the class.

### Matrix backends and scale

A crucial difference from ExpressionSet is that the assays in a SummarizedExperiment do not have to be ordinary R matrices. They can be sparse matrices or file-backed matrices that stay on disk and are read in chunks. The beachmat package was built exactly for this situation, providing a C++ API that lets developer code access data agnostically "from various matrix representations," including "dense, sparse and file-backed matrices," so that large single-cell RNA-seq experiments can be handled within memory limits on commodity hardware [5]. An ExpressionSet stores its matrix in an environment-based assayData structure that does not have this backend flexibility.

This matters for real projects. A single-cell dataset with 30,000 genes and 100,000 cells is a 3 billion entry matrix. As a dense double-precision matrix that is tens of gigabytes. As a sparse or HDF5-backed assay inside a SummarizedExperiment subclass, it can be worked with on a laptop. The authors of beachmat evaluated access performance across these representations and described "a clear memory/speed trade-off to motivate the choice of an appropriate representation" [5]. None of that machinery is available inside an ExpressionSet.

### The family of subclasses

The most important consequence of choosing SummarizedExperiment is inheritance. Because SummarizedExperiment is an S4 base class, entire families of specialized containers extend it while remaining compatible with code written for the parent class:

- **SingleCellExperiment** extends it for single-cell data, adding slots for reduced dimensions such as PCA and UMAP coordinates, alternative feature sets such as spike-ins, and size factor storage.
- **DESeqDataSet** extends it for differential expression analysis in the DESeq2 framework.
- **GenomicRatioSet** and related classes in the methylation analysis ecosystem extend it so that CpG-level methylation values travel with their genomic positions.
- **QTLExperiment**, a recent container for storing and manipulating QTL summary statistics, was explicitly designed by "building upon existing Bioconductor infrastructure and conventions" so that the object would be consistent and interoperable [6]. The fact that its authors chose SummarizedExperiment as that foundation is a strong signal about current practice.
- **CoverageExperiment** in the tidyCoverage package "directly extends the SummarizedExperiment fundamental class" to organize genome-wide coverage tracks over sets of genomic loci [7].
- **InteractionSet** provides the class hierarchy for Hi-C and ChIA-PET interaction data, again exploiting existing Bioconductor infrastructure while being consumed by higher-level packages [2].

The pattern is consistent. When Bioconductor developers need a new data container, they extend SummarizedExperiment. The tidyomics ecosystem, which bridges Bioconductor to the tidy R paradigm, was demonstrated on data "spanning six data frameworks," and its compatibility strategy is built around the modern class hierarchy rather than the legacy one [8]. Even applied packages that orchestrate complete pipelines, such as exvar for RNA-seq analysis and visualization, compose their workflows from current Bioconductor building blocks [9].

## Side-by-side comparison

![Comparison table mapping the expression matrix, sample metadata, feature metadata and experiment description to their accessors in each container.](/article-images/illustrations/summarizedexperiment-vs-expressionset-anatomy-slots.webp)

The table below is the core of the difference between SummarizedExperiment and ExpressionSet.

| Feature | ExpressionSet | SummarizedExperiment |
|--|--|--|
| Home package | Biobase | SummarizedExperiment |
| Era and design target | Microarray era, single expression matrix | Sequencing era, general matrix assay |
| Number of assay matrices | One primary `exprs` matrix, optional extra elements | Any number of named, dimension-matched assays |
| Assay accessor | `exprs()` | `assay()`, `assays()` |
| Row metadata | `featureData` / `fData()`, plain data frame | `rowRanges` (GRanges) or `rowData` (DataFrame) |
| Genomic coordinates | Not native, must be inferred from annotation packages | Native when ranged, rows carry chromosome, start, end, strand |
| Column metadata | `phenoData` / `pData()` | `colData` |
| Experiment metadata | Fixed MIAME fields via `experimentData()` | Free-form list via `metadata()` |
| Matrix backends | In-memory | In-memory, sparse, or file-backed (HDF5Array, DelayedArray) [5] |
| Subsetting guarantee | Matrix and metadata subset by index | Row, column, and metadata always kept in sync |
| Class extensions | Few, legacy (e.g., older array classes) | Extensive (SingleCellExperiment, DESeqDataSet, TreeSummarizedExperiment, QTLExperiment [6], CoverageExperiment [7]) |
| Ecosystem direction | Maintained for compatibility | Default for new Bioconductor packages [6][7][8] |
| Typical data | Legacy microarrays, some GEO datasets | RNA-seq, single-cell, ATAC-seq, methylation, proteomics, QTL summaries |
| Tidy-style access | Not supported | Supported through the tidyomics ecosystem [8] |

## The differences that actually decide the choice

### Genomic coordinates

If any analysis touches genomic position, SummarizedExperiment is effectively mandatory. With a RangedSummarizedExperiment you can subset by chromosome with `se[GRanges("chr7:1-1000000"), ]`, find nearest features, compute overlaps with peak sets, and pass the object to genome-aware downstream tools, all without manually maintaining a parallel annotation data frame. Legacy containers force you to join probe identifiers against annotation packages yourself and hope nothing drifts out of order.

### Multiple assays

Bulk RNA-seq analysis routinely needs raw counts for differential expression testing, normalized values for plotting, and transformed values for clustering. In a SummarizedExperiment these coexist as named assays with guaranteed dimension agreement. In an ExpressionSet you juggle separate matrices and re-check row alignment yourself. This is one of the most common sources of silent analysis errors in older workflows, where the expression matrix and its feature annotations can end up in different row orders after intermediate steps.

### Memory and scale

For bulk datasets with a few thousand genes and a few dozen samples, in-memory storage is fine and both containers behave the same. Scale changes the calculus. Sparse and file-backed assay backends let a SummarizedExperiment subclass hold data far larger than RAM, and the beachmat interface means C++ analysis code works across those representations without modification [5]. If your project involves single-cell data or large panels, the choice has already been made for you, because SingleCellExperiment is a SummarizedExperiment subclass.

### Ecosystem gravity

Look at where new development lands. Multi-state QTL analysis infrastructure chose SummarizedExperiment conventions for its container [6]. Epigenomic coverage tooling extends SummarizedExperiment directly [7]. Tidy-style omics analysis is built around modern Bioconductor frameworks [8]. Interactive and pipeline packages compose from current classes [9]. Meanwhile, ExpressionSet support persists mainly where legacy methods require it. runibic supporting both classes is the exception that proves the rule, an integration bridge built to serve users on either side of the divide [3].

None of this means ExpressionSet is broken. Biobase is actively maintained, and a great deal of published microarray analysis remains reproducible in that format. The honest statement is directional: SummarizedExperiment is where the ecosystem is going, and ExpressionSet is where a lot of valuable legacy data still lives.

## Worked examples

### Building each container from a count matrix

Suppose you have a raw count matrix from an RNA-seq pipeline, a data frame of sample information, and a table mapping genes to genomic coordinates.

```r
library(SummarizedExperiment)

# counts: genes x samples matrix
se <- SummarizedExperiment(
  assays = list(counts = counts),
  rowData = gene_annotation,          # DataFrame, same nrow as counts
  colData = sample_annotation         # DataFrame, same nrow as ncol(counts)
)
```

If you also have a GRanges object for the genes, pass `rowRanges = gr` instead of `rowData = gene_annotation` and you get a RangedSummarizedExperiment. Adding a second assay later is a one-line operation that keeps everything aligned:

```r
logcounts <- log2(counts + 1)
assay(se, "logcounts") <- logcounts
assayNames(se)   # "counts" "logcounts"
```

The ExpressionSet equivalent:

```r
library(Biobase)

es <- ExpressionSet(
  assayData = counts,
  phenoData = AnnotatedDataFrame(sample_annotation),
  featureData = AnnotatedDataFrame(gene_annotation)
)
exprs(es)[1:3, 1:3]
```

Note what is missing: there is nowhere natural to put genomic coordinates, and no way to add a second matrix as a peer of the first.

### Converting from ExpressionSet to SummarizedExperiment

When a downstream package requires a SummarizedExperiment and you have an ExpressionSet, use the provided conversion function rather than rebuilding by hand:

```r
library(SummarizedExperiment)
se <- makeSummarizedExperimentFromExpressionSet(es)
```

This transfers the expression matrix to the first assay, `pData` to `colData`, `fData` to `rowData`, and `experimentData` fields into `metadata`. Because `fData` rows are plain annotations, the result is a SummarizedExperiment without row ranges unless your feature data can be assembled into a GRanges object. For older objects that have been saved and reloaded across Bioconductor versions, call `updateObject(es)` first so internal structures match the current class definitions.

### Round-tripping in a legacy project

Many analysts work in both directions. A common pattern is downloading GEO data as an ExpressionSet, converting once at the top of the script, and running the entire downstream analysis on the SummarizedExperiment:

```r
library(GEOquery)
es <- getGEO("GSE12345")[[10]]
se <- makeSummarizedExperimentFromExpressionSet(es)
```

Doing the conversion once, immediately after import, keeps the rest of the script in one container idiom and avoids mixing `pData` and `colData` calls throughout your code.

## Which packages expect which container

For current differential expression and single-cell work, assume SummarizedExperiment. DESeq2 constructs its DESeqDataSet, a SummarizedExperiment subclass, from a count matrix, a SummarizedExperiment, or an ExpressionSet. The limma-voom pipeline accepts a SummarizedExperiment or a DGEList from edgeR. Single-cell packages such as scran and scater operate on SingleCellExperiment. Multi-omics integration through MultiAssayExperiment stores each assay as an experiment-class object, most often SummarizedExperiment derivatives.

On the legacy side, ExpressionSet remains the input for a substantial body of microarray tooling, including classification and survival packages written against Biobase, and it is the natural output of older array preprocessing pipelines. If you are reproducing a published microarray analysis from 2008 to 2014, expect ExpressionSet at every step.

When evaluating any new package, read the class signature of its main function. If it says `object` and documents a SummarizedExperiment method, write your import step to produce a SummarizedExperiment. Packages that bridge both worlds, such as runibic, which supports SummarizedExperiment, ExpressionSet, and biclust inputs, usually list accepted classes in their vignette [3].

## Worked scenarios

### Scenario 1: A legacy microarray study from GEO

You want to reanalyze a published breast cancer microarray cohort. The dataset downloads as an ExpressionSet with 22,000 probe sets and 250 samples. Your plan is normalization, batch assessment, and differential expression with a well-established array package.

**Recommendation:** stay in ExpressionSet for the legacy preprocessing steps, then convert to SummarizedExperiment if you want to use modern visualization or tidy-style manipulation downstream. There is no benefit to rewriting a working microarray pipeline, and the legacy packages that handle probe-level array quality are ExpressionSet native. If you later want to map probes to genomic positions and intersect with regulatory annotations, the conversion step becomes worthwhile.

### Scenario 2: A bulk RNA-seq differential expression project

You have a count matrix from an aligner or pseudoaligner, sample metadata including batch and condition, and gene coordinates from a recent genome build.

**Recommendation:** SummarizedExperiment, from the first line of the script. Build the ranged object once, attach both raw counts and log counts as named assays, and let DESeq2 or limma-voom consume it. Genomic coordinates let you quickly inspect results in context, for example pulling all genes within a candidate locus. This is the textbook use case for when to use SummarizedExperiment.

### Scenario 3: A single-cell dataset at scale

You have 80,000 cells and 25,000 genes, and the full dense matrix will not fit comfortably in memory on your workstation.

**Recommendation:** SingleCellExperiment, which is a SummarizedExperiment subclass, with the counts stored as a sparse matrix or an HDF5-backed DelayedArray. The sparse and file-backed representations exist precisely because scRNA-seq experiments "frequently generate large matrices containing expression values for each gene in each cell, requiring sparse or file-backed representations for memory-efficient manipulation" [5]. ExpressionSet is not a serious option here, and no mainstream single-cell package expects one.

### Scenario 4: Integrating expression with chromatin interaction data

You are studying whether genes with differential expression between conditions sit in regions whose chromatin contacts differ between the same conditions. Expression lives in a SummarizedExperiment. Contact data from Hi-C or ChIA-PET lives in an InteractionSet, the class hierarchy built to represent genomic interactions and their associated experimental measurements [2]. Because both containers are ranged and share GenomicRanges semantics, you can intersect gene ranges with interaction anchors directly. The GenomicInteractions package provides annotation and visualization methods for these datasets across Hi-C and ChIA-PET experiments [10].

**Recommendation:** two ranged containers used together. This scenario would be painful with an ExpressionSet because the expression-side object would lack coordinates and every gene-to-contact mapping would require a manual identifier join.

### Scenario 5: A cohort with multiple assay types per sample

You have RNA-seq counts, ATAC-seq peaks, and methylation beta values measured on the same patients, and you need one analysis that spans all of them.

**Recommendation:** MultiAssayExperiment, where each assay is held as its own SummarizedExperiment and the container manages sample mapping across experiments. ExpressionSet was never designed for this, and gluing multiple ExpressionSets together manually is the kind of bookkeeping that produces sample-order errors.

## A decision procedure you can apply in one minute

![Decision tree asking whether a package requires SummarizedExperiment, whether rows carry genomic coordinates, and whether several matrices are needed.](/article-images/illustrations/summarizedexperiment-vs-expressionset-container-triage.webp)

Ask these questions in order:

1. **Does any package you need require SummarizedExperiment or a subclass?** If yes, that settles it. Check the function documentation, not memory.
2. **Do your rows have meaningful genomic coordinates?** If yes, use a ranged SummarizedExperiment. Coordinates native to the object eliminate an entire class of annotation errors.
3. **Do you need more than one matrix in the same object?** Raw plus normalized, counts plus transformed, or multiple imputations all favor SummarizedExperiment.
4. **Is the dataset large enough to need sparse or on-disk storage?** If you are measuring memory in tens of gigabytes, SummarizedExperiment with a suitable backend, accessed through tooling such as beachmat [5], is the practical path.
5. **Is your input a legacy microarray dataset or does your key downstream package require ExpressionSet?** If yes, keep ExpressionSet for those steps and consider converting at the boundary.
6. **Are you writing a new package?** Build on SummarizedExperiment. New Bioconductor infrastructure consistently extends it, from QTL containers [6] to coverage tooling [7], and that is where interoperability comes from [2].

## Common Mistakes and Limitations

**Mixing accessor vocabularies.** The most common error when moving between containers is calling `pData()` on a SummarizedExperiment or `colData()` on an ExpressionSet. Each raises an error, but under time pressure analysts sometimes "fix" it by converting objects mid-script, which multiplies confusion. Pick one idiom per script and convert once at the boundary.

**Assuming row order matches between a matrix and a separate annotation table.** This is the classic ExpressionSet-era failure mode. If you build a SummarizedExperiment from a matrix and a rowData table, stop and verify `identical(rownames(assay(se)), rownames(rowData))` before any subsetting. The container will keep things aligned once constructed, but it cannot validate names it was never given.

**Subsetting an ExpressionSet by genomic position.** There is no native support. Analysts sometimes sort both the matrix and a coordinate table by chromosome and position and then subset by row index. This works only until any upstream step reorders rows. Do the range-based subsetting on a ranged SummarizedExperiment instead.

**Forgetting that subsetting semantics differ for metadata columns.** In both containers, `obj[rows, cols]` subsets data and metadata together, which is a feature. But analysts coming from tidy workflows sometimes extract `colData` or `pData` as a data frame, modify it, and forget to write it back, leaving the object and the local copy out of sync. Modify in place with an assignment, for example `colData(se)$batch <- batch_vector`, or use `<-` on the extracted, modified, and reassigned DataFrame.

**Converting without checking what was lost.** Converting an ExpressionSet with `makeSummarizedExperimentFromExpressionSet` transfers the exprs matrix and metadata tables, but if your feature annotation contained coordinate columns as character strings, they arrive as plain data columns, not as a GRanges. Building a proper ranged object requires converting those columns yourself.

**Assuming a class name means compatibility.** A function that accepts a SummarizedExperiment may also require a specific assay name, specific colData columns, or a subclass such as SingleCellExperiment. Read the method signature and the vignette. The QTL containers illustrate how far subclasses can specialize the parent interface while remaining consistent with Bioconductor conventions [6].

**The general limitation.** Container choice shapes which packages you can use with minimal friction, but it does not change the statistics of your analysis, the quality of your annotation, or the validity of your biological conclusions. A well-designed analysis in ExpressionSet beats a careless one in SummarizedExperiment. Individual projects also have genuine legacy constraints, such as a required package version pinned in a validated pipeline, that can override every general recommendation in this article. When a specific dataset or regulatory context demands it, follow the constraints of that project rather than the general rule.

## Frequently Asked Questions

### Should I convert my existing ExpressionSet objects to SummarizedExperiment?

Convert if the rest of your analysis uses modern packages, and keep the ExpressionSet if you depend on legacy microarray tools. The conversion function `makeSummarizedExperimentFromExpressionSet()` makes the switch cheap, so the cost is low either way. A reasonable default is to convert at the point where a modern package first requires it.

### What is the main difference between SummarizedExperiment and ExpressionSet?

The main difference is design era and capability. SummarizedExperiment supports multiple named assays, native genomic row ranges, free-form metadata, and sparse or file-backed matrix backends. ExpressionSet supports a single in-memory expression matrix with phenotype and feature annotation tables, which matched microarray workflows but does not scale to sequencing-era requirements.

### Can I use ExpressionSet for RNA-seq data?

You can store an RNA-seq count matrix in an ExpressionSet, and some older packages will accept it, but you lose multiple assays, genomic coordinates, and backend flexibility. Every current RNA-seq differential expression package works naturally with SummarizedExperiment or its subclasses, so starting from ExpressionSet for RNA-seq creates conversion work with no offsetting benefit.

### How do I access the data matrix in each container?

In ExpressionSet, use `exprs(es)`. In SummarizedExperiment, use `assay(se, "counts")` for a named assay or `assays(se)` for the list of all assays. The `assay()` call with a name argument is the direct replacement for the single `exprs()` accessor.

### Does SummarizedExperiment replace ExpressionSet?

Functionally it has become the default for new work, and new Bioconductor infrastructure builds on it rather than on ExpressionSet, but ExpressionSet is not deprecated and remains the standard container for much of the legacy microarray ecosystem. Treat ExpressionSet as a maintained legacy format and SummarizedExperiment as the current standard.

### What is the relationship between SummarizedExperiment and SingleCellExperiment?

SingleCellExperiment is a subclass of SummarizedExperiment. It inherits all SummarizedExperiment behavior, including assays, colData, and subsetting, and adds slots for reduced-dimension representations, alternative feature sets, and size factors. Any code that works on a SummarizedExperiment works on a SingleCellExperiment, which is why single-cell tooling builds there.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Should I convert my existing ExpressionSet objects to SummarizedExperiment?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Convert if the rest of your analysis uses modern packages, and keep the ExpressionSet if you depend on legacy microarray tools. The conversion function makeSummarizedExperimentFromExpressionSet() makes the switch cheap, so a reasonable default is to convert at the point where a modern package first requires it."
      }
    },
    {
      "@type": "Question",
      "name": "What is the main difference between SummarizedExperiment and ExpressionSet?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "SummarizedExperiment supports multiple named assays, native genomic row ranges, free-form metadata, and sparse or file-backed matrix backends. ExpressionSet supports a single in-memory expression matrix with phenotype and feature annotation tables, which matched microarray workflows but does not scale to sequencing-era requirements."
      }
    },
    {
      "@type": "Question",
      "name": "Can I use ExpressionSet for RNA-seq data?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "You can store an RNA-seq count matrix in an ExpressionSet, but you lose multiple assays, genomic coordinates, and backend flexibility. Current [RNA-seq differential expression](/knowledge/bioinformatics/rna-seq-differential-expression-deseq2-edger) packages work naturally with SummarizedExperiment or its subclasses, so starting from ExpressionSet for RNA-seq creates conversion work with no offsetting benefit."
      }
    },
    {
      "@type": "Question",
      "name": "How do I access the data matrix in each container?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "In ExpressionSet, use exprs(es). In SummarizedExperiment, use assay(se, \"counts\") for a named assay or assays(se) for the list of all assays."
      }
    },
    {
      "@type": "Question",
      "name": "Does SummarizedExperiment replace ExpressionSet?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Functionally it has become the default for new work, and new Bioconductor infrastructure builds on it rather than on ExpressionSet, but ExpressionSet is not deprecated and remains standard for much of the legacy microarray ecosystem. Treat ExpressionSet as a maintained legacy format and SummarizedExperiment as the current standard."
      }
    },
    {
      "@type": "Question",
      "name": "What is the relationship between SummarizedExperiment and SingleCellExperiment?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "SingleCellExperiment is a subclass of SummarizedExperiment. It inherits all SummarizedExperiment behavior, including assays, colData, and subsetting, and adds slots for reduced-dimension representations, alternative feature sets, and size factors, which is why single-cell tooling builds there."
      }
    }
  ]
}
</script>

## Sources

1. [ShortRead: a bioconductor package for input, quality assessment and exploration of high-throughput sequence data.](https://pubmed.ncbi.nlm.nih.gov/19654119/)
2. [Infrastructure for genomic interactions: Bioconductor classes for Hi-C, ChIA-PET and related experiments.](https://pubmed.ncbi.nlm.nih.gov/27303634/)
3. [runibic: a Bioconductor package for parallel row-based biclustering of gene expression data.](https://pubmed.ncbi.nlm.nih.gov/29939213/)
4. [A data-driven approach to preprocessing Illumina 450K methylation array data.](https://pubmed.ncbi.nlm.nih.gov/23631413/)
5. [beachmat: A Bioconductor C++ API for accessing high-throughput biological data from a variety of R matrix types.](https://pubmed.ncbi.nlm.nih.gov/29723188/)
6. [Orchestrating multi-state QTL analysis with Bioconductor.](https://pubmed.ncbi.nlm.nih.gov/42231150/)
7. [Epigenomics coverage data extraction and aggregation in R with tidyCoverage.](https://pubmed.ncbi.nlm.nih.gov/39073878/)
8. [The tidyomics ecosystem: enhancing omic data analyses.](https://pubmed.ncbi.nlm.nih.gov/38877315/)
9. [Efficient and easy gene expression and genetic variation data analysis and visualization using exvar.](https://pubmed.ncbi.nlm.nih.gov/40210898/)
10. [GenomicInteractions: An R/Bioconductor package for manipulating and investigating chromatin interaction data.](https://pubmed.ncbi.nlm.nih.gov/26576536/)