library(MouseGastrulationData)
sce <- WTChimeraData(
samples = 5,
type = "processed"
)Single-Cell RNA-seq Analysis with Bioconductor
A hands-on workflow from quality control to cell-type annotation
Introduction
This hands-on workshop walks through a compact but complete single-cell RNA-seq analysis using the Bioconductor ecosystem. The emphasis is on how the pieces fit together in practice: we start from a SingleCellExperiment, progressively add analysis results to the same object, and finish with biologically interpretable cell-type labels.
The statistical and biological foundations of the individual methods are covered in the accompanying theoretical session. Here, the goal is to build and inspect a working analysis pipeline.
Data → quality control → normalization → feature selection → PCA → UMAP → clustering → marker genes → cell-type annotation
By the end of the session, you should be able to:
- navigate a
SingleCellExperiment; - compute and inspect basic QC metrics;
- normalize counts and select highly variable genes;
- generate PCA and UMAP representations;
- identify graph-based clusters and their marker genes;
- annotate cells using an external reference with
SingleR.
1. Loading the data
We use the wild-type chimera dataset distributed with MouseGastrulationData. It contains mouse gastrulation cells with count data and rich cell-level metadata, while remaining small enough to run interactively during a workshop.
The object contains 2,411 cells and 29,453 features. The original count matrix is available in the counts assay, and the supplied metadata include sample information and mapped cell-type annotations.
sceclass: SingleCellExperiment
dim: 29453 2411
metadata(0):
assays(1): counts
rownames(29453): ENSMUSG00000051951 ENSMUSG00000089699 ...
ENSMUSG00000095742 tomato-td
rowData names(2): ENSEMBL SYMBOL
colnames(2411): cell_9769 cell_9770 ... cell_12178 cell_12179
colData names(11): cell barcode ... doub.density sizeFactor
reducedDimNames(2): pca.corrected.E7.5 pca.corrected.E8.5
mainExpName: NULL
altExpNames(0):
At this stage the object already contains the raw count matrix and metadata, but it does not yet contain the logcounts, PCA, UMAP, clustering or annotation results that we will generate below. The analysis will progressively enrich this same object rather than creating disconnected data structures.
2. The SingleCellExperiment object
SingleCellExperiment is the central container used by many Bioconductor single-cell packages. It keeps expression matrices, feature annotations, cell-level metadata and low-dimensional representations synchronized.
SingleCellExperiment object, showing the main assays, row- and column-level metadata, and reduced-dimensional representations.
The expression matrix forms the core of the object, while feature and cell annotations are stored alongside it. Results generated during the analysis, such as PCA or UMAP coordinates, can be added to the same object without breaking the correspondence between genes and cells.
Assays and annotations
The most important components for this workshop are:
assays: matrices such as raw counts and normalized expression;rowData: feature-level annotations;colData: cell-level annotations and QC metrics;reducedDims: PCA, UMAP and other low-dimensional representations.
assayNames(sce)[1] "counts"
counts(sce)[1:5, 1:5]5 x 5 sparse Matrix of class "dgCMatrix"
cell_9769 cell_9770 cell_9771 cell_9772 cell_9773
ENSMUSG00000051951 . . . . .
ENSMUSG00000089699 . . . . .
ENSMUSG00000102343 . . . . .
ENSMUSG00000025900 . . . . .
ENSMUSG00000025902 . . . . .
colData(sce)DataFrame with 2411 rows and 11 columns
cell barcode sample stage tomato
<character> <character> <integer> <character> <logical>
cell_9769 cell_9769 AAACCTGAGACTGTAA 5 E8.5 TRUE
cell_9770 cell_9770 AAACCTGAGATGCCTT 5 E8.5 TRUE
cell_9771 cell_9771 AAACCTGAGCAGCCTC 5 E8.5 TRUE
cell_9772 cell_9772 AAACCTGCATACTCTT 5 E8.5 TRUE
cell_9773 cell_9773 AAACGGGTCAACACCA 5 E8.5 TRUE
... ... ... ... ... ...
cell_12175 cell_12175 TTTGGTTAGTCCGTAT 5 E8.5 TRUE
cell_12176 cell_12176 TTTGGTTAGTGTTGAA 5 E8.5 TRUE
cell_12177 cell_12177 TTTGGTTGTTAAAGAC 5 E8.5 TRUE
cell_12178 cell_12178 TTTGGTTTCAGTCAGT 5 E8.5 TRUE
cell_12179 cell_12179 TTTGGTTTCGCCATAA 5 E8.5 TRUE
pool stage.mapped celltype.mapped closest.cell
<integer> <character> <character> <character>
cell_9769 3 E8.25 Mesenchyme cell_24159
cell_9770 3 E8.5 Endothelium cell_96660
cell_9771 3 E8.5 Allantois cell_134982
cell_9772 3 E8.5 Erythroid3 cell_133892
cell_9773 3 E8.25 Erythroid1 cell_76296
... ... ... ... ...
cell_12175 3 E8.5 Erythroid3 cell_138060
cell_12176 3 E8.5 Forebrain/Midbrain/H.. cell_72709
cell_12177 3 E8.25 Surface ectoderm cell_100275
cell_12178 3 E8.25 Erythroid2 cell_70906
cell_12179 3 E8.5 Spinal cord cell_102334
doub.density sizeFactor
<numeric> <numeric>
cell_9769 0.02985045 1.41243
cell_9770 0.00172753 1.22757
cell_9771 0.01338013 1.15439
cell_9772 0.00218402 1.28676
cell_9773 0.00211723 1.78719
... ... ...
cell_12175 0.00129403 1.219506
cell_12176 0.01833074 1.095753
cell_12177 0.03104037 0.910728
cell_12178 0.00169483 2.061701
cell_12179 0.03767894 1.798687
rowData(sce)DataFrame with 29453 rows and 2 columns
ENSEMBL SYMBOL
<character> <character>
ENSMUSG00000051951 ENSMUSG00000051951 Xkr4
ENSMUSG00000089699 ENSMUSG00000089699 Gm1992
ENSMUSG00000102343 ENSMUSG00000102343 Gm37381
ENSMUSG00000025900 ENSMUSG00000025900 Rp1
ENSMUSG00000025902 ENSMUSG00000025902 Sox17
... ... ...
ENSMUSG00000095041 ENSMUSG00000095041 AC149090.1
ENSMUSG00000063897 ENSMUSG00000063897 DHRSX
ENSMUSG00000096730 ENSMUSG00000096730 Vmn2r122
ENSMUSG00000095742 ENSMUSG00000095742 CAAA01147332.1
tomato-td tomato-td tomato-td
reducedDims(sce)List of length 2
names(2): pca.corrected.E7.5 pca.corrected.E8.5
The count matrix is sparse, which is expected for scRNA-seq data. Feature names are Ensembl gene identifiers, while rowData(sce)$SYMBOL stores gene symbols. The cell metadata already contain experimental information and previously mapped annotations. We will leave those existing annotations and PCAs untouched and create our own downstream results.
3. Quality control
Quality control aims to identify cells whose expression profiles are dominated by technical problems rather than meaningful biology. We focus on three standard per-cell summaries:
- library size: total number of counts per cell;
- detected features: number of genes with non-zero counts;
- mitochondrial percentage: fraction of counts assigned to mitochondrial genes.
Identifying mitochondrial genes
The matrix uses Ensembl gene IDs, so we map them to chromosome names using EnsDb.Mmusculus.v79. Genes annotated on chromosome MT are flagged as mitochondrial.
library(EnsDb.Mmusculus.v79)
library(AnnotationDbi)
chr.loc <- mapIds(
EnsDb.Mmusculus.v79,
keys = rownames(sce),
keytype = "GENEID",
column = "SEQNAME",
multiVals = "first"
)
is.mito <- chr.loc == "MT"
is.mito[is.na(is.mito)] <- FALSE
table(is.mito)is.mito
FALSE TRUE
29440 13
In this dataset, 13 features are identified as mitochondrial genes.
Computing QC metrics
qc <- perCellQCMetrics(
sce,
subsets = list(Mito = is.mito)
)
colData(sce) <- cbind(colData(sce), qc)We first inspect each metric separately before deciding which cells to remove.
(
plotColData(sce, y = "sum") |
plotColData(sce, y = "detected") |
plotColData(sce, y = "subsets_Mito_percent")
) +
plot_layout(guides = "collect")Library size and the number of detected genes are related measures of sequencing depth and complexity. Cells at the extreme low end of either distribution are potentially low quality. A high mitochondrial percentage can indicate damaged cells in which cytoplasmic RNA has been preferentially lost. The aim is not to maximize filtering, but to identify clear outliers.
Defining low-quality cells
We use data-adaptive thresholds rather than arbitrary fixed cut-offs.
qc.filter <- perCellQCFilters(
qc,
sub.fields = "subsets_Mito_percent"
)
table(qc.filter$discard)
FALSE TRUE
2324 87
sce$discard <- qc.filter$discardThe automatic filtering marks 87 of 2,411 cells for removal, leaving 2,324 cells for downstream analysis.
We can now highlight the discarded cells on the individual QC distributions.
(
plotColData(sce, y = "sum", colour_by = "discard") |
plotColData(sce, y = "detected", colour_by = "discard") |
plotColData(sce, y = "subsets_Mito_percent", colour_by = "discard")
) +
plot_layout(guides = "collect")The highlighted cells let us check whether the automatic rule is targeting extreme observations rather than removing a large central portion of the data. This visual check is important: QC thresholds should always be treated as a diagnostic decision, not as an unquestioned preprocessing recipe.
A bivariate view is especially useful because it shows whether cells with high mitochondrial content also occupy unusual regions of the library-size distribution.
plotColData(
sce,
x = "sum",
y = "subsets_Mito_percent",
colour_by = "discard"
)The joint plot helps detect problematic cells that may not look extreme when each QC metric is considered in isolation. Once we are satisfied that the flagged cells represent QC outliers, we remove them.
sce <- sce[, !sce$discard]4. Normalization
Cells can differ substantially in sequencing depth and capture efficiency. Normalization places expression values on a comparable scale before we measure transcriptional similarity.
Here we use logNormCounts(), which computes normalized log-expression values and stores them directly in a new logcounts assay.
sce <- logNormCounts(sce)
assayNames(sce)[1] "counts" "logcounts"
The SingleCellExperiment now contains both the original counts and normalized expression values.
logcounts(sce)[10:15, 10:15]6 x 6 sparse Matrix of class "dgCMatrix"
cell_9778 cell_9779 cell_9780 cell_9781 cell_9782 cell_9783
ENSMUSG00000033813 2.22095 2.443967 2.18185 2.380348 2.984191 2.659692
ENSMUSG00000002459 . . . . . .
ENSMUSG00000085623 . . . . . .
ENSMUSG00000033793 . . . . . 1.220173
ENSMUSG00000025905 . . . . . .
ENSMUSG00000033774 . . . . . .
Normalization does not replace the raw counts. Instead, logcounts is added as a second assay, so downstream methods can use normalized expression while the original data remain available in the same object.
5. Feature selection
Not every detected gene is informative for distinguishing cell states. Many genes show little biological variation or mostly contribute technical noise.
We model the mean–variance relationship and quantify the biological component of variability for each gene.
dec <- modelGeneVar(sce)
decDataFrame with 29453 rows and 6 columns
mean total tech bio p.value
<numeric> <numeric> <numeric> <numeric> <numeric>
ENSMUSG00000051951 0.002954686 0.003756409 0.003000574 7.55835e-04 1.01938e-01
ENSMUSG00000089699 0.000000000 0.000000000 0.000000000 0.00000e+00 NaN
ENSMUSG00000102343 0.000000000 0.000000000 0.000000000 0.00000e+00 NaN
ENSMUSG00000025900 0.000839533 0.000927307 0.000852572 7.47351e-05 3.29188e-01
ENSMUSG00000025902 0.170769849 0.386818391 0.170162868 2.16656e-01 6.71410e-11
... ... ... ... ... ...
ENSMUSG00000095041 0.36452490 0.3502868 0.34216706 0.008119788 0.452361
ENSMUSG00000063897 0.50329012 0.4227755 0.44746303 -0.024687477 0.609606
ENSMUSG00000096730 0.00000000 0.0000000 0.00000000 0.000000000 NaN
ENSMUSG00000095742 0.00186983 0.0022693 0.00189887 0.000370437 0.162554
tomato-td 0.58882456 0.4762199 0.50365749 -0.027437598 0.608259
FDR
<numeric>
ENSMUSG00000051951 6.42460e-01
ENSMUSG00000089699 NaN
ENSMUSG00000102343 NaN
ENSMUSG00000025900 7.52549e-01
ENSMUSG00000025902 3.52807e-09
... ...
ENSMUSG00000095041 0.752549
ENSMUSG00000063897 0.752549
ENSMUSG00000096730 NaN
ENSMUSG00000095742 0.752549
tomato-td 0.752549
We retain the 1,000 most highly variable genes for dimensionality reduction.
hvg <- getTopHVGs(dec, n = 1000)
length(hvg)[1] 1000
head(hvg)[1] "ENSMUSG00000055609" "ENSMUSG00000052217" "ENSMUSG00000069919"
[4] "ENSMUSG00000048583" "ENSMUSG00000052187" "ENSMUSG00000051855"
PCA and clustering work best when the input emphasizes genes that vary meaningfully between cells. Restricting the analysis to highly variable genes also reduces noise and computational cost without discarding the original expression matrix.
6. Principal component analysis
PCA compresses the expression profiles of the selected HVGs into a smaller set of orthogonal components. This denoised representation is used as the basis for neighbour finding and clustering.
sce <- runPCA(
sce,
subset_row = hvg
)
reducedDims(sce)List of length 3
names(3): pca.corrected.E7.5 pca.corrected.E8.5 PCA
plotReducedDim(
sce,
dimred = "PCA"
)The PCA projection already reveals broad transcriptional structure, but the first two components represent only part of the total variation. For downstream analysis we retain the multidimensional PCA representation rather than relying only on what is visible in this two-dimensional plot.
7. UMAP
UMAP provides a two-dimensional visualization that emphasizes local neighbourhood structure. We compute it from the PCA coordinates rather than directly from the full expression matrix.
sce <- runUMAP(
sce,
dimred = "PCA"
)
plotReducedDim(
sce,
dimred = "UMAP"
)The UMAP separates the dataset into several compact regions and trajectories, suggesting substantial cellular heterogeneity. At this point the visualization is deliberately unlabelled: the next step asks whether graph-based clustering recovers coherent groups within this structure.
8. Clustering
We identify groups of transcriptionally similar cells using graph-based clustering. A nearest-neighbour graph is constructed in PCA space and communities are detected with the Louvain algorithm.
colLabels(sce) <- clusterCells(
sce,
use.dimred = "PCA",
BLUSPARAM = NNGraphParam(
cluster.fun = "louvain"
)
)
table(colLabels(sce))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
88 133 75 385 164 257 120 105 136 138 174 329 59 142 19
The procedure identifies 15 clusters, with sizes ranging from small rare groups to several hundred cells.
plotReducedDim(
sce,
dimred = "UMAP",
colour_by = "label"
)The cluster colouring provides a discrete summary of the continuous UMAP structure. Well-separated UMAP regions should generally correspond to individual or closely related clusters, while neighbouring clusters may represent finer substructure within related developmental states. Clusters are computational groups at this stage; they are not yet biological cell types.
9. Marker genes
Marker detection connects the unsupervised clusters to gene-level biology. We search for genes that are up-regulated in each cluster relative to the others.
markers <- findMarkers(
sce,
groups = colLabels(sce),
direction = "up"
)
markersList of length 15
names(15): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
markers is a list with one table per cluster. Each table summarizes pairwise comparisons against the remaining clusters and ranks genes according to their ability to distinguish that cluster.
head(markers[[1]])DataFrame with 6 rows and 18 columns
Top p.value FDR summary.logFC logFC.2
<integer> <numeric> <numeric> <numeric> <numeric>
ENSMUSG00000049866 1 6.10531e-79 5.28881e-76 3.19375 2.7712217
ENSMUSG00000028023 1 4.91833e-43 9.22673e-41 2.68504 2.6655890
ENSMUSG00000051855 1 1.53028e-140 2.25357e-136 5.65312 0.0596104
ENSMUSG00000002265 1 4.20586e-138 4.05009e-134 4.33073 2.0866778
ENSMUSG00000037335 1 2.11091e-102 3.27225e-99 4.33614 4.3361382
ENSMUSG00000018217 1 4.55417e-113 1.03180e-109 5.09744 3.6598802
logFC.3 logFC.4 logFC.5 logFC.6 logFC.7 logFC.8
<numeric> <numeric> <numeric> <numeric> <numeric> <numeric>
ENSMUSG00000049866 3.244595 3.50600 2.56889 2.31187 2.58754 2.382022
ENSMUSG00000028023 1.900518 2.72152 1.97717 2.70612 2.71271 0.654973
ENSMUSG00000051855 0.354282 5.65312 1.90749 1.08866 3.30373 0.121485
ENSMUSG00000002265 0.773699 4.89576 1.14814 4.33073 4.41609 0.989117
ENSMUSG00000037335 1.620026 4.66726 4.09930 4.71761 4.69330 0.207842
ENSMUSG00000018217 1.043548 5.42612 4.18262 5.20494 5.25020 0.358036
logFC.9 logFC.10 logFC.11 logFC.12 logFC.13 logFC.14
<numeric> <numeric> <numeric> <numeric> <numeric> <numeric>
ENSMUSG00000049866 3.19375 2.66964 2.81275 1.52971 2.979505 1.84787
ENSMUSG00000028023 2.64760 2.72109 2.39715 2.07519 2.685043 2.38217
ENSMUSG00000051855 4.77004 3.52721 1.95034 1.27569 0.705496 2.82082
ENSMUSG00000002265 3.63977 4.29893 2.38821 1.39028 3.446944 2.15516
ENSMUSG00000037335 4.22999 4.66830 4.70533 2.87727 4.644707 4.63394
ENSMUSG00000018217 5.17777 4.66260 5.28814 3.99988 4.419181 5.09744
logFC.15
<numeric>
ENSMUSG00000049866 -0.2214644
ENSMUSG00000028023 2.6309222
ENSMUSG00000051855 0.5684350
ENSMUSG00000002265 0.4252704
ENSMUSG00000037335 0.1701581
ENSMUSG00000018217 -0.0221686
Top summarizes the best rank achieved across pairwise comparisons, while p.value and FDR quantify statistical evidence. The logFC.* columns show the direction and magnitude of expression differences against individual clusters. In practice, we would combine these statistics with known biological markers rather than assigning cell identities from significance alone.
10. Cell-type annotation
Manual annotation based on marker genes is valuable but can be time-consuming and subjective. As a complementary approach, we compare each query cell with an independently annotated embryonic reference using SingleR.
Preparing the reference
We use EmbryoAtlasData() from the same data package. Cells without a reference label are removed, as are cell types represented by fewer than 10 cells.
library(MouseGastrulationData)
ref <- EmbryoAtlasData(samples = 29)ref <- ref[, !is.na(ref$celltype)]
tab <- table(ref$celltype)
keep.types <- names(tab)[tab >= 10]
ref <- ref[, ref$celltype %in% keep.types]
ref <- logNormCounts(ref)
refclass: SingleCellExperiment
dim: 29452 6453
metadata(0):
assays(2): counts logcounts
rownames(29452): ENSMUSG00000051951 ENSMUSG00000089699 ...
ENSMUSG00000096730 ENSMUSG00000095742
rowData names(2): ENSEMBL SYMBOL
colnames(6453): cell_95728 cell_95730 ... cell_103293 cell_103294
colData names(17): cell barcode ... colour sizeFactor
reducedDimNames(2): pca.corrected umap
mainExpName: NULL
altExpNames(0):
table(ref$celltype)
Allantois Blood progenitors 1
322 12
Blood progenitors 2 Cardiomyocytes
33 241
Caudal Mesoderm Def. endoderm
21 30
Endothelium Erythroid1
169 23
Erythroid2 Erythroid3
71 550
ExE endoderm ExE mesoderm
294 332
Forebrain/Midbrain/Hindbrain Gut
991 214
Haematoendothelial progenitors Intermediate mesoderm
197 198
Mesenchyme Neural crest
356 241
NMP Paraxial mesoderm
342 507
PGC Pharyngeal mesoderm
10 354
Somitic mesoderm Spinal cord
225 346
Surface ectoderm
374
The filtered reference contains 6,453 cells spanning 25 embryonic cell types, providing a biologically appropriate label space for the query cells.
Running SingleR
SingleR compares each query profile with the labelled reference. We aggregate reference cells within labels to keep the workshop computation fast.
library(SingleR)
pred <- SingleR(
test = sce,
ref = ref,
labels = ref$celltype,
de.method = "wilcox",
aggr.ref = TRUE
)
table(pred$labels)
Allantois Blood progenitors 1
87 5
Blood progenitors 2 Cardiomyocytes
19 79
Caudal Mesoderm Def. endoderm
44 7
Endothelium Erythroid1
59 45
Erythroid2 Erythroid3
118 201
ExE mesoderm Forebrain/Midbrain/Hindbrain
153 261
Gut Haematoendothelial progenitors
46 69
Intermediate mesoderm Mesenchyme
73 270
Neural crest NMP
47 107
Paraxial mesoderm PGC
164 3
Pharyngeal mesoderm Somitic mesoderm
143 103
Spinal cord Surface ectoderm
139 82
The predictions cover a broad range of developmental lineages rather than collapsing most cells into one or two dominant labels, which is a useful first sanity check for this heterogeneous dataset.
sce$cell_type <- pred$labelsVisualizing predicted cell types
With many nominal categories, a continuous colour scale is inappropriate. We therefore generate a qualitative palette designed to keep neighbouring labels visually distinct.
library(Polychrome)
cell_types <- sort(unique(sce$cell_type))
cols <- createPalette(
length(cell_types),
seedcolors = c("#E41A1C", "#377EB8", "#4DAF4A")
)
names(cols) <- cell_types
plotReducedDim(
sce,
dimred = "UMAP",
colour_by = "cell_type"
) +
scale_colour_manual(values = cols) +
guides(
colour = guide_legend(
title = "Cell type",
override.aes = list(size = 3)
)
) +
theme(
legend.position = "right",
legend.key.height = grid::unit(0.45, "cm")
)The predicted labels occupy distinct regions of the UMAP and recover a diverse set of embryonic lineages, including mesodermal, erythroid, endothelial, neural and endodermal populations. Some related labels remain close to one another, which is expected for developmental states connected by gradual transcriptional transitions. Automated labels should therefore be treated as evidence to combine with marker genes and biological knowledge, not as ground truth.
11. Putting everything together
At the end of the workflow, the same SingleCellExperiment contains the raw and normalized expression matrices, QC information, low-dimensional embeddings, cluster assignments and predicted cell types.
sceclass: SingleCellExperiment
dim: 29453 2324
metadata(0):
assays(2): counts logcounts
rownames(29453): ENSMUSG00000051951 ENSMUSG00000089699 ...
ENSMUSG00000095742 tomato-td
rowData names(2): ENSEMBL SYMBOL
colnames(2324): cell_9769 cell_9770 ... cell_12178 cell_12179
colData names(20): cell barcode ... label cell_type
reducedDimNames(4): pca.corrected.E7.5 pca.corrected.E8.5 PCA UMAP
mainExpName: NULL
altExpNames(0):
assayNames(sce)[1] "counts" "logcounts"
reducedDims(sce)List of length 4
names(4): pca.corrected.E7.5 pca.corrected.E8.5 PCA UMAP
colData(sce)DataFrame with 2324 rows and 20 columns
cell barcode sample stage tomato
<character> <character> <integer> <character> <logical>
cell_9769 cell_9769 AAACCTGAGACTGTAA 5 E8.5 TRUE
cell_9770 cell_9770 AAACCTGAGATGCCTT 5 E8.5 TRUE
cell_9771 cell_9771 AAACCTGAGCAGCCTC 5 E8.5 TRUE
cell_9772 cell_9772 AAACCTGCATACTCTT 5 E8.5 TRUE
cell_9773 cell_9773 AAACGGGTCAACACCA 5 E8.5 TRUE
... ... ... ... ... ...
cell_12175 cell_12175 TTTGGTTAGTCCGTAT 5 E8.5 TRUE
cell_12176 cell_12176 TTTGGTTAGTGTTGAA 5 E8.5 TRUE
cell_12177 cell_12177 TTTGGTTGTTAAAGAC 5 E8.5 TRUE
cell_12178 cell_12178 TTTGGTTTCAGTCAGT 5 E8.5 TRUE
cell_12179 cell_12179 TTTGGTTTCGCCATAA 5 E8.5 TRUE
pool stage.mapped celltype.mapped closest.cell
<integer> <character> <character> <character>
cell_9769 3 E8.25 Mesenchyme cell_24159
cell_9770 3 E8.5 Endothelium cell_96660
cell_9771 3 E8.5 Allantois cell_134982
cell_9772 3 E8.5 Erythroid3 cell_133892
cell_9773 3 E8.25 Erythroid1 cell_76296
... ... ... ... ...
cell_12175 3 E8.5 Erythroid3 cell_138060
cell_12176 3 E8.5 Forebrain/Midbrain/H.. cell_72709
cell_12177 3 E8.25 Surface ectoderm cell_100275
cell_12178 3 E8.25 Erythroid2 cell_70906
cell_12179 3 E8.5 Spinal cord cell_102334
doub.density sizeFactor sum detected subsets_Mito_sum
<numeric> <numeric> <numeric> <integer> <numeric>
cell_9769 0.02985045 0.932680 27577 5418 471
cell_9770 0.00172753 0.810610 29309 5405 679
cell_9771 0.01338013 0.762288 28795 5218 480
cell_9772 0.00218402 0.849694 34794 4781 496
cell_9773 0.00211723 1.180146 38300 5211 488
... ... ... ... ... ...
cell_12175 0.00129403 0.805283 26680 4308 507
cell_12176 0.01833074 0.723565 19013 4684 174
cell_12177 0.03104037 0.601386 24627 5367 513
cell_12178 0.00169483 1.361415 46162 5312 833
cell_12179 0.03767894 1.187738 38398 6020 252
subsets_Mito_detected subsets_Mito_percent total discard
<integer> <numeric> <numeric> <logical>
cell_9769 10 1.70795 27577 FALSE
cell_9770 10 2.31669 29309 FALSE
cell_9771 12 1.66696 28795 FALSE
cell_9772 12 1.42553 34794 FALSE
cell_9773 12 1.27415 38300 FALSE
... ... ... ... ...
cell_12175 11 1.900300 26680 FALSE
cell_12176 13 0.915163 19013 FALSE
cell_12177 10 2.083080 24627 FALSE
cell_12178 11 1.804515 46162 FALSE
cell_12179 12 0.656284 38398 FALSE
label cell_type
<factor> <character>
cell_9769 1 Mesenchyme
cell_9770 2 Endothelium
cell_9771 3 Allantois
cell_9772 4 Erythroid3
cell_9773 4 Erythroid1
... ... ...
cell_12175 4 Erythroid3
cell_12176 6 Spinal cord
cell_12177 15 Surface ectoderm
cell_12178 4 Erythroid1
cell_12179 7 Spinal cord
The object has travelled through the entire analysis:
counts → QC → log-normalization → HVGs → PCA → UMAP → clustering → markers → cell-type annotation
Each step adds information while preserving the existing data and metadata in a common, interoperable container.
Take-home messages
SingleCellExperimentprovides a common data structure across the workflow.- QC decisions should be inspected visually rather than applied blindly.
- Normalization and feature selection prepare the data for similarity-based analyses.
- PCA provides a denoised working space; UMAP provides an intuitive visualization.
- Clusters summarize transcriptional structure but do not automatically define cell types.
- Marker genes and reference-based annotation provide complementary biological evidence.
- Bioconductor packages interoperate by adding results back to the same object.
Session information
For reproducibility, the exact R and package versions used to render the workshop are reported below.
sessionInfo()R version 4.5.1 (2025-06-13 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 10 x64 (build 19045)
Matrix products: default
LAPACK version 3.12.1
locale:
[1] LC_COLLATE=Korean_Korea.utf8 LC_CTYPE=Korean_Korea.utf8
[3] LC_MONETARY=Korean_Korea.utf8 LC_NUMERIC=C
[5] LC_TIME=Korean_Korea.utf8
time zone: Asia/Seoul
tzcode source: internal
attached base packages:
[1] stats4 stats graphics grDevices utils datasets methods
[8] base
other attached packages:
[1] Polychrome_1.6.1 SingleR_2.12.0
[3] EnsDb.Mmusculus.v79_2.99.0 ensembldb_2.34.0
[5] AnnotationFilter_1.34.0 GenomicFeatures_1.62.0
[7] AnnotationDbi_1.72.0 patchwork_1.3.2
[9] bluster_1.20.0 scran_1.38.1
[11] scater_1.38.1 ggplot2_4.0.3
[13] scuttle_1.20.0 BiocStyle_2.38.0
[15] MouseGastrulationData_1.24.0 SpatialExperiment_1.20.0
[17] SingleCellExperiment_1.32.0 SummarizedExperiment_1.40.0
[19] Biobase_2.70.0 GenomicRanges_1.62.1
[21] Seqinfo_1.0.0 IRanges_2.44.0
[23] S4Vectors_0.48.1 BiocGenerics_0.56.0
[25] generics_0.1.4 MatrixGenerics_1.22.0
[27] matrixStats_1.5.0
loaded via a namespace (and not attached):
[1] RColorBrewer_1.1-3 rstudioapi_0.19.0
[3] jsonlite_2.0.0 magrittr_2.0.5
[5] ggbeeswarm_0.7.3 magick_2.9.1
[7] farver_2.1.2 rmarkdown_2.31
[9] BiocIO_1.20.0 vctrs_0.7.3
[11] DelayedMatrixStats_1.32.0 memoise_2.0.1
[13] Rsamtools_2.26.0 RCurl_1.98-1.20
[15] htmltools_0.5.9 S4Arrays_1.10.1
[17] AnnotationHub_4.0.0 curl_8.0.0
[19] BiocNeighbors_2.4.0 SparseArray_1.10.10
[21] htmlwidgets_1.6.4 httr2_1.3.0
[23] cachem_1.1.0 GenomicAlignments_1.46.0
[25] igraph_2.3.3 lifecycle_1.0.5
[27] pkgconfig_2.0.3 rsvd_1.0.5
[29] Matrix_1.7-6 R6_2.6.1
[31] fastmap_1.2.0 digest_0.6.39
[33] colorspace_2.1-3 RSpectra_0.16-2
[35] dqrng_0.4.1 irlba_2.3.7
[37] ExperimentHub_3.0.0 RSQLite_3.53.3
[39] beachmat_2.26.0 labeling_0.4.3
[41] filelock_1.0.3 httr_1.4.8
[43] abind_1.4-8 compiler_4.5.1
[45] bit64_4.8.6 withr_3.0.3
[47] S7_0.2.2 BiocParallel_1.44.0
[49] viridis_0.6.5 DBI_1.3.0
[51] rappdirs_0.3.4 DelayedArray_0.36.1
[53] scatterplot3d_0.3-45 rjson_0.2.23
[55] tools_4.5.1 vipor_0.4.7
[57] otel_0.2.0 beeswarm_0.4.0
[59] glue_1.8.1 restfulr_0.0.17
[61] grid_4.5.1 cluster_2.1.8.1
[63] gtable_0.3.6 BiocSingular_1.26.1
[65] ScaledMatrix_1.18.0 metapod_1.18.0
[67] XVector_0.50.0 ggrepel_0.9.8
[69] BiocVersion_3.22.0 pillar_1.11.1
[71] limma_3.66.0 BumpyMatrix_1.18.0
[73] dplyr_1.2.1 BiocFileCache_3.0.0
[75] lattice_0.22-7 FNN_1.1.4.1
[77] rtracklayer_1.70.1 bit_4.6.0
[79] tidyselect_1.2.1 locfit_1.5-9.12
[81] Biostrings_2.78.0 knitr_1.51
[83] gridExtra_2.3.1 scrapper_1.4.0
[85] ProtGenerics_1.42.0 edgeR_4.8.2
[87] xfun_0.56 statmod_1.5.2
[89] UCSC.utils_1.6.1 lazyeval_0.2.3
[91] yaml_2.3.12 evaluate_1.0.5
[93] codetools_0.2-20 cigarillo_1.0.0
[95] tibble_3.3.1 BiocManager_1.30.27
[97] cli_3.6.6 uwot_0.2.5
[99] dichromat_2.0-1 Rcpp_1.1.2
[101] GenomeInfoDb_1.46.2 dbplyr_2.6.0
[103] png_0.1-9 XML_3.99-0.24
[105] parallel_4.5.1 blob_1.3.0
[107] sparseMatrixStats_1.22.0 bitops_1.1-0
[109] viridisLite_0.4.3 scales_1.4.0
[111] purrr_1.2.2 crayon_1.5.3
[113] rlang_1.3.0 cowplot_1.2.0
[115] KEGGREST_1.50.0