Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

limma-voom and limma-trend

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:

  1. 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.

  2. 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).

  3. Normalization. TMM factors scale the library sizes (Normalization factors and size factors).

  4. 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).

  5. 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 rgir_{gi} of gene gg in sample ii and effective library sizes RiR_i (library sizes times normalization factors), voom works with log-CPM values, log2 counts per million:

ygi=log⁡2rgi+0.5Ri+1 106.y_{gi} = \log_2 \frac{r_{gi} + 0.5}{R_i + 1}\, 10^6 .

The design is fitted to every gene by least squares, which gives fitted values μ^gi\hat\mu_{gi} and residual standard deviations sgs_g. The square roots sg1/2s_g^{1/2} are smoothed by LOWESS (lowess; Cleveland (1979)) against each gene’s average log count, xg=yˉg+log⁡2(Ri+1)‾−log⁡2106x_g = \bar y_g + \overline{\log_2(R_i + 1)} - \log_2 10^6, and each observation gets the weight

wgi=lo⁡(λgi)−4,λgi=log⁡2 ⁣(2μ^gi(Ri+1)/106),w_{gi} = \operatorname{lo}(\lambda_{gi})^{-4}, \qquad \lambda_{gi} = \log_2\!\left(2^{\hat\mu_{gi}} (R_i + 1) / 10^6\right),

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 nn by limma’s rule (choose_lowess_span): 1 up to 50 genes, and

span=0.3+0.7 (50/n)1/3\text{span} = 0.3 + 0.7\,(50 / n)^{1/3}

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 n−1/3n^{-1/3}, 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 gg and sample jj, with prior weights wgjw_{gj}, the model gives the mean and the variance

E(ygj)=xj′bg,var⁡(ygj)=σg2 euj/wgj,E(y_{gj}) = x_j' b_g, \qquad \operatorname{var}(y_{gj}) = \sigma_g^2\, e^{u_j} / w_{gj},

where the log variance factors u=Zγu = Z\gamma are common to all genes and sum to zero. The sample quality weights are e−uje^{-u_j}, whose product is one. sample_quality_weights estimates γ\gamma 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).

Limits and differences

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:

Further reading:

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).

References
  1. 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
  2. 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
  3. 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
  4. Scott, D. W. (1979). On optimal and data-based histograms. Biometrika, 66(3), 605–610. 10.1093/biomet/66.3.605
  5. 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
  6. 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
  7. 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
  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
  9. 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
  10. 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/
  11. 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
  12. 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
  13. 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