Module 25 Lesson 1 of 10 · ⏱ ~55 min · R · Seurat · scRNA-seq

Introduction to scRNA-seq & Seurat

Understand what single-cell RNA sequencing measures, why it revolutionised cell biology, and how to install and configure Seurat — the most widely used R package for scRNA-seq analysis.

What is single-cell RNA sequencing?

Every cell in your body carries the same genome — the same DNA sequence. But cells are wildly different: a root cell in Sorghum bicolor behaves nothing like a leaf mesophyll cell, even though they share identical DNA. The reason is gene expression: different cells switch different genes on and off.

Traditional bulk RNA sequencing (RNA-seq) measures the average gene expression across millions of cells in a tissue sample. That average destroys the identity of individual cells. Rare cell types — a stress-activated immune cell, a newly differentiating stem cell — are completely masked by the majority.

Single-cell RNA sequencing (scRNA-seq) solves this by measuring gene expression in each cell individually. You end up with a matrix where each row is a cell and each column is a gene — giving you a transcriptomic fingerprint for thousands of cells at once.

🔬 Why this matters in plant genomics

In sorghum research, bulk RNA-seq would tell you that drought stress changes expression across the whole root. scRNA-seq tells you which specific root cell types respond — epidermis, endodermis, stele, quiescent centre — and by how much. This resolution is essential for understanding where and how tolerance mechanisms activate.

What scRNA-seq actually measures

In a typical scRNA-seq experiment:

  • Cells are dissociated from tissue into a single-cell suspension
  • Each cell is captured in a tiny droplet (e.g. 10x Genomics Chromium) containing a bead with a unique cell barcode
  • RNA from the cell is reverse-transcribed and labelled with the barcode plus a Unique Molecular Identifier (UMI) per transcript
  • Libraries are sequenced; reads are demultiplexed by barcode → each barcode = one cell
  • UMI counts per gene per cell form the raw count matrix
💡

UMIs are critical. Without UMIs, PCR amplification bias would make highly-amplified transcripts look more abundant than they are. UMIs mark each original mRNA molecule uniquely, so duplicates can be collapsed before counting. Seurat always works with UMI counts, never raw read counts.

Bulk RNA-seq vs Single-cell RNA-seq

Understanding the differences shapes every decision you make in a Seurat workflow.

Feature Bulk RNA-seq scRNA-seq
Resolution Tissue average (millions of cells) Per-cell (thousands of individual cells)
Input ~1 µg total RNA Single-cell suspension
Sparsity Most genes detected Very sparse — most entries are zero
Cost Lower per sample Higher (£300–£1000+ per sample)
Cell type discovery Requires prior marker knowledge Unbiased clustering reveals new types
Rare cells Signal drowned out Detected if sampled
Analysis tool DESeq2, edgeR, limma Seurat, Scanpy, Monocle
⚠️

Sparsity is the defining challenge. A typical scRNA-seq matrix is 80–95 % zeros. Not because those genes are absent — but because mRNA capture is inefficient and not every transcript is sequenced. This is called dropout. Every step in the Seurat workflow is designed to handle or mitigate this sparsity.

Why Seurat?

🧰 What Seurat actually is

Seurat is an R package developed at the Satija Lab (New York Genome Center). It provides a complete end-to-end workflow for scRNA-seq analysis: from raw count matrices all the way to annotated cell type clusters and publication-quality figures. It is the most cited scRNA-seq tool in the world, with over 20,000 citations as of 2025.

Seurat is not just a collection of functions — it introduces a dedicated data structure called the Seurat Object that stores everything in one place:

  • The raw count matrix (stored as a sparse matrix to save memory)
  • Normalised and scaled data
  • Dimensionality reduction results (PCA, UMAP)
  • Cluster identities (called "idents")
  • Cell-level metadata (QC metrics, sample origin, annotations)
  • Gene-level metadata

By keeping everything in one object, you never lose track of which cells belong to which analysis state. This is fundamentally different from the DESeq2 workflow, where you manage separate data frames for counts, metadata, and results.

Seurat versions

VersionKey additionCurrent?
Seurat v2Initial Canonical Correlation Analysis (CCA) integrationLegacy
Seurat v3Anchor-based integration, SCTransform normalisationWidely used
Seurat v4Weighted nearest neighbour (WNN) multimodal analysisStable
Seurat v5Sketch-based analysis, split layers, improved scalability✅ Latest (2024+)
💡

This module uses Seurat v5. If you find older tutorials online (2019–2022), some function names and argument names differ. Always check the version with packageVersion("Seurat") before following any tutorial.

Understanding 10x Genomics data formats

The most common scRNA-seq platform is the 10x Genomics Chromium system. After sequencing and alignment (using Cell Ranger), 10x produces a standard output folder containing three files:

FileContentsSize
matrix.mtx.gz Sparse count matrix in Matrix Market Exchange format — row/col indices + non-zero values Typically 10–500 MB
barcodes.tsv.gz Cell barcodes — one per line; the row labels of the matrix Kilobytes
features.tsv.gz Gene identifiers and names — the column labels of the matrix Kilobytes
📦 Why the MEX (Matrix Market Exchange) format?

A full 10x count matrix might have 30,000 genes × 10,000 cells = 300 million entries. Storing all of these as a regular dense matrix would require ~2.4 GB just for integers. Since 90 % of entries are zero, the MEX format stores only the non-zero values along with their row and column positions — typically reducing the file to a few megabytes. R loads this into a dgCMatrix (dense-generic-compressed sparse matrix), which Seurat uses internally throughout.

The three files together are called the filtered feature-barcode matrix and are typically located at:

Directory structure
sample_output/
└── outs/
    └── filtered_feature_bc_matrix/
        ├── matrix.mtx.gz
        ├── barcodes.tsv.gz
        └── features.tsv.gz

Seurat's Read10X() function reads all three files at once and combines them into a single sparse matrix — you just point it at the folder and it does the rest.

Installing Seurat

🔧 Before you install

Seurat v5 requires R ≥ 4.1.0 and depends on several Bioconductor packages. The cleanest approach is to install from CRAN (which handles most dependencies automatically) and then install Bioconductor packages separately. On Ubuntu, you may also need system libraries.

Step 1 — Check your R version

📁 Run from: R console (any working directory)
R
# Check current R version — must be ≥ 4.1.0
R.version.string

# Check if Seurat is already installed
packageVersion("Seurat")

Step 2 — Install system libraries (Ubuntu)

📁 Run from: Ubuntu terminal
Bash
# Required C libraries for R packages that Seurat depends on
sudo apt-get install -y \
  libhdf5-dev \
  libcurl4-openssl-dev \
  libssl-dev \
  libxml2-dev \
  libgeos-dev \
  libgdal-dev \
  libproj-dev

# libhdf5-dev → needed for reading HDF5 (.h5) 10x files
# libcurl4-openssl-dev → needed for internet downloads inside R

Step 3 — Install Seurat from CRAN

📁 Run from: R console
R
# Install Seurat v5 from CRAN
install.packages("Seurat")

# Install SeuratObject (data structure package — sometimes installed automatically)
install.packages("SeuratObject")

# Install supporting packages
install.packages(c(
  "dplyr",       # data manipulation
  "ggplot2",     # plotting (Seurat uses ggplot2 internally)
  "patchwork",   # arrange multiple plots
  "Matrix",      # sparse matrix support
  "scales"       # colour scales for plots
))

Step 4 — Install Bioconductor dependencies

📁 Run from: R console
R
# Install BiocManager if not already present
if (!requireNamespace("BiocManager", quietly = TRUE))
  install.packages("BiocManager")

# Install Bioconductor packages used by Seurat
BiocManager::install(c(
  "limma",       # differential expression testing backend
  "glmGamPoi",   # fast GLM fitting for SCTransform
  "DESeq2"       # used for pseudobulk DE in later lessons
))
⚠️

Installation takes 10–20 minutes on first run because Seurat has ~100 dependencies. If any package fails with a compilation error, the missing system library is usually the cause — re-read Step 2 and check the error message for which C header is missing.

First look in R — verifying your installation

Once installed, let's verify everything works and take a first look at the PBMC dataset we will use throughout this module. PBMC stands for Peripheral Blood Mononuclear Cells — a mixture of immune cells (T cells, B cells, NK cells, monocytes) that is the standard benchmark dataset for Seurat tutorials.

🩸 Why PBMC?

The 10x Genomics PBMC 3k dataset (2,700 cells from a healthy donor) has well-characterised cell types with known markers. This makes it ideal for learning — you can immediately verify that your clustering is biologically correct. Every major scRNA-seq tool publishes a tutorial using this dataset. We will use it across all 10 lessons of this module.

Load Seurat and check version

📁 Run from: R console or RStudio
R
# Load Seurat
library(Seurat)
library(SeuratObject)
library(dplyr)
library(ggplot2)

# Confirm version — should be ≥ 5.0.0
packageVersion("Seurat")
# [1] '5.1.0'

# See all exported functions (over 300!)
ls("package:Seurat") |> head(20)

Download the PBMC 3k dataset

📁 Run from: Ubuntu terminal
Bash
# Create a working directory for this module
mkdir -p ~/scrna-seurat-pbmc/data/raw
cd ~/scrna-seurat-pbmc/data/raw

# Download the PBMC 3k filtered feature-barcode matrix (22 MB)
wget -q \
  https://cf.10xgenomics.com/samples/cell/pbmc3k/pbmc3k_filtered_gene_bc_matrices.tar.gz

# Extract — creates pbmc3k_filtered_gene_bc_matrices/hg19/
tar -xzf pbmc3k_filtered_gene_bc_matrices.tar.gz

# Inspect the three files Seurat will read
ls -lh pbmc3k_filtered_gene_bc_matrices/filtered_gene_bc_matrices/hg19/
# -rw-r--r--  barcodes.tsv   70K
# -rw-r--r--  genes.tsv      502K
# -rw-r--r--  matrix.mtx     17M
💡

The PBMC 3k data uses the older genes.tsv filename (not features.tsv.gz). This is fine — Read10X() handles both naming conventions automatically.

Peek at the raw files in R

📁 Run from: R console (working directory: ~/scrna-seurat-pbmc)
R
# Point to the data directory
data_dir <- "data/raw/pbmc3k_filtered_gene_bc_matrices/filtered_gene_bc_matrices/hg19"

# Read the sparse count matrix — returns a dgCMatrix
counts <- Read10X(data.dir = data_dir)

# What class is this?
class(counts)
# [1] "dgCMatrix"
attr(class(counts), "package")
# [1] "Matrix"

# Dimensions: genes × cells
dim(counts)
# [1] 32738  2700
# 32,738 genes measured in 2,700 cells

# First 5 genes, first 4 cells — most values are zero
counts[1:5, 1:4]
# 4 x 4 sparse Matrix of class "dgCMatrix"
#           AAACATACAACCAC AAACATTGAGCTAC AAACATTGATCAGC AAACCGTGCTTCCG
# AL627309.1             .              .              .              .
# AP006222.2             .              .              .              .
# RP11-206L10.2          .              .              .              .
# RP11-206L10.9          .              .              .              .
# LINC00115              .              .              .              .

# Dots (.) represent zero — the matrix is very sparse
# Calculate sparsity: % of entries that are zero
sparsity <- 1 - nnzero(counts) / prod(dim(counts))
cat(sprintf("Sparsity: %.1f%%\n", sparsity * 100))
# Sparsity: 94.3%

94.3 % of all entries in this matrix are zero. That single number explains why every decision in the Seurat workflow — normalisation method, feature selection, dimensionality reduction — is specifically designed for sparse count data.

Advertisement Support free bioinformatics education

Exercises

1
Verify your Seurat installation

Load Seurat in R and run packageVersion("Seurat"). If your version is below 5.0.0, update it using install.packages("Seurat"). Then run ?CreateSeuratObject to open the help page and read what the min.cells and min.features arguments do. We will use these in Lesson 2.

▶ Show answer

min.cells — include a gene only if it is detected in at least this many cells. Setting min.cells = 3 removes genes detected in fewer than 3 cells, which are unlikely to be informative.

min.features — include a cell only if it has at least this many genes detected. Setting min.features = 200 removes empty droplets and debris that have very few transcripts.

2
Explore the raw count matrix

After reading the PBMC data with Read10X(), answer these questions using R commands:

  • How many genes are in the dataset?
  • How many cells are in the dataset?
  • What is the maximum UMI count for any single gene in any single cell? (Hint: max(counts))
  • What are the first 5 cell barcodes? (Hint: colnames(counts)[1:5])
▶ Show answer

dim(counts) → 32,738 genes × 2,700 cells

max(counts) → approximately 1,000+ (varies; one highly expressed gene in one cell)

colnames(counts)[1:5] → the first 5 are cell barcodes like AAACATACAACCAC-1

3
Understand sparsity

Calculate the sparsity of the PBMC count matrix using the formula shown in the lesson. Then look up one gene you know — for example PTPRC (encodes CD45, a pan-leukocyte marker) — and find how many cells it is detected in:

sum(counts["PTPRC", ] > 0)

Is it detected in most cells or only a minority? Does this match your expectation for a marker gene expressed in all blood cells?

▶ Show answer

Sparsity ≈ 94.3 % — nearly 19 out of every 20 values is zero.

PTPRC is a pan-leukocyte marker, so it should be detected in most cells. Running sum(counts["PTPRC", ] > 0) shows it in roughly 1,900–2,200 cells (out of 2,700). That is about 70–80 % — high for a single gene, which confirms it is a good pan-marker. Most genes are detected in far fewer cells (often < 10 %).

Advertisement Your ad here