Glossary · RNA-seq
Log fold change shrinkage
Why your DESeq2 volcano plot has a spike of huge fold changes at low expression, and what apeglm or ashr does to fix it before you rank genes.
By Ming "Tommy" Tang, Director of Bioinformatics in Big Pharma · Reviewed September 2026 · 3 min read
Also: apeglm, ashr, lfcShrink
Definition
Log fold change (LFC) shrinkage is an empirical Bayes correction applied to a fitted differential expression model's effect size estimates, pulling noisy, poorly-supported log2 fold changes toward zero while leaving well-supported large effects largely unchanged. In DESeq2 it is implemented as a separate post-processing step, lfcShrink(), run after DESeq(), with a choice of prior: apeglm (adaptive Cauchy prior, the default since DESeq2 1.28.0), ashr (a mixture-of-normals prior that also handles arbitrary contrasts), or the original normal prior. It is a bias-variance trade-off: you accept a small, controlled bias toward zero in exchange for a large reduction in variance, specifically for genes where the data give the model little information to pin down the fold change.
You meet this the first time you sort a DESeq2 results table by log2FoldChange and the top hits are all genes with three reads in one sample and zero in another. The fold change is technically correct and completely useless: a jump from 1 count to 8 counts is an 8-fold change with almost no statistical support behind it. Log fold change shrinkage is the fix DESeq2 ships for exactly this failure mode, and it runs as a separate step after DESeq(), called lfcShrink().
This matters because two different numbers in the same results table answer two different questions. The unshrunken log2FoldChange from DESeq() is what the Wald test used to compute your p-value. The shrunken log2FoldChange from lfcShrink() is what you should rank genes by, plot on an MA plot, or report as an effect size. Mixing these up, using the wrong one for the wrong purpose, is the single most common way this step gets misapplied.
Why it matters
Get this wrong and your gene ranking, your GSEA preranked list, your "top hits" table, and your MA plot all get dominated by statistical noise instead of biology. The DESeq2 paper's own Bottomly dataset benchmark makes the size of the problem concrete: rank genes by unshrunken log2 fold change and split the samples into two independent groups, and only 21 of the top 100 genes overlap between the two rankings. Rank by shrunken fold change instead, and 81 of the top 100 overlap. That is the difference between a gene list you can defend to a collaborator and one that is mostly an artifact of which four low-count genes happened to get an extra read in one replicate.
Get it right and you also avoid the opposite failure: over-shrinking a real, large, well-supported effect into looking unremarkable. Both apeglm and ashr are built so that precise, large effects stay largely unshrunk while small effects indistinguishable from zero get shrunk hard toward zero. The amount of shrinkage a gene gets depends on how much information the data provide for that specific gene, not just on its mean count, so two genes with similar expression but different dispersion can be shrunk by very different amounts.
Where people get it wrong
The mistake practitioners make most often is filtering on the shrunken log2FoldChange and treating it as if it changed the significance call, or conversely, ranking genes on the unshrunken log2FoldChange while using the shrunken plot to eyeball which genes look interesting. These are two different columns computed for two different jobs. DESeq() gives you the fold change the Wald test actually used, paired with its p-value and padj. lfcShrink() recomputes log2FoldChange and lfcSE under a shrinkage prior for ranking and visualization, and in the process drops the stat column entirely, keeping only baseMean, log2FoldChange, lfcSE, pvalue, and padj. The pvalue/padj columns carried over into the shrunken table are still the ones from the original test, not recomputed from the shrunken estimate, so shrinkage does not change which genes are "significant." The other trap is treating apeglm's optional s-value output as a drop-in replacement for padj: an s-value bounds the probability of getting the sign of the effect wrong among genes at or below that s-value, which is a different guarantee than an FDR-controlled p-value, and the two are not interchangeable on a plot legend.
A concrete example
Standard workflow: fit the model, then shrink the coefficient of interest for ranking and plotting, keeping the unshrunken results around for the actual significance call.
dds <- DESeq(dds)
# unshrunken: use this table's pvalue/padj for calling significance
res <- results(dds, name = "condition_B_vs_A")
# shrunken: use this for ranking genes, GSEA input, and MA/volcano plots
res_shrunk <- lfcShrink(dds, coef = "condition_B_vs_A", type = "apeglm")
plotMA(res, ylim = c(-5, 5)) # noisy fan of high LFC at low baseMean
plotMA(res_shrunk, ylim = c(-5, 5)) # same genes, low-count noise pulled to 0
# ashr alternative, works directly from a contrast, no coef relevel needed
res_ashr <- lfcShrink(dds, contrast = c("condition", "B", "A"), type = "ashr")Related terms
Questions people ask
- What does lfcShrink do in DESeq2?
It takes the log2 fold change estimates from a fitted DESeq2 model and pulls the noisy ones, mostly from low-count or high-dispersion genes, toward zero using empirical Bayes shrinkage, while leaving well-supported large effects largely alone. It does not touch your p-values or padj.
- Should I filter genes on shrunken or unshrunken log2FoldChange?
Use the unshrunken log2FoldChange and the Wald test p-value/padj from DESeq() for calling significance, since that is what the test was computed on. Use the shrunken log2FoldChange from lfcShrink() for ranking, GSEA input, and any plot or table you show someone, because it will not be dominated by noise from low-count genes.
- apeglm vs ashr, which should I use?
apeglm has been the DESeq2 default since version 1.28.0 because it preserves large effects better and has lower mean absolute error; use it for standard two-group coefficient comparisons and small sample sizes. Use ashr when you need a contrast rather than a coefficient (no relevel-and-rerun needed), when you have many coefficients and want speed, or when you want a smoother gradient of shrinkage rather than apeglm's more aggressive push toward zero.
- Why did my results table lose the stat column after lfcShrink?
lfcShrink() recomputes log2FoldChange and lfcSE under the shrinkage prior and drops the Wald statistic column because it no longer corresponds to a simple test statistic under apeglm or ashr; baseMean, log2FoldChange, lfcSE, pvalue, and padj remain. If you need the original stat, keep a copy of the DESeq() results before shrinking.
- Does shrinkage change my p-values?
No. Standard lfcShrink() with apeglm or ashr shrinks the fold change estimate and its standard error but reuses the p-value and padj already computed by DESeq(); the only exception is that apeglm can optionally report s-values instead, which answer a different question about the probability of a false sign.
Related pages
Related reading on the blog
Sources
- DESeq2 Vignette: Analyzing RNA-seq data with DESeq2 — apeglm became the default shrinkage estimator in DESeq2 1.28.0; results table column changes
- Support.Bioconductor: New function lfcShrink() in DESeq2 — lfcShrink() introduced in DESeq2 1.16 as a separate function; three shrinkage methods (apeglm, ashr, normal)
- Moderated estimation of fold change and dispersion for RNA-seq data with DESeq2 — Bias-variance trade-off rationale; Bottomly dataset replicate consistency numbers (81/100 vs 21/100)
- Heavy-tailed prior distributions for sequence count data: removing the noise and preserving large differences — apeglm's adaptive Cauchy prior and why large effects stay largely unshrunk
- apeglm: Approximate Posterior Estimation for GLM — apeglm computational methods and performance
- Support.Bioconductor: Interpreting the results from lfcShrink() with apeglm in DESeq2 — s-values explanation; ashr working with contrasts without relevel
- Support.Bioconductor: Which of apeglm and ashr may be more appropriate for pseudobulked DESeq2 analysis — apeglm more aggressive than ashr, pushes more genes toward logFC=0
- Support.Bioconductor: Log2FoldChange shrinkage method in DESeq2 — apeglm performs well with small sample sizes; both methods leave precise large effects unshrunk