Back to blog
Research Guide

Why Your DESeq2 Log2 Fold Change Cutoff Of Zero Is Wrong

By Abdullah Shahid · · Updated · 11 min read

A researcher posted to the bioinformatics community with a specific problem: too many GO terms. Their differential expression analysis had returned hundreds of significant gene ontology hits, and they could not figure out what to report. The top comment identified the issue immediately. They had filtered their DESeq2 results with abs(log2FoldChange) > 0. That cutoff does not filter anything. Any gene with a non-zero fold change passes it, which in a real experiment is nearly every gene that was tested. The GO-term explosion was not a downstream analysis problem. It was a upstream filtering problem disguised as an enrichment problem.

This mistake is common enough to have its own community thread. Understanding why it happens, and how to replace it with something defensible, is one of the most practically useful things you can do for the quality of your published results.

What Does Log2 FC Mean in RNA-Seq?

Log2 FC means log2 fold change: the base-2 logarithm of expression in the numerator condition divided by expression in the denominator condition. In a treated vs control contrast, positive values indicate higher expression in treated samples and negative values indicate lower expression. Reversing the contrast reverses every sign, so always report the reference level and contrast direction.

The conversion back to ordinary fold change is 2^log2FC. For a negative log2 FC, it is often clearer to describe the reciprocal decrease than to report a decimal ratio.

Log2 FCOrdinary fold changeInterpretation for treated vs control
-20.25Expression is 4-fold lower in treated
-10.5Expression is 2-fold lower in treated
01No estimated change
0.585about 1.5Expression is about 1.5-fold higher in treated
12Expression is 2-fold higher in treated
24Expression is 4-fold higher in treated

Log2 FC measures effect size, not certainty. A large value can still be poorly supported when counts are low or variability is high, while a small value can have a very low adjusted p-value in a large, precise experiment. That is why DEG reporting should consider the estimated effect, its uncertainty, and multiple-testing-adjusted significance together.

Why log2FC Greater Than Zero Means Nothing

Fold change on a log2 scale is symmetric around zero. A log2FC of 1 means a twofold upregulation. A log2FC of minus 1 means a twofold downregulation. A log2FC of 0.01 means a 0.7 percent increase. It is statistically distinguishable from zero, but it is biologically indistinguishable from noise in almost any RNA-seq experiment.

When you filter abs(log2FoldChange) > 0, you are asking DESeq2 to return every gene whose estimated fold change is not exactly zero. In practice, because DESeq2 estimates fold changes from count data with biological variability, essentially no gene returns exactly zero. The filter is tautological. You are left with whatever the padj threshold alone selects, which at padj < 0.05 in a well-powered experiment can be thousands of genes.

Those thousands of genes then go into your enrichment analysis. The enrichment analysis tests every GO term in the database against a gene list that is far too broad to be biologically informative. Terms that are enriched simply because your gene list is large enough to contain a representative fraction of every annotation class come out as significant. The results are not wrong in a statistical sense; they are just not telling you anything about your experiment specifically.

What Reviewers Expect and Why

The question a reviewer asks when they see your fold change cutoff is: what is the smallest biological effect you are willing to call meaningful? A cutoff of zero says your answer is “any effect at all.” That is not a scientific position. It is an absence of one.

There is no universal reviewer-approved log2 FC cutoff. Values such as abs(log2FoldChange) >= 1 (twofold change) or >= 1.5 (roughly threefold) are common reporting choices, but convention alone does not make them correct for every experiment. A defensible threshold should follow the biological question, measurement precision, expected effect size, and cost of follow-up. A smaller, consistent shift across a pathway can be important even when no single gene changes twofold.

For some experiments, particularly those using very precise perturbations where you expect subtle transcriptional changes, a lower cutoff with a strong justification is defensible. The key word is justification. You need to be able to explain why you chose the threshold you chose, and “because it was the default” is not a justification.

A common exploratory reporting rule is padj < 0.05 and abs(log2FoldChange) >= 1, when a twofold minimum matches the scientific goal. Both conditions must be met under that rule. The adjusted p-value limits the expected false discovery rate, while the fold-change threshold excludes effects below the chosen reporting minimum. Confirmatory analyses should define that minimum before inspecting the results whenever possible.

A common starting point for DEG filtering

For an exploratory analysis, adjusted p-value below 0.05 plus absolute log2 fold change at or above 1 is a transparent starting rule. It is not universal. Choose the effect-size minimum for the biological question, state it in the methods, and consider threshold-aware tests when the minimum itself is part of the claim.

padj Versus p: Never Filter on the Raw p-Value

The adjusted p-value and the raw p-value are both present in the DESeq2 results object. They look similar. They are not interchangeable.

The raw p-value from DESeq2 is the probability of observing a test statistic as extreme as the one measured, assuming no real effect, for that one gene. Across the tens of thousands of genes tested simultaneously, many raw p-values below 0.05 will appear purely by chance. This is the multiple testing problem.

The adjusted p-value corrects for the number of simultaneous tests using the Benjamini-Hochberg procedure. It controls the false discovery rate: if you filter at padj < 0.05, you expect at most 5 percent of the genes that pass to be false positives. That is the guarantee you need to make a claim about your gene list.

If you filter on the raw p-value column instead, you have no such guarantee. In a standard human RNA-seq experiment testing 20,000 genes at p < 0.05, you expect roughly 1,000 false positives by chance alone. Those will be in your DEG list and your enrichment analysis.

DESeq2 applies Benjamini-Hochberg correction by default. The column in the results table is padj. Filter on that column. The column named pvalue is for diagnostics and reporting, not for thresholding.

lfcShrink and Why It Changes Your Threshold Logic

Fold change estimation in DESeq2 without shrinkage is noisy for genes with low counts. A gene with 3 counts in one condition and 8 in another can show an apparent log2FC of 1.4, but the confidence interval on that estimate spans several log2FC units. The point estimate is unreliable.

lfcShrink addresses this by pulling fold change estimates for noisy, lowly expressed genes toward zero. The statistical significance of those genes (their padj) is unchanged. What changes is the reported fold change: it becomes a shrunken estimate that better reflects the precision of the underlying data.

This matters for your threshold logic in a specific way. If you are working with unshrunken fold changes, a threshold of abs(log2FoldChange) >= 1 will include some genes where the true fold change is much smaller and the observed value reflects sampling noise. After lfcShrink, the same threshold is more conservative: genes that pass it are more likely to have a genuine fold change near 1.

The practical recommendation is to apply lfcShrink using the apeglm estimator before filtering on fold change. The apeglm estimator is the current default recommendation from the DESeq2 team and performs well across a range of experimental designs.

library(DESeq2)
library(apeglm)
# Run DESeq2
dds <- DESeq(dds)
# Apply shrinkage to the contrast of interest
# "condition_treated_vs_control" must match the resultsNames(dds) output
res_shrunk <- lfcShrink(dds,
coef = "condition_treated_vs_control",
type = "apeglm")
# Filter using the shrunken fold changes and adjusted p-value
deg_table <- as.data.frame(res_shrunk)
degs <- deg_table[
!is.na(deg_table$padj) &
deg_table$padj < 0.05 &
abs(deg_table$log2FoldChange) >= 1, ]
nrow(degs) # Number of DEGs after both thresholds
Side by side volcano plots showing DESeq2 results before and after lfcShrink, with unshrunken estimates showing artificially high fold changes for low-count genes
Figure 1: Volcano plots before and after lfcShrink with apeglm. Unshrunken estimates (left) show spuriously large fold changes for lowly expressed genes. After shrinkage (right), those estimates contract toward zero, producing a tighter, more reliable fold change distribution.

Statistical Significance Versus Biological Significance

The most important conceptual distinction in differential expression analysis is between statistical significance and biological significance. They measure different things. Confusing them produces both false positives and false negatives in the interpretation of results.

A gene is statistically significant when its padj is below your chosen threshold. That means: given the data you collected, the probability that this effect is a false positive is acceptably low. It says nothing about whether the effect is large enough to matter biologically.

A gene is biologically significant when the magnitude of the change is large enough to have a plausible functional consequence. Whether a twofold change in a given gene is biologically meaningful depends entirely on the gene and the context. For a transcription factor, a twofold increase in expression might drive major downstream regulatory changes. For a structural protein, a twofold change might have no measurable phenotype.

The fold change threshold you apply at the DEG filtering step is a proxy for biological significance. It is a crude proxy; you should not claim that every gene above your threshold is biologically important or that every gene below it is irrelevant. What you can claim is that you have applied a principled filter that limits your reported gene list to effects large enough to warrant further investigation.

The right framing in a paper is: “Genes meeting both the statistical criterion (padj < 0.05) and the effect size criterion (abs(log2FoldChange) >= 1) were considered differentially expressed for downstream analysis.” That framing is accurate about what you did and defensible to a reviewer who asks why.

Two volcano plots comparing DEG selection at log2FC cutoff of 0 versus log2FC cutoff of 1, showing the dramatic reduction in selected genes when a meaningful fold change threshold is applied
Figure 2: The practical effect of the fold change threshold. At log2FC greater than 0 (left), nearly every statistically significant gene is reported. At log2FC at least 1 (right), the reported gene list contracts to genes with biologically meaningful effect sizes. The GO-term explosion disappears.

Putting It Together

The corrected workflow is three steps. Apply lfcShrink with the apeglm estimator to get reliable fold change estimates. Filter the results table requiring both padj < 0.05 and abs(log2FoldChange) >= 1. Pass that filtered gene list, and only that filtered gene list, to your enrichment analysis, with the full tested universe (all genes with non-NA padj) as the background.

Those three steps will not make your biology more interesting. What they will do is ensure that the results you report reflect your experiment rather than statistical artifacts of a misconfigured filter. They will also cut your GO-term list from hundreds of terms to a manageable set that a reader can actually interpret.

NotchBio applies these defaults automatically: lfcShrink with apeglm runs on every differential expression analysis, the significance filter requires both padj < 0.05 and abs(log2FoldChange) >= 1 by default, and the enrichment background is always the tested gene universe. If you want to change those defaults, you can. But the starting point is correct, which means you do not have to know to set it.

Further reading

Read another related post

View all posts