# Independent Filtering in DESeq2 and edgeR

Independent filtering is a statistical procedure that removes genes with very low read counts from differential expression testing before or after p-value calculation, so that multiple-testing correction is applied only to genes with enough data to detect a signal. In DESeq2 the procedure runs automatically inside the results() call and optimizes a count threshold to maximize the number of significant findings, while in edgeR the equivalent job is done up front with the filterByExpr() function.

The procedure matters because almost every bulk RNA-seq experiment measures far more genes than it can power. A typical human RNA-seq library assigns reads to well over 20,000 genes, and a large share of those genes sit so low in abundance that no realistic fold change in them could ever reach [statistical significance](/blog/guides/statistical-significance). If you feed all of them into a Benjamini-Hochberg correction, each near-zero-count gene adds a test to the denominator and inflates the adjusted p-values of the genes you actually care about. Independent filtering recovers that lost power in a statistically defensible way. When you understand what the procedure is doing, you can interpret NA adjusted p-values, explain a suspiciously small list of hits, and troubleshoot the classic RNA-seq error of an experiment reporting almost nothing or almost everything. The broader point has been made repeatedly in method-comparison work: analysis choices, including which genes enter the test, change which genes come out, and reviewers increasingly expect analysts to document and justify these choices [1],.

## What Independent Filtering Means in DESeq2 and edgeR

The word "independent" carries the statistical weight. A filter is independent when the quantity used to remove genes is unrelated to the test statistic under the null hypothesis of no differential expression. Mean expression level satisfies this condition. A gene can be lowly expressed in every sample and still be truly unchanged, so dropping low-count genes does not bias the null distribution of p-values. By contrast, filtering directly on p-values, adjusted p-values, or fold changes removes genes based on the very quantity you are testing, which distorts the false discovery rate in ways that are hard to quantify. This distinction, formalized in the multiple-testing literature for high-throughput experiments, is the conceptual foundation for both tools described here.

The two packages implement the same idea at different stages of the workflow:

- **DESeq2** keeps all genes with nonzero counts through the model fitting stage, computes a raw p-value for every testable gene, and then applies independent filtering as an optimization step inside results(). It searches for the mean-count threshold that yields the most rejections at your chosen FDR cutoff.
- **edgeR** filters before testing. The filterByExpr() function computes a counts-per-million (CPM) cutoff tailored to your library sizes and design, subsets the DGEList, and only the retained genes enter dispersion estimation and the generalized linear model. There is no post-hoc threshold optimization, because the filtered-out genes never receive a p-value.

Both approaches protect the false discovery rate (FDR), which is the expected proportion of false positives among the genes you call significant. They differ in timing, in what gets reported for removed genes, and in how much control you have over the threshold. Those differences drive most of the practical interpretation questions that come up in RNA-seq consulting.

## Why the Multiple Testing Problem Demands Filtering

Differential expression analysis tests one hypothesis per gene. With 25,000 tested genes and a per-gene error rate of 0.05, you would expect roughly 1,250 false positives by chance alone, which is why raw p-values are never used directly. The standard remedy is the Benjamini-Hochberg (BH) procedure, which controls the FDR by scaling each p-value according to its rank among all tested genes.

The denominator in that calculation is where filtering earns its keep. Consider a simplified picture:

| Genes tested | Genes truly DE | Genes low-count and untestable | Effect on BH correction |
|--|--|--|--|
| 25,000 (no filtering) | 300 | 10,000 | 10,000 weak tests inflate every adjusted p-value |
| 15,000 (filtered) | 300 | 0 | Same 300 real signals compete against fewer tests, so more cross the FDR line |

The genes you remove by an expression-based filter are not genes that would have been significant anyway. They are genes whose counts are so low that the test has essentially no power. Keeping them in the correction buys nothing and costs power. This is the core argument for independent filtering, and it applies identically whether the filter is optimized after the fact, as in DESeq2, or set from count thresholds before the test, as in edgeR.

One caution belongs here. Filtering helps when the removed genes are a random sample with respect to the null hypothesis. If low abundance correlates with something your design is confounded with, an expression filter can interact with the biology in ways you should check, a theme that appears in broader evaluations of how global expression properties affect differential expression methods [3].

## How DESeq2 Implements Independent Filtering, Step by Step

DESeq2's independent filtering runs entirely within results(), which means you get it by default and you may not know it happened. The sequence of operations, as documented in the DESeq2 package documentation, is:

1. **Model fitting.** DESeq(dds) estimates size factors, dispersions, and fitted model coefficients for every gene with at least one nonzero count. Genes with all-zero counts across all samples cannot be tested and receive NA throughout.
2. **Wald or likelihood ratio test.** Every testable gene receives a raw p-value.
3. **Filter statistic.** The default filter is the mean of normalized counts for each gene, computed across all samples. This statistic is independent of the test statistic under the null, which is what licenses the procedure.
4. **Threshold optimization.** DESeq2 sorts genes by the filter statistic and scans candidate cutpoints. For each candidate it asks how many genes would show an adjusted p-value at or below your alpha if the correction were applied to that subset. It selects the cutpoint that maximizes the number of rejections.
5. **Adjustment and masking.** The BH correction is applied to the retained genes. Genes falling below the selected threshold, and genes whose adjusted p-value exceeds alpha, receive padj = NA. Raw p-values are never modified.

Two parameters control the behavior:

```r
res <- results(dds, alpha = 0.05)
```

The alpha argument must match the FDR cutoff you intend to use. If you plan to report genes at FDR < 0.05, set alpha = 0.05 so the filter is optimized for exactly that operating point. Setting alpha = 0.1 relaxes both the filter and the adjustment. Setting alpha after the fact, or calling results() with the default alpha of 0.1 and then filtering the table at 0.05, is one of the most common RNA-seq errors because the filter was optimized for the wrong target.

You can also watch the optimization directly:

```r
metadata(res)$filterThreshold
metadata(res)$filterTheta
metadata(res)$filterNumRej
```

filterThreshold reports the mean-normalized-count cutoff, for example a value like 4.8, meaning genes whose average normalized abundance falls below that value were masked. filterTheta reports the corresponding quantile of genes removed. plot(res) draws the rejection curve, with the chosen threshold marked, so you can see whether the optimum is a sharp peak or a flat plateau.

To compare against no filtering at all:

```r
resNoFilt <- results(dds, independentFiltering = FALSE, alpha = 0.05)
sum(res$padj < 0.05, na.rm = TRUE)
sum(resNoFilt$padj < 0.05, na.rm = TRUE)
```

In most moderately sized experiments the filtered call returns more genes at the same FDR, which is the power gain the procedure exists to deliver. In very large experiments with deep libraries, the two numbers often converge because few genes fall below any reasonable threshold.

### Reading NA values in the DESeq2 results table

![Decision path through a DESeq2 results row that ends in NA, separating all-zero genes, filtered genes and outlier-flagged genes.](/article-images/illustrations/independent-filtering-deseq2-edger-deseq2-na-triage.webp)

Every NA in the padj column has a specific cause, and the distinction matters for interpretation:

- **NA in both log2FoldChange and pvalue** means the gene had all-zero or nearly-zero counts and was never testable.
- **A p-value present but padj = NA** means the gene was masked by independent filtering, or its adjusted p-value exceeded alpha.
- **A log2FoldChange but NA pvalue** can reflect genes filtered by an cooksCutoff outlier-removal step, which is a separate mechanism from independent filtering.

The message to carry forward is that NA padj values after independent filtering are a normal, documented outcome, not an RNA-seq error in your pipeline.

## How edgeR Handles Low-Abundance Genes with filterByExpr

edgeR takes the opposite order. Filtering happens before dispersion estimation, before the negative binomial generalized linear model is fitted, and before any p-value exists. The tool for this is filterByExpr(), which implements the filtering logic originally developed for the limma-voom pipeline and described in the edgeR user guide.

The function works in counts per million (CPM), which normalizes raw counts by library size:

```r
library(edgeR)
y <- DGEList(counts = counts, group = group)
y <- calcNormFactors(y)
design <- model.matrix(~ group)
keep <- filterByExpr(y, design)
y <- y[keep, , keep.lib.sizes = FALSE]
```

Three properties define the behavior:

1. **Design-aware thresholds.** filterByExpr() inspects the design matrix and determines the size of the smallest group being compared. A CPM value that reads as noise in a five-versus-five comparison may be perfectly informative in a ten-versus-ten comparison, so the effective cutoff scales with the design rather than being a fixed number.
2. **A minimum count, not a minimum CPM alone.** The default requires at least min.count = 10 raw counts (the default) in the least-replicated group, combined with a CPM cutoff that accounts for the minimum library size. This prevents the classic error of keeping a gene whose CPM looks acceptable only because its library was tiny.
3. **One pass, no optimization.** Genes that fail the filter are removed from the object. They never receive a p-value, a log2 fold change, or an entry in the final table. The BH correction inside topTags() then operates only on the retained genes.

```r
y <- estimateDisp(y, design, robust = TRUE)
fit <- glmQLFit(y, design, robust = TRUE)
qlf <- glmQLFTest(fit, coef = 2)
topTags(qlf, n = Inf)
```

The practical consequence is that edgeR result tables contain no NA padj values from filtering. Every row in the table was tested. If you want the removed genes listed for a supplementary table, you retrieve them from the original unfiltered DGEList, which is why saving the pre-filter object is good laboratory hygiene.

The contrast with DESeq2 can be stated in one sentence. DESeq2 optimizes the filter to maximize discoveries at your alpha, edgeR sets the filter from count-based evidence rules before testing, and both preserve FDR control because the filter criterion is independent of differential expression status.

## Independent Filtering and the Terms It Gets Confused With

| Term | What it actually is | How it differs from independent filtering |
|--|--|--|
| Independent filtering (DESeq2) | Post-test optimization of a mean-count threshold to maximize rejections at a given alpha | Filter criterion is expression level, threshold chosen from the data, padj set to NA below it |
| filterByExpr (edgeR) | Pre-test CPM and count-based filtering scaled to the design | No threshold optimization, no NA values, removed genes never tested |
| BH / FDR correction | Statistical adjustment of p-values for multiple testing | Applied within the retained set, it does not remove genes by itself |
| Hard pre-filtering by CPM or TPM | User-chosen count cutoff applied before any tool runs | Fully manual, threshold not data-driven, must never use p-values or fold changes as criteria |
| Screening by fold change or p-value | Post-hoc selection of "interesting" genes | Not a filter in the statistical sense, it biases the FDR and is not independent |
| Differential expression threshold (log2FC > 1) | Effect-size criterion layered on top of FDR | Orthogonal to filtering, it shrinks the hit list after the fact |

The confusion students most often carry is treating all of these as interchangeable "cleaning steps." They are not. Only the first three have a documented statistical justification for preserving error control. The last two are legitimate presentation choices, but they change what your gene list means, and they should be reported as explicit thresholds in your methods section.

## A Complete Worked Workflow

The following two blocks show a minimal, correct two-group comparison in each package with the filtering step in its proper position. Both assume a count matrix named counts with genes in rows and samples in columns, and a factor named group with two levels.

DESeq2:

```r
library(DESeq2)
dds <- DESeqDataSetFromMatrix(countData = counts,
                              colData = coldata,
                              design = ~ group)
dds <- dds[rowSums(counts(dds)) > 1, ]   # optional coarse pre-filter
dds <- DESeq(dds)
res <- results(dds, alpha = 0.05)
metadata(res)$filterThreshold
sum(res$padj < 0.05, na.rm = TRUE)
```

The rowSums pre-filter is optional and only removes genes with essentially no information. It is a convenience, not a requirement, because DESeq2's independent filtering handles the statistical side. The critical rule is that the criterion must be total or normalized counts, never the p-value.

edgeR:

```r
library(edgeR)
y <- DGEList(counts = counts, group = group)
y <- calcNormFactors(y)
design <- model.matrix(~ group)
keep <- filterByExpr(y, design)
table(keep)
y <- y[keep, , keep.lib.sizes = FALSE]
y <- estimateDisp(y, design, robust = TRUE)
fit <- glmQLFit(y, design, robust = TRUE)
qlf <- glmQLFTest(fit, coef = 2)
de <- topTags(qlf, n = Inf, adjust.method = "BH", filterByFDR = 0.05)$table
```

Two details deserve attention. First, filtering must precede estimateDisp(), because low-count genes degrade dispersion estimates for the genes that remain. Second, filtering after calcNormFactors() but with keep.lib.sizes = FALSE recomputes library sizes on the retained genes, which is the documented recommendation in the edgeR user guide.

The same structural logic carries into pipelines and graphical platforms. Automated systems such as BioWardrobe wrap ChIP-seq and RNA-seq analysis, including differential expression, behind a user interface with built-in quality control measures designed for troubleshooting NGS experiments [4]. When a pipeline makes the filtering decision for you, knowing where in the sequence the decision sits is what lets you read its output intelligently rather than treating the gene list as an oracle.

## How to Test and Observe Independent Filtering in Your Own Data

Observation is straightforward once you know which objects hold the information. For DESeq2:

- **metadata(res)$filterThreshold** gives the numeric cutoff on mean normalized counts. Reporting this single number in your methods section makes the filter reproducible.
- **metadata(res)$filterTheta** gives the fraction of genes removed.
- **plot(res)** shows the number of rejections across candidate thresholds, which is the actual optimization the package performed.
- **mcols(res)$description** documents what each column means in the current results object, useful when training new analysts.

For edgeR:

- **table(keep)** reports how many genes the filter retained and removed.
- **cpm(y)[1:5, 1:5]** lets you spot-check whether the retained genes meet the CPM expectations you intended.
- **sum(keep == FALSE)** counts the removed genes for your methods statement.

A useful sanity check in any project is to run the analysis once with the filter disabled or loosened and once at the default, then compare hit counts and inspect a few genes near the boundary. If the top of your list is stable across those settings, your main conclusions are robust to the filtering choice. If the list changes wholesale, the finding is fragile and you should say so. Comparative work on differential expression methods emphasizes exactly this kind of robustness checking, and reports that method-to-method variability shrinks as sample sizes grow, while results in small cell-line experiments can differ meaningfully between tools [1].

## Interpreting Results After Filtering

Interpretation questions cluster around three recurring situations.

**The hit list seems too short.** First check the filter threshold. A high threshold on a shallow-sequenced experiment can remove a large share of the transcriptome. Compare the number of genes tested (rows retained) with the number you expected, and consider whether alpha was set correctly. Also verify that the design matrix is right, because a misspecified model is a far more common cause of empty result tables than filtering ever is.

**The hit list seems too long.** Extremely large lists usually indicate a real global effect, such as a treatment that shifts expression genome-wide, or a batch artifact, rather than a filtering problem. Work on global expression shifts has shown that violations of the equal-total-RNA assumption, common in cancers with active master regulators such as c-MYC, affect some differential expression methods much more than others and can produce biased conclusions in standard pipelines [3]. Filtering interacts with this only weakly, since the filter is abundance-based, but a global shift can change which genes sit above the abundance threshold in each condition.

**Reviewers or collaborators question the NA values.** The strongest response is to report the filter threshold, the number of genes tested before and after filtering, and the alpha used for optimization. Methodological scrutiny of published analyses frequently targets exactly these choices, such as which data entered the analysis and which summary measures were used, as illustrated by published correspondence on differential expression methodology in cancer studies [2]. A one-paragraph methods statement covering filter criterion, threshold, and alpha answers most of those questions in advance.

## Choosing Between Tools and Filters in Practice

![Side-by-side comparison of DESeq2 and edgeR filtering position, criterion, threshold choice and NA reporting.](/article-images/illustrations/independent-filtering-deseq2-edger-deseq2-vs-edger-filter.webp)

Neither tool is wrong. The choice depends on the experiment and on what you need the gene list to do.

DESeq2's approach suits analysts who want the data to pick the operating point. It is particularly helpful when library depths vary, when the experiment is underpowered, and when you want the maximum defensible number of rejections at a stated FDR. The cost is a results table with NA values and a threshold that differs between contrasts, which complicates merging results across multiple comparisons, because the same gene can be retained in one contrast and masked in another.

edgeR's approach suits analysts who want a single, transparent, design-aware rule applied uniformly across all contrasts in a project. Because the filter runs before dispersion estimation, it also stabilizes the model fit. The cost is that the default CPM-based rule is not optimized for discovery count, so in marginal cases you may test slightly fewer genes than an optimized threshold would retain.

Head-to-head evaluations of differential expression tools provide context for this choice. In a study using knock-out and over-expression experiments to test recovery of a known causal gene in RNA-seq cell line data, the authors found potential value in testing the generalized linear model implementation of edgeR with robust dispersion estimation more frequently for both single-variate and multivariate two-group comparisons, with criteria that could be set less stringent than an absolute fold change above 1.5 and FDR below 0.05, and they observed that variability between methods decreased in patient-sample comparisons with larger sample sizes [1]. Their work supports a practical habit rather than a single verdict. Run the comparison under more than one tool and filtering regime when the conclusion matters, and treat agreement between regimes as evidence of robustness.

Whatever tool you choose, document the filter with the same care you document the model. Reproducibility frameworks for sequencing analysis, including structured protocols that standardize preprocessing through downstream transcriptomic steps and provide explicit guidance on parameter selection and troubleshooting, exist precisely because undocumented parameter choices are the leading source of irreproducible results [5]. The filter threshold is one of those parameters.

## Downstream Consequences for Gene-Set and Pathway Analysis

Independent filtering does not end at the gene list. Whatever list you carry forward into enrichment analysis inherits every property of the filter that produced it.

The list you hand to a gene-set analysis (GSA) tool is a ranked or thresholded subset of the filtered results, and the statistical behavior of GSA methods depends on the distributional properties of the input expression values. Systematic examination of twenty-two tumor and normal datasets found that multivariate normality, an assumption embedded in many gene-set methods when they were first proposed, frequently fails in real data, and that non-normality, including mixture distributions representing distinct tumor subtypes, measurably affects the performance of both parametric and non-parametric GSA methods [6]. A filtering choice that systematically removes certain abundance classes can therefore shift the composition of the gene sets you test, which is one more reason to report the filter threshold rather than let it remain invisible.

Two practical rules follow:

1. Use the full ranked table (all retained genes, sorted by a statistic such as the moderated test statistic) for methods like GSEA-style ranking, and apply the FDR-filtered subset only where the method requires a defined list.
2. Keep the filter consistent across contrasts you intend to compare in enrichment analysis, so that differences in enriched pathways reflect biology rather than different thresholds per contrast.

## Common Mistakes and Limitations

The following mistakes account for most of the confusion and most of the RNA-seq error reports surrounding filtering in real projects.

- **Filtering on adjusted p-values or fold changes before correction.** This is circular. It removes genes because of the result you are trying to measure, distorts the FDR, and makes error control uninterpretable. Filter on counts or CPM only.
- **Calling results() with the default alpha and then thresholding at a different cutoff.** The filter is optimized for the alpha you pass. If you optimized at alpha = 0.1 and then reported genes at FDR < 0.05, the filter was tuned for the wrong target. Pass the alpha you intend to report.
- **Treating padj = NA as a bug.** In DESeq2, NA values below the filter threshold are the procedure working as designed. Compute sum(res$padj < 0.05, na.rm = TRUE) and report the threshold alongside it.
- **Forgetting that filter thresholds differ per contrast in DESeq2.** When you merge three contrasts into one figure, a gene can be NA in one table and significant in another. Either accept that, or use independentFiltering = FALSE with an explicit count-based pre-filter applied uniformly, and say which you did in the methods.
- **Filtering before calcNormFactors in edgeR.** TMM normalization factors should be estimated on the full gene set, because the composition assumption needs the broadest possible view of the library. Filter after normalization.
- **Assuming the filter recovers power for free.** Independent filtering improves power for genes near the detectable-abundance boundary. It cannot create signal for genes with zero counts in a condition, and it cannot fix a confounded design, a missing replicate, or a global expression shift that violates the assumptions of the model [3].
- **Reporting fold-change screening as filtering.** Adding an effect-size cutoff such as |log2FC| > 1 is a legitimate presentation rule, but it is not independent and it must not be described as such in the methods.
- **Applying single-cell habits to bulk data.** Workflows from single-cell analysis, such as Seurat-based protocols with their own parameter-selection and troubleshooting conventions, carry different abundance and detection distributions than bulk RNA-seq [5]. A CPM rule tuned for bulk libraries does not transfer unchanged.

A final limitation concerns interpretation at the biological level. Filtering decisions, distributional assumptions in downstream gene-set methods, and database selection for validation all shape the final story, and each has been shown in methodological literature to alter conclusions under realistic conditions [6],. Individual projects need judgment that no default parameter provides, and study-level decisions about experimental design, including animal or clinical work, need involvement from the relevant subject-matter specialist.

## Troubleshooting Common RNA-Seq Errors Around Filtering

When a differential expression result looks wrong, filtering is rarely the root cause, but it is frequently where the symptom appears. A systematic troubleshooting order saves hours.

1. **Zero significant genes.** Check the design matrix for correct sample mapping, check size factors for extreme spread, then check metadata(res)$filterThreshold in DESeq2 or table(keep) in edgeR. If the filter removed an unexpectedly large share of genes, look at library sizes, because shallow sequencing raises every CPM-based threshold.
2. **Thousands of significant genes.** Verify the contrast direction and coefficient numbering in the design matrix, then look for a global expression shift, which comparative studies show some methods tolerate better than others [3]. A genome-wide shift is a real biological or technical signal, not a filtering artifact, and it may warrant a different analysis strategy.
3. **Errors from downstream tools on NA values.** Many enrichment and visualization functions do not accept NA. Either use na.omit() deliberately, or export the unfiltered table with a column flagging which genes the filter removed, so nothing is silently lost.
4. **Different gene counts between pipeline versions.** If an automated platform updated its defaults, the filter may have changed. Platforms that bundle differential expression behind automated QC, such as BioWardrobe, expose quality-control measures precisely so users can catch these shifts rather than discovering them in review [4].
5. **Discrepancy between DESeq2 and edgeR hit lists.** Compare the number of genes tested in each. A difference in the tested set, not the model, explains most discrepancies. Then compare the overlap among genes passing a shared count-based floor, which removes the filter as a variable.
6. **Thresholds that vary inexplicably between samples or batches.** Recompute CPM distributions per sample before filtering. One failed library drags down the minimum library size used by filterByExpr and loosens the filter for everyone. Fix the library, not the filter.

A troubleshooting habit worth building is to log, for every analysis, five numbers: total genes, genes with all-zero counts, genes entering the test, the filter threshold or rule, and genes called significant at your FDR. Those five numbers reproduce the filtering behavior of the entire pipeline on a single line, and they turn vague questions like "why did the results change" into answerable ones.

## Quick Review

- Independent filtering removes low-expression genes from the multiple-testing correction because they cannot contribute signal, which increases power for the rest.
- "Independent" means the filter criterion, usually mean normalized counts or CPM, is unrelated to the test statistic under the null hypothesis.
- DESeq2 filters after testing, inside results(), and optimizes the threshold to maximize rejections at your chosen alpha. Inspect it with metadata(res)$filterThreshold and plot(res).
- edgeR filters before testing with filterByExpr(), using a design-aware CPM and minimum-count rule, so its tables contain no filtering-related NA values.
- alpha in results() must match the FDR cutoff you plan to report, otherwise the filter is optimized for the wrong target.
- Never filter on p-values, adjusted p-values, or fold changes, because those criteria are not independent and they break FDR control.
- Report the filter threshold, the number of genes tested, and alpha in your methods section for every published analysis.

## Frequently Asked Questions

### What does independent filtering in DESeq2 and edgeR mean?

It means removing genes with insufficient read counts from the differential expression analysis so the false discovery rate correction applies only to genes that were actually testable. DESeq2 does this after testing by optimizing a mean-count threshold inside results(), and edgeR does it before testing with filterByExpr(), which applies a CPM and minimum-count rule scaled to your design.

### Does edgeR perform independent filtering the same way DESeq2 does?

No. edgeR filters before the statistical test using filterByExpr(), so removed genes never receive a p-value and no threshold optimization occurs. DESeq2 tests all genes first, then chooses the abundance threshold that maximizes the number of significant findings at your alpha. Both preserve FDR control because both use expression-based, not p-value-based, criteria.

### Why are so many padj values NA in my DESeq2 results?

NA values are expected and are not a pipeline error. Genes with all-zero counts were never testable, and genes below the optimized filter threshold, or with adjusted p-values above alpha, are masked with padj = NA. Check metadata(res)$filterThreshold to see the cutoff, and use sum(res$padj < 0.05, na.rm = TRUE) to count your significant genes.

### Does independent filtering change my p-values or fold changes?

No. Raw p-values and log2 fold changes are computed before filtering and are untouched. Only the adjusted p-value column changes, because the BH correction is applied to a smaller set of genes, and genes below the threshold are assigned NA rather than an adjusted value.

### Should I pre-filter low-count genes before running DESeq2 or edgeR?

A coarse pre-filter that removes genes with almost no counts in every sample is acceptable and speeds up computation, as long as the criterion is total or normalized counts. In DESeq2, independent filtering makes aggressive pre-filtering unnecessary. In edgeR, filterByExpr() after calcNormFactors() is the standard step and should run before estimateDisp().

### Does independent filtering give me more significant genes than I deserve?

No, when the filter criterion is expression-based and independent of the test statistic, the false discovery rate among the retained tests remains controlled at your alpha. What increases is power, because fewer weak tests inflate the BH correction. Filtering on p-values or fold changes instead would be circular and would distort the error rate, which is why the independence property is the entire basis of the procedure.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What does independent filtering in DESeq2 and edgeR mean?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "It means removing genes with insufficient read counts from the differential expression analysis so the false discovery rate correction applies only to genes that were actually testable. DESeq2 does this after testing by optimizing a mean-count threshold inside results(), and edgeR does it before testing with filterByExpr(), which applies a CPM and minimum-count rule scaled to your design."
      }
    },
    {
      "@type": "Question",
      "name": "Does edgeR perform independent filtering the same way DESeq2 does?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. edgeR filters before the statistical test using filterByExpr(), so removed genes never receive a p-value and no threshold optimization occurs. DESeq2 tests all genes first, then chooses the abundance threshold that maximizes the number of significant findings at your alpha. Both preserve FDR control because both use expression-based, not p-value-based, criteria."
      }
    },
    {
      "@type": "Question",
      "name": "Why are so many padj values NA in my DESeq2 results?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "NA values are expected and are not a pipeline error. Genes with all-zero counts were never testable, and genes below the optimized filter threshold, or with adjusted p-values above alpha, are masked with padj = NA. Check metadata(res)$filterThreshold to see the cutoff, and use sum(res$padj < 0.05, na.rm = TRUE) to count your significant genes."
      }
    },
    {
      "@type": "Question",
      "name": "Does independent filtering change my p-values or fold changes?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No. Raw p-values and log2 fold changes are computed before filtering and are untouched. Only the adjusted p-value column changes, because the BH correction is applied to a smaller set of genes, and genes below the threshold are assigned NA rather than an adjusted value."
      }
    },
    {
      "@type": "Question",
      "name": "Should I pre-filter low-count genes before running DESeq2 or edgeR?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "A coarse pre-filter that removes genes with almost no counts in every sample is acceptable and speeds up computation, as long as the criterion is total or normalized counts. In DESeq2, independent filtering makes aggressive pre-filtering unnecessary. In edgeR, filterByExpr() after calcNormFactors() is the standard step and should run before estimateDisp()."
      }
    },
    {
      "@type": "Question",
      "name": "Does independent filtering give me more significant genes than I deserve?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No, when the filter criterion is expression-based and independent of the test statistic, the false discovery rate among the retained tests remains controlled at your alpha. What increases is power, because fewer weak tests inflate the BH correction. Filtering on p-values or fold changes instead would be circular and would distort the error rate, which is why the independence property is the entire basis of the procedure."
      }
    }
  ]
}
</script>

## Sources

1. [Critical Differential Expression Assessment for Individual Bulk RNA-Seq Projects](https://pubmed.ncbi.nlm.nih.gov/38405814/)
2. [Comment on ‘The expression landscape of cachexia-inducing factors in human cancers’ by Freire et al.](https://pubmed.ncbi.nlm.nih.gov/33442951/)
3. [The Influence of the Global Gene Expression Shift on Downstream Analyses](https://pubmed.ncbi.nlm.nih.gov/27092944/)
4. [Analysis of ChIP-Seq and RNA-Seq Data with BioWardrobe](https://pubmed.ncbi.nlm.nih.gov/29767371/)
5. [A Reproducible Seurat-Based Protocol for Single-Cell RNA Sequencing Analysis of Peripheral Blood Mononuclear Cell CD4+ T Cells During Malaria Reinfection.](https://pubmed.ncbi.nlm.nih.gov/42612107/)
6. [The impact of distributional assumptions in gene-set and pathway analysis: how far can it go wrong?](https://doi.org/10.1101/2021.02.01.429279)