| name | pydeseq2 |
| description | Differential expression analysis for RNA-seq count data. Invoke when query mentions DESeq2, differential expression, DE genes, or RNA-seq comparison. |
PyDESeq2
Python implementation of DESeq2 for differential expression analysis with bulk RNA-seq data.
When to Use
- Query mentions "DESeq2", "differential expression", "DE genes"
- Comparing gene expression between conditions (treated vs control)
- Multi-factor designs with batch effects or covariates
- Input is a count matrix (raw integer counts, NOT normalized)
Quick Start
import pandas as pd
from pydeseq2.dds import DeseqDataSet
from pydeseq2.ds import DeseqStats
counts_df = pd.read_csv("counts.csv", index_col=0).T
metadata = pd.read_csv("metadata.csv", index_col=0)
genes_to_keep = counts_df.columns[counts_df.sum(axis=0) >= 10]
counts_df = counts_df[genes_to_keep]
dds = DeseqDataSet(
counts=counts_df,
metadata=metadata,
design="~condition",
refit_cooks=True
)
dds.deseq2()
ds = DeseqStats(dds, contrast=["condition", "treated", "control"])
ds.summary()
results = ds.results_df
significant = results[results.padj < 0.05]
Design Formulas
design = "~condition"
design = "~batch + condition"
design = "~age + condition"
design = "~group + condition + group:condition"
Rule: put adjustment variables (batch, age) BEFORE the variable of interest.
Contrast Specification
Format: [variable, test_level, reference_level]
ds = DeseqStats(dds, contrast=["condition", "treated", "control"])
for treatment in ["A", "B", "C"]:
ds = DeseqStats(dds, contrast=["condition", treatment, "control"])
ds.summary()
Result Columns
| Column | Meaning |
|---|
baseMean | Mean normalized count across samples |
log2FoldChange | Log2 fold change between conditions |
lfcSE | Standard error of LFC |
stat | Wald test statistic |
pvalue | Raw p-value |
padj | BH-adjusted p-value (use this for significance) |
LFC Shrinkage
ds.lfc_shrink()
Use shrunk LFC for visualization and ranking only. P-values are unchanged.
Common Pitfalls
-
Data orientation: Count matrices often load as genes × samples. You MUST transpose to samples × genes with .T.
-
Index mismatch: Sample names in counts and metadata must match exactly.
common = counts_df.index.intersection(metadata.index)
counts_df = counts_df.loc[common]
metadata = metadata.loc[common]
-
Non-integer counts: PyDESeq2 requires raw counts. Do NOT pass TPM, FPKM, or normalized values.
-
Design not full rank: Happens when variables are confounded. Check with:
pd.crosstab(metadata.condition, metadata.batch)
-
Continuous covariates: Ensure numeric type:
metadata["age"] = pd.to_numeric(metadata["age"])
-
Use padj, not pvalue: Always filter by adjusted p-value for multiple testing correction.
Filtering Results
sig = results[results.padj < 0.05]
sig_large = results[(results.padj < 0.05) & (abs(results.log2FoldChange) > 1)]
up = sig[sig.log2FoldChange > 0]
down = sig[sig.log2FoldChange < 0]