This chapter answers the question that Normalization and
filtering prepared the counts for: which genes change
between conditions in each cell type?
limma_voom answers it by default. It runs limma-voom Law et al., 2014Ritchie et al., 2015 on
every cell type of a cube, one cell type at a time, and limma_trend runs
limma-trend the same way. Genes that change in each cell
type shows both on published data.
What runs in each cell type¶
Each cell type is fitted on its own, with its own samples, genes, normalization factors, mean-variance trend and prior, in the steps of the RNA-seq workflows that the limma and edgeR User’s Guides describe:
Samples. The samples with too few cells or counts in the cell type are left out (Samples with few cells or counts). The cell type’s design matrix keeps the rows of the full design matrix that belong to the remaining samples, so every column keeps its meaning.
Genes. edgeR’s
filterByExpr, given that design matrix, keeps the genes with enough counts, and the library sizes become the totals over the genes kept (Filtering by expression).Normalization. TMM factors scale the library sizes (Normalization factors and size factors).
voom. voom turns the counts into log-CPM values with a precision weight for each value (below), and a weighted linear model is fitted to every gene (Designs, linear models and contrasts).
Tests. Each contrast is estimated from the fit (Contrasts), each gene’s residual variance is moderated by empirical Bayes (Empirical Bayes moderation), and the p-values of each contrast are adjusted by Benjamini and Hochberg’s method over the genes of the cell type (Multiple testing).
In R these are filterByExpr and calcNormFactors of edgeR, then voom, lmFit,
contrasts.fit, eBayes and topTable of limma, and the results agree with theirs
(Checks and further reading).
With fewer samples, the design matrix of a cell type can lose rank, for instance when all
the samples of one donor were left out. The cell type is then fitted on the columns kept
by R’s qr(), and each contrast is tested where its samples can estimate it; the others
are reported (Designs that cannot answer the question). A cell
type that cannot be fitted at all, such as one left without residual degrees of freedom,
gets the reason in the result’s summary (DifferentialExpressionResult), and the
other cell types are fitted as usual.
voom¶
Log counts are noisier at low counts than at high ones, and a linear model that weighs every observation alike gives the noisy ones too much say. voom estimates the mean-variance trend of the log counts and gives every observation a precision weight from it, so that the weighted linear model and the empirical Bayes moderation of limma apply to counts Law et al., 2014.
For counts of gene in sample and effective library sizes (library sizes times normalization factors), voom works with log-CPM values, log2 counts per million:
The design is fitted to every gene by least squares, which gives fitted values
and residual standard deviations . The square roots are
smoothed by LOWESS (lowess; Cleveland (1979)) against each gene’s average log
count, , and each observation
gets the weight
the inverse of the variance that the trend predicts at its fitted log count. voom
returns the log-CPM values, the weights and the trend in a VoomResult.
The span¶
Each local fit of the LOWESS uses a share of the genes, the span. By default the span is
chosen from the number of genes by limma’s rule (choose_lowess_span): 1 up to
50 genes, and
beyond, so 0.65 at 400 genes and 0.395 at 20,000: the part of the span above 0.3 falls
in proportion to , as the width of a histogram’s bins does in the rules of
Scott (1979) and Freedman & Diaconis (1981). limma’s voom chooses the span this way by default since
limma 3.66; its default before was 0.5, which span=0.5 reproduces. In a cube, each cell
type’s span follows the number of its genes that pass the filter, and the cell type’s
record keeps it: in the data of Kang et al. (2018) the spans run from 0.44 in the CD4 T cells
(6,295 genes) to 0.59 in the megakaryocytes (704 genes).
limma-trend¶
limma-trend uses no precision weights. Instead, the empirical Bayes prior for the
variances follows the mean-variance trend. limma_trend takes the steps above, but
the counts become log-CPM values with a prior count of 3, as the limma User’s Guide
computes them for limma-trend; every gene is fitted by unweighted least squares, or with
sample weights when they are asked for; and the prior variance follows the genes’ average
log-CPM (eBayes(trend = TRUE); Estimating the prior). The
standard errors of contrasts are then exact, as limma’s are, since every gene has the
same weights.
voom applies the trend to each observation, with its sample’s library size; limma-trend
applies it to each gene, and assumes that library sizes are not very different. limma’s
documentation says that voom should be more powerful when library sizes are very
different, and that otherwise the two methods should give similar results (voom’s help
page; Law et al. (2014)). The limma User’s Guide (sections 15.4 and 15.5) says that limma-trend
usually works well when the largest library size is not more than about three times the
smallest. Pseudobulk library sizes grow with the number of cells pooled, which can differ
severalfold between samples, so limma_voom is the default.
Prior weights from the number of cells¶
A pseudobulk profile sums the counts of its cells, so its log-CPM values estimate the mean
expression of those cells, whose variance falls as one over their number: the precision of
a profile is proportional to its number of cells, as the dreamlet paper argues
Hoffman et al., 2026. prior_weights="n_cells" makes the number of cells of each sample in
the cell type its prior weight, as dreamlet does. In limma_voom they weight the
fits from which voom estimates its trend, and multiply its precision weights; in
limma_trend they are sample weights of the linear model. Only their ratios within
a cell type count, so weights ten times larger change nothing. The cube must record its
cell numbers, as one from from_cells does.
Sample quality weights¶
Some samples follow the model less closely than others: pooled from few cells, sequenced
less deeply, or taken from an outlying donor. Removing them loses power, and keeping them
adds noise. Sample quality weights down-weight them instead Ritchie et al., 2006Liu et al., 2015. For
pseudobulk data, a limma author recommends sample weights, estimated by edgeR’s
voomLmFit, in preference to a covariate of quality such as RNA integrity Smyth, 2025.
For gene and sample , with prior weights , the model gives the mean and the
variance
where the log variance factors are common to all genes and sum to zero. The
sample quality weights are , whose product is one. sample_quality_weights
estimates by restricted maximum likelihood (REML; Smyth (2002)) or gene by gene
Ritchie et al., 2006, as limma’s arrayWeights does, with a prior worth 10 genes at equal
weights (prior_n) that keeps the weights near one when the evidence is weak.
voom_with_quality_weights combines them with voom as limma’s
voomWithQualityWeights does: voom; sample quality weights from its log-CPM values and
weights; voom again, with those as prior weights; sample quality weights again, from the
new trend’s weights; and the new trend’s weights times the new sample quality weights.
quality_weights=True runs it in every cell type of limma_voom, gene by gene, as
voomWithQualityWeights does by default. limma_trend estimates the weights from
its log-CPM values by REML, which arrayWeights chooses for data without prior weights or
missing values, and fits them as sample weights. The result’s sample table keeps each
sample’s weight. Where the weights cannot be estimated, for example when fewer than two
residual degrees of freedom remain, each is one, and a warning names the cell type.
In a design that pairs two samples per donor, such as ~ donor + condition, the two
samples of a donor have equal and opposite residuals (up to voom’s precision weights), so an
outlying sample pulls its partner’s weight down with it: limma-trend gives both samples
the same weight, and limma-voom gives them similar ones.
Defaults and choices¶
The defaults follow limma’s workflow for RNA-seq, with two additions: samples with fewer than 10 cells in a cell type are left out, and contrasts get exact standard errors (Contrasts).
voom or trend.
limma_voomby default;limma_trendwhen library sizes lie within about threefold of each other (limma-trend).Samples, genes and normalization.
min_cells,min_counts,expression_filterandnormalizationset the samples, genes and library sizes of each cell type’s fit, andprior_countthe prior count of limma-trend, as Normalization and filtering gives them.The span, of
limma_voom: chosen in each cell type by default (The span);span=0.5reproduces limma’s voom before limma 3.66.Sample weights.
quality_weights=Trueestimates a weight for each sample from the data, and a mapping changes the settings of the estimate, such as{"method": "reml"};prior_weights="n_cells"takes the weights from the numbers of cells. The two are alternatives: limma’svoomWithQualityWeightstakes no prior weights, and edgeR’svoomLmFitrefuses prior weights together with estimated sample weights, aslimma_voomandlimma_trenddo.Hypervariable genes.
robust=Trueestimates the prior robustly, so that genes with outlying variances do not distort it Phipson et al., 2016 (Estimating the prior).Contrasts of several coefficients. Their standard errors are exact by default; in
limma_voom,contrast_standard_errors="limma"reproduces limma’s approximation (Contrasts). In limma-trend the two agree.Many cell types. p-values are adjusted within each cell type and contrast;
adjust_across_groups=Trueadds a column adjusted over every cell type (Multiple testing).
Limits and differences¶
Fixed effects only. A design with random effects is refused. Several samples of a donor are paired in the design (
~ donor + condition), or the donor gets a random effect withdream_voom(Mixed models and dream). limma’sduplicateCorrelationand voom’s block correlation are not implemented.No voomLmFit. limma’s documentation now recommends edgeR’s
voomLmFitovervoom, in particular to estimate a correlation within blocks or for sparse counts with many zeros (voom’s help page);voomLmFitis not implemented.Normalization. Normalization factors are TMM factors or none; voom’s microarray-style normalization of log-CPM values (
normalize.method) is not offered.Groups of samples.
limma_voomandlimma_trendgive each sample a weight of its own. limma’s variance models for groups of samples (var.group,var.design) are offered bysample_quality_weightsandvoom_with_quality_weights, on one matrix.Rank-deficient cell types. Every contrast that a cell type’s samples can estimate is tested, where limma’s
contrasts.fitrefuses some and changes the meaning of others (Designs that cannot answer the question).Values without meaning are refused.
voomraises an error where limma’s voom returns values without meaning: for a sample whose library size is zero, or where the trend is not positive at a fitted value.Designs of full rank.
voomneeds a design of full column rank, where limma’s drops the aliased columns;limma_voomfits each cell type on the columns it keeps.Prior weights in voom’s weights. With prior weights,
voomreturns the product of the trend’s weights and the prior weights, the weights that a fit needs; limma’svoomreturns only the trend’s weights, which its help page says modify the prior weights.Where REML stops.
sample_quality_weightsstops REML where limma stops, which can be short of the maximum of the likelihood, so that its weights are limma’s; a smallertoleranceconverges further. The gene-by-gene algorithm depends on the order of the genes, as limma’s does.
Checks and further reading¶
Each step is compared with limma 3.62.1 and edgeR 4.4.0, run as separate programs on the same data:
voom and its span: exact (1e-10), and close (1e-6) on rarefied counts (Validation).
Sample quality weights, alone and with voom: close (1e-6), the tier of iterative estimates (Validation).
limma_voomandlimma_trendon simulated cubes, with rank-deficient cell types: exact, and close with robust moderation (Validation); with sample quality weights, prior weights,min_countsand adjustment across cell types: exact, and close with sample quality weights (Validation).limma_voomon the published data of Kang et al. (2018), from the cells to the tables of 8 cell types: exact (Validation).
Further reading:
Law et al. (2014) introduced voom, whose precision weights let limma’s linear models and empirical Bayes moderation analyse RNA-seq counts.
Ritchie et al. (2015) described limma and its workflows for RNA-seq and microarray data.
Ritchie et al. (2006) introduced quality weights for the arrays of a microarray experiment, and Liu et al. (2015) combined them with voom’s weights for RNA-seq, which improved power.
Kang et al. (2018) pooled cells of 8 lupus patients, split into interferon-beta stimulated and untreated cultures, and assigned cells to donors by their genotypes; the documentation uses these data (Datasets).
Perez et al. (2022) profiled more than 1.2 million blood cells of 162 lupus cases and 99 controls, and found interferon-stimulated genes raised in monocytes and fewer naive CD4 T cells.
The next chapters model what limma-voom does not: the counts themselves (edgeR and DESeq2), and several samples of each donor (Mixed models and dream).
- Law, C. W., Chen, Y., Shi, W., & Smyth, G. K. (2014). voom: precision weights unlock linear model analysis tools for RNA-seq read counts. Genome Biology, 15(2), R29. 10.1186/gb-2014-15-2-r29
- Ritchie, M. E., Phipson, B., Wu, D., Hu, Y., Law, C. W., Shi, W., & Smyth, G. K. (2015). limma powers differential expression analyses for RNA-sequencing and microarray studies. Nucleic Acids Research, 43(7), e47. 10.1093/nar/gkv007
- Cleveland, W. S. (1979). Robust locally weighted regression and smoothing scatterplots. Journal of the American Statistical Association, 74(368), 829–836. 10.1080/01621459.1979.10481038
- Scott, D. W. (1979). On optimal and data-based histograms. Biometrika, 66(3), 605–610. 10.1093/biomet/66.3.605
- Freedman, D., & Diaconis, P. (1981). On the histogram as a density estimator: L2 theory. Zeitschrift Für Wahrscheinlichkeitstheorie Und Verwandte Gebiete, 57(4), 453–476. 10.1007/BF01025868
- Kang, H. M., Subramaniam, M., Targ, S., Nguyen, M., Maliskova, L., McCarthy, E., Wan, E., Wong, S., Byrnes, L., Lanata, C. M., Gate, R. E., Mostafavi, S., Marson, A., Zaitlen, N., Criswell, L. A., & Ye, C. J. (2018). Multiplexed droplet single-cell RNA-sequencing using natural genetic variation. Nature Biotechnology, 36(1), 89–94. 10.1038/nbt.4042
- Hoffman, G. E., Lee, D., Bendl, J., Prashant, N. M., Hong, A., Casey, C., Alvia, M., Shao, Z., Argyriou, S., Therrien, K., Venkatesh, S., Voloudakis, G., Haroutunian, V., Fullard, J. F., & Roussos, P. (2026). Efficient differential expression analysis of large-scale single-cell transcriptomics data using Dreamlet. Nature Communications, 17(1), 9597. 10.1038/s41467-026-75680-8
- Ritchie, M. E., Diyagama, D., Neilson, J., van Laar, R., Dobrovic, A., Holloway, A., & Smyth, G. K. (2006). Empirical array quality weights in the analysis of microarray data. BMC Bioinformatics, 7(1), 261. 10.1186/1471-2105-7-261
- Liu, R., Holik, A. Z., Su, S., Jansz, N., Chen, K., Leong, H. S., Blewitt, M. E., Asselin-Labat, M.-L., Smyth, G. K., & Ritchie, M. E. (2015). Why weight? Modelling sample and observational level variability improves power in RNA-seq analyses. Nucleic Acids Research, 43(15), e97. 10.1093/nar/gkv412
- Smyth, G. K. (2025). Answer to “Guidance on limma for pseudobulk scRNA-seq with multi-factor design”, 11 September 2025. Bioconductor support site. https://support.bioconductor.org/p/9162569/
- Smyth, G. K. (2002). An efficient algorithm for REML in heteroscedastic regression. Journal of Computational and Graphical Statistics, 11(4), 836–847. 10.1198/106186002871
- Phipson, B., Lee, S., Majewski, I. J., Alexander, W. S., & Smyth, G. K. (2016). Robust hyperparameter estimation protects against hypervariable genes and improves power to detect differential expression. The Annals of Applied Statistics, 10(2), 946–963. 10.1214/16-AOAS920
- Perez, R. K., Gordon, M. G., Subramaniam, M., Kim, M. C., Hartoularos, G. C., Targ, S., Sun, Y., Ogorodnikov, A., Bueno, R., Lu, A., Thompson, M., Rappoport, N., Dahl, A., Lanata, C. M., Matloubian, M., Maliskova, L., Kwek, S. S., Li, T., Slyper, M., … Ye, C. J. (2022). Single-cell RNA-seq reveals cell type-specific molecular and genetic associations to lupus. Science, 376(6589), eabf1970. 10.1126/science.abf1970