Written and maintained by CASRAI Editorial Board
Last updated
PyDESeq2 is a Python reimplementation of DESeq2, written from scratch, for differential expression analysis of bulk RNA-seq count data. If you have a raw count table and sample metadata and want to test which genes differ between conditions without leaving Python, this guide gives the install routes, a minimal runnable pipeline, the input shape that trips most first runs, and a cross-walk from R DESeq2 names to their PyDESeq2 equivalents. It was read against the official documentation and repository on 2026-11-07.
What PyDESeq2 is used for in research
PyDESeq2 tests for differential expression in bulk RNA-seq. Its documentation describes it as a Python implementation of the DESeq2 method, built on the AnnData data structure and part of the scverse ecosystem. It supports single-factor and multi-factor designs, categorical or continuous factors, Wald tests, and optional apeGLM log fold change shrinkage. The defaults are designed to broadly match DESeq2 v1.34.0, although the documentation cautions that some values and features may differ from R DESeq2. It is released under the MIT licence and is free to use.
The method is described in Muzellec, Telenczuk, Cabeli and Andreux, “PyDESeq2: a python package for bulk RNA-seq differential expression analysis”, Bioinformatics 2023, doi:10.1093/bioinformatics/btad547 (first posted as a bioRxiv preprint in December 2022). Cite it if you report results from the package. For the R-side methods landscape, see DESeq2 vs edgeR and the broader RNA-seq experimental design and analysis guide.
Install PyDESeq2 (pip, conda, uv, bioconda)
All routes below come from the official installation page and the project README.
| Route | Command | Where it is documented |
|---|---|---|
| pip | pip install pydeseq2 |
Documentation installation page |
| conda environment, then pip | conda env create -n pydeseq2, then conda activate pydeseq2, then pip install pydeseq2 |
Documentation installation page |
| uv | uv add pydeseq2 |
GitHub README |
| bioconda | conda install -c bioconda pydeseq2 |
GitHub README |
The repository states that continuous integration tests Python 3.12 to 3.14. The installation page states no minimum Python version. If you are setting up a fresh environment, installing Anaconda for Python covers the conda side.
Step-by-step: run a differential expression analysis
The steps below follow the official minimal pipeline example (using the package’s synthetic example data).
- Prepare the counts. A pandas DataFrame of raw, non-negative integer counts with samples as rows and genes as columns.
- Prepare the metadata. A DataFrame with one row per sample; its index must match the counts index. It must contain the column you name in the design (here
condition). - Import the classes.
from pydeseq2.datasets import load_example_data from pydeseq2.dds import DeseqDataSet from pydeseq2.default_inference import DefaultInference from pydeseq2.ds import DeseqStats - Load data (the example loader; replace with your own DataFrames).
counts_df = load_example_data(modality="raw_counts", dataset="synthetic", debug=False) metadata = load_example_data(modality="metadata", dataset="synthetic", debug=False) - Fit the model.
inference = DefaultInference(n_cpus=8) dds = DeseqDataSet(counts=counts_df, metadata=metadata, design="~condition", refit_cooks=True, inference=inference) dds.deseq2() - Test a contrast.
ds = DeseqStats(dds, contrast=["condition", "B", "A"], inference=inference) ds.summary() results = ds.results_df
The contrast is given as the factor name followed by the two levels to compare, here condition B against condition A. n_cpus sets the number of CPU cores used for inference; adjust it to your machine.
Output and how to read it
ds.results_df is a DataFrame with one row per gene and the columns baseMean, log2FoldChange, lfcSE, stat, pvalue and padj. The PyDESeq2 example page does not define these columns; the meanings below are the standard DESeq2 convention that the package follows.
baseMean: mean of normalised counts across samples.log2FoldChange: the estimated log2 fold change for the contrast you requested.lfcSE: standard error of that estimate.stat: the Wald test statistic.pvalue: the unadjusted p-value.padj: the p-value adjusted for multiple testing across genes. In DESeq2 and edgeR this is a Benjamini-Hochberg adjusted value, and papers usually filter on it at a 5% or 10% false discovery rate.
Filter on padj, not pvalue, when listing differentially expressed genes. To visualise the table, see volcano plots for RNA-seq.
R DESeq2 to PyDESeq2 cross-walk
| R DESeq2 concept | PyDESeq2 equivalent |
|---|---|
DESeqDataSet object |
DeseqDataSet (pydeseq2.dds) |
DESeq() fitting call |
dds.deseq2() |
results() with a contrast |
DeseqStats(dds, contrast=[...]), then ds.summary() and ds.results_df |
| Count matrix, genes as rows | pandas DataFrame, samples as rows and genes as columns |
This is a map of names, not a guarantee of identical numbers: the documentation states that defaults broadly match DESeq2 v1.34.0 and that some values may differ.
Troubleshooting
Counts and metadata do not line up
The counts DataFrame has samples as rows and genes as columns, which is the opposite orientation to the genes-by-samples count matrix R users are used to. If your table is genes by samples, transpose it first. The metadata index must match the sample index of the counts.
Non-integer or negative values
The input must be raw, non-negative integer counts, not normalised values. Supply the counts from your quantification step rather than TPM or already-normalised tables. See creating a DataFrame in Python if you are building the tables by hand.
Installation problems
Use a clean conda environment as in the table above, and install with pip inside it. If you are on a bioconda-based setup, use the bioconda command instead.
Free alternatives
| Tool | Language and home | Licence | Install |
|---|---|---|---|
| PyDESeq2 | Python, scverse | MIT | pip install pydeseq2 |
| DESeq2 | R, Bioconductor: negative binomial model for count data | LGPL (>= 3) | BiocManager::install("DESeq2") |
| edgeR | R, Bioconductor: differential expression of sequence count data | GPL (>= 2) | BiocManager::install("edgeR") |
Licences and install commands are from each project’s own page, read 2026-11-07. For a method-level comparison of the two R packages, see DESeq2 vs edgeR; for other free tools, see free statistical software.
FAQ
What is PyDESeq2?
A Python implementation of the DESeq2 method for bulk RNA-seq differential expression, built on AnnData and part of scverse, released under the MIT licence.
How do I install PyDESeq2?
Run pip install pydeseq2. Conda users can run conda install -c bioconda pydeseq2, and uv users uv add pydeseq2.
Does PyDESeq2 give the same results as DESeq2?
Its defaults are designed to broadly match DESeq2 v1.34.0, but the documentation states that some values or features may differ from R DESeq2.
What input does PyDESeq2 need?
Raw non-negative integer counts in a samples-by-genes pandas DataFrame, plus a metadata DataFrame whose index matches the samples.
How do I cite PyDESeq2?
Cite Muzellec, Telenczuk, Cabeli and Andreux, Bioinformatics 2023, doi:10.1093/bioinformatics/btad547.
Sources
- PyDESeq2 documentation: pydeseq2.readthedocs.io, including the minimal pipeline example and installation page (read 2026-11-07).
- scverse/PyDESeq2 repository: github.com/scverse/PyDESeq2 (read 2026-11-07).
- Muzellec et al., bioRxiv preprint, December 2022: 10.1101/2022.12.14.520412.
- Bioconductor DESeq2 and edgeR package pages (read 2026-11-07).







