Chapter 8 Batch correction
8.1 Motivation
In large scRNA-seq projects, data generation may be split across multiple batches due to logistical constraints. However, the processing of different batches is often subject to uncontrollable differences, e.g., small changes in incubation times, differences in reagent concentration/quality. This may introduce systematic differences in the observed expression in cells from different batches, a.k.a., “batch effects”. Large batch effects are problematic as they mask biological variation and complicate interpretation of the results. For example, is the separation between clusters driven by interesting biology or batch effects?
Batch correction aims to remove batch effects from scRNA-seq data prior to downstream steps like clustering.
Specifically, we want to merge cells from different batches that represent the same biological subpopulation.
This simplifies interpretation as cells from the same subpopulation will be assigned to the same cluster, placed together in \(t\)-SNE plots, etc.
Otherwise, if the batch effects are strong, cells would just separate by their batch of origin, which would distract from the actual biology of interest.
Note that this step is quite different from the blocking discussed in most of the previous chapters,
where setting block= just instructs those functions to ignore the batch effect instead of actively removing it.
Historically, we used linear regression for batch correction of RNA-seq data (Ritchie et al. 2015; Leek et al. 2012). (We can achieve the same effect with our PCA if we center the scores by block, see Section 4.4.) However, this assumes that the composition of cell subpopulations is either known, and can be used as a covariate in the model; or the composition is the same across batches, with a consistent batch effect in each subpopulation. Such assumptions are usually inappropriate for single-cell studies. Instead, we use bespoke methods for single-cell data (Haghverdi et al. 2018; Butler et al. 2018; Lin et al. 2019) that do not require these strong assumptions28.
8.2 Correction with mutual nearest neighbors
Mutual nearest neighbors (MNN) correction was one of the first batch correction methods developed for single-cell data (Haghverdi et al. 2018). For each cell in batch \(B_1\), we search for the \(k\) nearest neighbors in batch \(B_2\) (for some small \(k\), e.g., 10 - 20). Similarly, for each cell in batch \(B_2\), we search for the \(k\) nearest neighbors in batch \(B_1\). We form an MNN pair between cells \(x_1\) in batch \(B_1\) and \(x_2\) in batch \(B_2\) if \(x_1\) is \(x_2\)’s nearest neighbor and vice versa. The assumption is that MNN pairs will (mostly) only form between \(x_1\) and \(x_2\) from the same biological subpopulation. A subpopulation unique to batch \(B_2\) will (hopefully) not be able to form MNN pairs to \(B_1\), as each cell in \(B_1\) will be preoccupied with forming MNN pairs with cells from its matching subpopulation in \(B_2\). The difference between the cells in each MNN pair defines the direction and magnitude of the batch effect for its surrounding neighborhood, allowing us to correct, e.g., \(B_2\) to \(B_1\) by subtracting that difference from each cell in \(B_2\). To demonstrate, let’s use several PBMC datasets from 10X Genomics (Zheng et al. 2017):
library(TENxPBMCData)
sce.pbmc3k <- TENxPBMCData('pbmc3k')
sce.pbmc4k <- TENxPBMCData('pbmc4k')
sce.pbmc8k <- TENxPBMCData('pbmc8k')
# Finding a common set of genes across all batches to allow us to combine
# everything into a single object. This is only necessary if the different
# batches were processed with different genome annotations.
inter.pbmc <- Reduce(
intersect,
list(
rownames(sce.pbmc3k),
rownames(sce.pbmc4k),
rownames(sce.pbmc8k)
)
)
sce.pbmc <- combineCols(
sce.pbmc3k[inter.pbmc,],
sce.pbmc4k[inter.pbmc,],
sce.pbmc8k[inter.pbmc,]
)
sce.pbmc$batch <- rep(
c("3k", "4k", "8k"),
c(ncol(sce.pbmc3k), ncol(sce.pbmc4k), ncol(sce.pbmc8k))
)
# For each dataset, TENxPBMCData loads the count data into the R session as a
# file-backed matrix, i.e., the "matrix" object is just a pointer to file
# containing the actual counts. For greater efficiency, we load the data into
# memory as a sparse matrix so that we don't have to repeatedly read from disk.
counts(sce.pbmc) <- as(counts(sce.pbmc), "dgCMatrix")
# Quality control, blocking on the batch of origin for each cell.
is.mito.pbmc <- grep("MT", rowData(sce.pbmc)$Symbol)
library(scrapper)
sce.qc.pbmc <- quickRnaQc.se(
sce.pbmc,
subsets=list(MT=is.mito.pbmc),
block=sce.pbmc$batch
)
sce.qc.pbmc <- sce.pbmc[,sce.qc.pbmc$keep]
# Normalization, blocking on the batch of origin for each cell.
sce.norm.pbmc <- normalizeRnaCounts.se(
sce.qc.pbmc,
size.factors=sce.qc.pbmc$sum,
block=sce.qc.pbmc$batch
)
# We now choose the top HVGs, with blocking.
sce.var.pbmc <- chooseRnaHvgs.se(
sce.norm.pbmc,
block=sce.norm.pbmc$batch
)
# Running the PCA on the HVG submatrix, with blocking.
sce.pca.pbmc <- runPca.se(
sce.var.pbmc,
features=rowData(sce.var.pbmc)$hvg,
number=25,
block=sce.var.pbmc$batch
)If we examine the distribution of cells without any batch correction, we observe some batch-specific substructure (Figure 8.1). Such batch effects could have any number of causes - biological differences in the underlying cell population between donors, differences in the technology used for cell capture and/or sequencing, or changes in the computational piplines for alignment and quantification. Regardless of their origins, we consider these differences to be uninteresting as all batches are assaying the same PBMC population and should be replicates of each other.
sce.unc.tsne.pbmc <- runTsne.se(sce.pca.pbmc)
library(scater)
plotReducedDim(sce.unc.tsne.pbmc, "TSNE", colour_by="batch") + ggtitle("uncorrected")
Figure 8.1: \(t\)-SNE plot of the cells from the PBMC dataset, without any batch correction. Each cell is colored according to its batch of origin.
To remove the batch effects, we use correctMnn.se() to apply MNN correction to the PC scores for all cells.
(As mentioned in the other chapters, we take advantage of the compaction and denoising effects of the PCA on the HVGs.)
This yields a set of corrected scores that can be used in place of the original PCs in downstream analyses.
We observe greater intermingling between batches in Figure 8.2, indicating that we have successfully mitigated the batch effect.
sce.mnn.pbmc <- correctMnn.se(sce.pca.pbmc, sce.pca.pbmc$batch)
sce.mnn.tsne.pbmc <- runTsne.se(sce.mnn.pbmc, reddim.type="MNN")
plotReducedDim(sce.mnn.tsne.pbmc, "TSNE", colour_by="batch") + ggtitle("After correction")
Figure 8.2: \(t\)-SNE plot of the cells from the PBMC dataset after MNN correction. Each cell is colored according to its batch of origin.
We quantify the impact of MNN correction by clustering on the corrected PCs and examining the distribution of cells across batches within each cluster. If a cluster has contributions from multiple batches, it probably represents a cell type/state that is shared across those batches. We expect our PBMC clusters to be more-or-less evenly distributed across batches as each batch is a replicate of the others.
sce.mnn.graph.pbmc <- clusterGraph.se(sce.mnn.pbmc, reddim.type="MNN")
# This is a normalized matrix of cell counts for each group (row) and block
# (column). We divide each column by the number of cells in each batch to
# account for differences between batches. Then we divide by the row sums to
# get the distribution of cells across batches in each cluster.
cluster.batch.mnn.pbmc <- countGroupsByBlock(
sce.mnn.graph.pbmc$clusters,
sce.mnn.graph.pbmc$batch,
normalize.groups=TRUE,
normalize.block=TRUE
)
print(cluster.batch.mnn.pbmc, digits=2, zero.print=".")## block
## groups 3k 4k 8k
## 1 0.31 0.35 0.34
## 2 0.30 0.37 0.33
## 3 0.53 0.22 0.25
## 4 0.35 0.32 0.32
## 5 0.43 0.31 0.26
## 6 0.29 0.37 0.34
## 7 0.33 0.34 0.33
## 8 0.57 0.23 0.20
## 9 0.17 0.34 0.49
## 10 0.18 0.23 0.59
## 11 0.31 0.34 0.35
## 12 0.19 0.41 0.40
## 13 0.20 0.38 0.42
## 14 0.24 0.40 0.35
## 15 0.20 0.42 0.38
## 16 0.40 0.32 0.29
If a cluster has no contribution from a batch, this either represents a unique subpopulation or it indicates that batch correction was not completely successful. Indeed, clustering on the uncorrected PCs yields some batch-specific clusters in the PBMC data. These are unlikely to represent unique types/states given that the batches should be replicates.
sce.unc.graph.pbmc <- clusterGraph.se(sce.pca.pbmc)
cluster.batch.unc.pbmc <- countGroupsByBlock(
sce.unc.graph.pbmc$clusters,
sce.unc.graph.pbmc$batch,
normalize.groups=TRUE,
normalize.block=TRUE
)
print(cluster.batch.unc.pbmc, digits=2, zero.print=".")## block
## groups 3k 4k 8k
## 1 0.3861 0.2925 0.3214
## 2 0.3078 0.3475 0.3446
## 3 1.0000 . .
## 4 0.4319 0.3114 0.2567
## 5 0.2169 0.4198 0.3633
## 6 0.3140 0.3462 0.3398
## 7 0.9988 0.0012 .
## 8 0.1629 0.2419 0.5953
## 9 0.1144 0.4045 0.4811
## 10 0.1928 0.4142 0.3930
## 11 0.1714 0.4345 0.3940
## 12 0.1764 0.4287 0.3949
## 13 0.0340 0.5094 0.4566
## 14 0.0113 0.4034 0.5853
## 15 . 0.5026 0.4974
To be more explicit, the corrected PCs are useful as they put cells from all batches onto a common coordinate system. This greatly simplifies downstream steps like clustering and \(t\)-SNE, which can compute distances between cells as if no batch effect was present. We do not have to worry about, e.g., the formation of multiple clusters that represent the same cell type/state but are only separated due to batch effects. Such redundant clusters are annoying to interpret as we have to (i) inspect more clusters to discover the same biology and (ii) match them up to each other for further analyses like those in Chapter 9). In addition, by merging batches together, we increase the number of cells by pooling together shared subpopulations across batches. This increases the number of cells available provides some opportunities for improved resolution of rare subpopulations29.
8.3 Assumptions of MNN correction
Compared to linear regression, MNN correction does not assume that the population composition is the same or known beforehand. It effectively learns the shared population structure via identification of MNN pairs and uses this information to estimate a local batch effect for subpopulation-specific correction. However, MNN correction is not without its own assumptions:
- It requires some shared subpopulations between batches to encourage formation of the correct MNN pairs. For example, if one batch contains B cells only and another batch contains T cells only, MNN pairs would form between the two cell types and the correction would merge them together. More generally, MNN correction becomes more robust with more shared subpopulations between batches. This implicitly reduces the risk of forming incorrect MNN pairs between unique subpopulations in each batch. If our first batch also contained T cells, they would match across batches and the B cells would (correctly) not participate in any MNN pairs.
- Any shared subpopulations should have more than \(k\) cells in each batch to ensure that MNN pairs do not incorrectly form across different subpopulations. For example, let’s say our first batch that contains only B cells and our second batch contains T cells and fewer than 10 B cells. If we used \(k = 10\), some MNN pairs would form between the B cells in the first batch and T cells in the second batch, which would be wrong. (That said, failure is not guaranteed for small populations - the example above would have worked out fine if T cells also existed in the first batch. It’s just that the risk of incorrect MNN pairs is much higher when the subpopulation size drops below \(k\).)
- For more subtle population structure, MNN correction assumes that the batch effect is orthogonal to the axes of biological variation. Say we’re studying some kind of continuous biological variation like differentiation, and we have two batches that are replicates of each other. We then accidentally introduced a batch effect that is not orthogonal to the biological variation, e.g., we incubated the second batch for a bit too long and now it has higher baseline expression of the differentiation marker. Here, MNN correction would be slightly incorrect as it preserves the non-orthogonal component of the batch effect (Figure 8.3).
Figure 8.3: Diagram of MNN correction when the batch effect is confounded with biological variation.
Violations of some of these assumptions might be tolerable, sometimes. For example, we wouldn’t lose too much sleep if monocytes and macrophages were merged together across batches… but then again, maybe we would, if we were really interested in studying differentiation in that particular lineage. In any case, it is best to treat batch-corrected data - and conclusions derived from it - with a grain of salt. The various merging decisions made by the algorithm may or may not be sensible depending on our scientific question. We recommend verifying any important conclusions30 by repeating the analysis without correction, e.g., by analyzing individual batches separately to check that a putative cell type is not an artifact.
8.4 What is a batch effect, anyway?
In this chapter’s introduction, we defined batch effects in terms of technical differences that are obviously uninteresting.
However, certain biological differences are also uninteresting and can be treated as batch effects.
One example is the biological variability between replicate samples (e.g., donors, animals, cultures) from which the cells are extracted.
We are generally uninterested in systematic differences between samples, which might cause cells of the same type to form separate clusters based on their sample of origin.
In these replicated experiments, we might consider removing this sample-to-sample variability by treating each sample as a batch in correctMnn.se().
Similarly, we could apply batch correction to any uninteresting categorical factor in our dataset, e.g., sex, genotype, cell cycle phase.
Admittedly, at this point, we’re misusing the word “batch”31,
but we’re already halfway into this chapter so let’s just bear with it until the end.
Now, what happens if different samples contain cells from different experimental conditions? Consider an experiment with two samples where one contains control cells and the other contains drug-treated cells. If we applied MNN correction to the samples, any treatment-induced differential expression would be treated as a batch effect and removed. Don’t freak out - this behavior is both expected and desirable. By merging cells from both conditions, we only need to characterize population heterogeneity once for the entire dataset. For example, we can use the corrected coordinates to define a common set of clusters across both treated and control samples, and then interpret those clusters to identify their corresponding cell types or states. This, in turn, allows us test for differences in expression or abundance of the same cell type/state between conditions (Chapter 9).
Some users get very upset that a (very interesting!) biological difference between conditions is deliberately removed by batch correction. However, this concern is largely misplaced as the corrected values are only ever used for defining common clusters and annotations. Any differences between conditions will still be preserved in the results of Chapter 9. The alternative strategy would be to cluster each condition separately and to attempt to identify matching clusters across conditions, which is much less convenient - though not an inherently bad idea, see Section 9.4.
8.5 Obtaining corrected gene expression values
It is possible to obtain MNN-corrected gene expression values but these are much more difficult to interpret. The correction is not obliged to preserve relative differences in per-gene expression when aligning multiple batches. In fact, the opposite is true - the correction must distort the expression profiles to merge batches together, as any differences in expression between batches for the same subpopulation would be a batch effect. Let’s demonstrate using two pancreas datasets (Grun et al. 2016; Muraro et al. 2016) that we’ll treat as separate batches.
library(scRNAseq)
sce.grun <- GrunPancreasData()
sce.muraro <- MuraroPancreasData()
# Taking the intersection of features for both endogenous genes...
inter.pancreas <- intersect(rownames(sce.grun), rownames(sce.muraro))
sce.grun <- sce.grun[inter.pancreas,]
sce.muraro <- sce.muraro[inter.pancreas,]
# And spike-ins, for completeness...
inter.ercc.pancreas <- intersect(rownames(altExp(sce.grun, "ERCC")), rownames(altExp(sce.muraro, "ERCC")))
altExp(sce.grun, "ERCC") <- altExp(sce.grun, "ERCC")[inter.ercc.pancreas,]
altExp(sce.muraro, "ERCC") <- altExp(sce.muraro, "ERCC")[inter.ercc.pancreas,]
# Before combining both datasets into a single SCE object.
sce.pancreas <- combineCols(sce.grun, sce.muraro)
sce.pancreas$batch <- rep(c("grun", "muraro"), c(ncol(sce.grun), ncol(sce.muraro)))
# Quality control, blocking on the batch of origin for each cell. We don't
# have mitochondrial genes here so we'll use the spike-ins instead.
library(scrapper)
sce.qc.pancreas <- quickRnaQc.se(
sce.pancreas,
subsets=NULL,
altexp.proportions="ERCC",
block=sce.pancreas$batch
)
sce.qc.pancreas <- sce.qc.pancreas[,sce.qc.pancreas$keep]
# Normalization, blocking on the batch of origin for each cell.
sce.norm.pancreas <- normalizeRnaCounts.se(
sce.qc.pancreas,
size.factors=sce.qc.pancreas$sum,
block=sce.qc.pancreas$batch
)
# We now choose the top HVGs, with blocking.
sce.var.pancreas <- chooseRnaHvgs.se(sce.norm.pancreas, block=sce.norm.pancreas$batch)
# Running the PCA on the HVG submatrix, with blocking.
sce.pca.pancreas <- runPca.se(
sce.var.pancreas,
features=rowData(sce.var.pancreas)$hvg,
block=sce.var.pancreas$batch
)We use correctMnn.se() to obtain MNN-corrected PCs for clustering and visualization.
Both batches contribute to each cluster and are intermingled in Figure 8.4,
which is expected given that both datasets are measuring the same pancreatic cell types.
sce.mnn.pancreas <- correctMnn.se(sce.pca.pancreas, sce.qc.pancreas$batch)
sce.nn.mnn.pancreas <- runAllNeighborSteps.se(sce.mnn.pancreas, reddim.type="MNN")
cluster.batch.mnn.pancreas <- countGroupsByBlock(
sce.nn.mnn.pancreas$clusters,
sce.nn.mnn.pancreas$batch,
normalize.groups=TRUE,
normalize.block=TRUE
)
print(cluster.batch.mnn.pancreas, digits=2, zero.print=".")## block
## groups grun muraro
## 1 0.78 0.22
## 2 0.63 0.37
## 3 0.70 0.30
## 4 0.86 0.14
## 5 0.33 0.67
## 6 0.27 0.73
## 7 0.37 0.63
## 8 0.65 0.35
## 9 0.49 0.51
## 10 0.74 0.26
## 11 0.26 0.74
## 12 0.23 0.77
## 13 0.29 0.71
## 14 0.38 0.62
Figure 8.4: \(t\)-SNE plot of the Grun and Muraro pancreas datasets after MNN correction. Each point is a cell, colored according to its assigned batch.
We recover “corrected expression values” for any given gene by multiplying the corrected PCs with the corresponding row of the rotation matrix. This is effectively a low-rank approximation of our original log-expression matrix, but using the corrected coordinates for each cell. Of particular interest is the INS-IGF2 gene, where MNN correction forces the expression profiles to be consistent between batches (Figure 8.5). (As of time of writing, this involved eliminating the variability in INS-IGF2 across clusters in the Grun dataset to match the lack of expression in the Muraro dataset, though the opposite outcome is equally possible, i.e., introducing non-zero expression in the Muraro dataset to match that of the Grun dataset.) From the perspective of the correction algorithm, this effect is intended as these differences between batches are part of the batch effect and must be removed. However, if we relied the corrected expression values, we would draw misleading conclusions about the behavior of INS-IGF2 across batches. For example, if one batch consisted of drug-treated patients and another batch was a control, we would not detect any treatment-induced differential expression from the corrected expression values.
current.insigf2 <- "INS-IGF2__chr11"
rotation.insigf2 <- metadata(sce.nn.mnn.pancreas)$PCA$rotation[current.insigf2,]
lowrank.unc.insigf2 <- reducedDim(sce.nn.mnn.pancreas, "PCA") %*% rotation.insigf2
lowrank.mnn.insigf2 <- reducedDim(sce.nn.mnn.pancreas, "MNN") %*% rotation.insigf2
gridExtra::grid.arrange(
plotExpression(
sce.nn.mnn.pancreas,
x="clusters",
features=current.insigf2,
colour_by="clusters",
other_fields="batch"
) +
facet_grid(~batch) +
ggtitle("original expression"),
plotXY(
sce.nn.mnn.pancreas$clusters,
lowrank.unc.insigf2,
colour_by=sce.nn.mnn.pancreas$clusters,
other_fields=list(batch=sce.nn.mnn.pancreas$batch)
) +
facet_grid(~batch) +
ggtitle("reconstruction without correction"),
plotXY(
sce.nn.mnn.pancreas$clusters,
lowrank.mnn.insigf2,
colour_by=sce.nn.mnn.pancreas$clusters,
other_fields=list(batch=sce.nn.mnn.pancreas$batch)
) +
facet_grid(~batch) +
ggtitle("reconstruction with correction"),
ncol=1
)
Figure 8.5: Expression of INS-IGF2 across clusters in the combined Grun/Muraro pancreas dataset. Expression is quantified in terms of the log-normalized expression values (top panel), the reconstructed expression values with the uncorrected PCs (middle), and the reconstructed expression values with the MNN-corrected PCs (bottom).
For gene-based analyses, we recommend using the original log-expression values as these are easier to interpret. Differences between batches should be handled by some other mechanism, e.g., blocking during marker detection (Figure 8.6, Section 7.5). Over the past decade, we have - perhaps once or twice - used the corrected values for visualization, just to synchronize expression across all batches to the same color gradient in a \(t\)-SNE plot for nicer aesthetics. This was probably not worth the hassle of checking that the corrected values gave the same conclusions as the original expression values.
markers.pancreas <- scoreMarkers.se(
sce.nn.mnn.pancreas,
sce.nn.mnn.pancreas$clusters,
block=sce.nn.mnn.pancreas$block
)
# Looking at the top markers for cluster 1.
chosen.markers.pancreas <- markers.pancreas[["1"]]
previewMarkers(chosen.markers.pancreas)## DataFrame with 10 rows and 3 columns
## mean detected lfc
## <numeric> <numeric> <numeric>
## PRSS1__chr7 7.61820 1.000000 6.54557
## SPINK1__chr5 7.43795 1.000000 5.93621
## PRSS3P2__chr7 6.57052 1.000000 5.85816
## PLA2G1B__chr12 5.69794 0.992958 5.10010
## CTRB1__chr16 6.24111 0.992958 5.59467
## REG1A__chr2 8.81592 1.000000 6.20073
## CPA1__chr7 6.10904 1.000000 5.36314
## CELA3A__chr1 5.90581 1.000000 5.45492
## CTRB2__chr16 6.74839 0.992958 5.61807
## CTRC__chr1 4.04000 0.971831 3.72824
plotExpression(
sce.nn.mnn.pancreas,
x="clusters",
features=rownames(chosen.markers.pancreas)[1],
colour_by="clusters",
other_fields="batch"
) + facet_grid(~batch)
Figure 8.6: Distribution of log-expression values across clusters for the top marker in cluster 1 of the merged Grun/Muraro pancreas dataset. Each point is a cell and each facet is a batch.
Session information
## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.4 LTS
##
## Matrix products: default
## BLAS: /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0 LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_GB LC_COLLATE=C
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: America/New_York
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] scRNAseq_2.27.0 scater_1.41.2
## [3] ggplot2_4.0.3 scuttle_1.23.1
## [5] scrapper_1.7.3 TENxPBMCData_1.31.0
## [7] HDF5Array_1.41.0 h5mread_1.5.0
## [9] rhdf5_2.57.3 DelayedArray_0.39.3
## [11] SparseArray_1.13.2 S4Arrays_1.13.0
## [13] abind_1.4-8 Matrix_1.7-6
## [15] SingleCellExperiment_1.35.2 SummarizedExperiment_1.43.0
## [17] Biobase_2.73.2 GenomicRanges_1.65.1
## [19] Seqinfo_1.3.0 IRanges_2.47.2
## [21] S4Vectors_0.51.6 BiocGenerics_0.59.10
## [23] generics_0.1.4 MatrixGenerics_1.25.0
## [25] matrixStats_1.5.0 BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] RColorBrewer_1.1-3 jsonlite_2.0.0 magrittr_2.0.5
## [4] ggbeeswarm_0.7.3 GenomicFeatures_1.65.0 gypsum_1.9.0
## [7] farver_2.1.2 rmarkdown_2.31 BiocIO_1.23.3
## [10] vctrs_0.7.3 memoise_2.0.1 Rsamtools_2.29.0
## [13] RCurl_1.98-1.19 htmltools_0.5.9 BiocBaseUtils_1.15.1
## [16] AnnotationHub_4.3.2 curl_7.1.0 BiocNeighbors_2.7.2
## [19] Rhdf5lib_2.1.0 sass_0.4.10 alabaster.base_1.13.1
## [22] bslib_0.11.0 alabaster.sce_1.13.0 httr2_1.3.0
## [25] cachem_1.1.0 GenomicAlignments_1.49.1 lifecycle_1.0.5
## [28] pkgconfig_2.0.3 rsvd_1.0.5 R6_2.6.1
## [31] fastmap_1.2.0 digest_0.6.39 AnnotationDbi_1.75.2
## [34] irlba_2.3.7 ExperimentHub_3.3.1 RSQLite_3.53.3
## [37] beachmat_2.29.0 filelock_1.0.3 labeling_0.4.3
## [40] httr_1.4.8 compiler_4.6.1 bit64_4.8.2
## [43] withr_3.0.3 S7_0.2.2 BiocParallel_1.47.0
## [46] viridis_0.6.5 DBI_1.3.0 alabaster.ranges_1.13.0
## [49] alabaster.schemas_1.13.0 rappdirs_0.3.4 rjson_0.2.23
## [52] tools_4.6.1 vipor_0.4.7 otel_0.2.0
## [55] beeswarm_0.4.0 glue_1.8.1 restfulr_0.0.17
## [58] rhdf5filters_1.25.2 grid_4.6.1 gtable_0.3.6
## [61] ensembldb_2.37.3 BiocSingular_1.29.0 ScaledMatrix_1.21.0
## [64] XVector_0.53.0 ggrepel_0.9.8 BiocVersion_3.24.0
## [67] pillar_1.11.1 dplyr_1.2.1 BiocFileCache_3.3.0
## [70] lattice_0.22-9 rtracklayer_1.73.0 bit_4.6.0
## [73] tidyselect_1.2.1 Biostrings_2.81.6 knitr_1.51
## [76] gridExtra_2.3.1 bookdown_0.47 ProtGenerics_1.45.0
## [79] xfun_0.60 UCSC.utils_1.9.0 lazyeval_0.2.3
## [82] yaml_2.3.12 evaluate_1.0.5 codetools_0.2-20
## [85] cigarillo_1.3.1 tibble_3.3.1 alabaster.matrix_1.13.0
## [88] BiocManager_1.30.27 cli_3.6.6 jquerylib_0.1.4
## [91] dichromat_2.0-1 Rcpp_1.1.2 GenomeInfoDb_1.49.1
## [94] dbplyr_2.6.0 png_0.1-9 XML_3.99-0.23
## [97] parallel_4.6.1 blob_1.3.0 AnnotationFilter_1.37.0
## [100] bitops_1.1-0 alabaster.se_1.13.0 viridisLite_0.4.3
## [103] scales_1.4.0 purrr_1.2.2 crayon_1.5.3
## [106] rlang_1.3.0 cowplot_1.2.0 KEGGREST_1.53.6
References
Butler, A., P. Hoffman, P. Smibert, E. Papalexi, and R. Satija. 2018. “Integrating single-cell transcriptomic data across different conditions, technologies, and species.” Nat. Biotechnol. 36 (5): 411–20.
Grun, D., M. J. Muraro, J. C. Boisset, K. Wiebrands, A. Lyubimova, G. Dharmadhikari, M. van den Born, et al. 2016. “De novo prediction of stem cell identity using single-cell transcriptome data.” Cell Stem Cell 19 (2): 266–77.
Haghverdi, L., A. T. L. Lun, M. D. Morgan, and J. C. Marioni. 2018. “Batch effects in single-cell RNA-sequencing data are corrected by matching mutual nearest neighbors.” Nat. Biotechnol. 36 (5): 421–27.
Leek, J. T., W. E. Johnson, H. S. Parker, A. E. Jaffe, and J. D. Storey. 2012. “The sva package for removing batch effects and other unwanted variation in high-throughput experiments.” Bioinformatics 28 (6): 882–83.
Lin, Y., S. Ghazanfar, K. Y. X. Wang, J. A. Gagnon-Bartsch, K. K. Lo, X. Su, Z. G. Han, et al. 2019. “scMerge leverages factor analysis, stable expression, and pseudoreplication to merge multiple single-cell RNA-seq datasets.” Proc. Natl. Acad. Sci. U.S.A. 116 (20): 9775–84.
Muraro, M. J., G. Dharmadhikari, D. Grun, N. Groen, T. Dielen, E. Jansen, L. van Gurp, et al. 2016. “A single-cell transcriptome atlas of the human pancreas.” Cell Syst 3 (4): 385–94.
Ritchie, M. E., B. Phipson, D. Wu, Y. Hu, C. W. Law, W. Shi, and G. K. Smyth. 2015. “limma powers differential expression analyses for RNA-sequencing and microarray studies.” Nucleic Acids Res. 43 (7): e47.
Zheng, G. X., J. M. Terry, P. Belgrader, P. Ryvkin, Z. W. Bent, R. Wilson, S. B. Ziraldo, et al. 2017. “Massively parallel digital transcriptional profiling of single cells.” Nat. Commun. 8 (January): 14049.
Well, to be more precise, they trade these obviously-wrong assumptions for a different set of less-obviously-wrong assumptions.↩︎
Though this benefit is probably not that meaningful as it assumes that the correction algorithm manages to preserve the rare subpopulations. Indeed, MNN correction, graph-based clustering and UMAP are all based on \(k\)-nearest neighbors with similar choices for \(k\), so if a cell type is too rare to be identified within a single batch, it’s unlikely to form correct MNN pairs either.↩︎
As opposed to other conclusions that are just there to fill up the manuscript. I mean… does the world really need more gene regulatory networks?↩︎
A better name would be “block”, which is what we use to describe uninteresting factors in the rest of the book. But “block correction” sounds weird and bitcoin-related so we just decided to stick with “batch” in the chapter name.↩︎