Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Differential gene expression analysis for bulk RNA-seq with PyDESeq2, including formulaic designs, Wald tests, FDR correction, LFC shrinkage, and result visualization.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 102% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 327% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 151% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 51% | 0% |
PyDESeq2 is a Python implementation of DESeq2 for differential expression analysis with bulk RNA-seq data. Design and execute complete workflows from data loading through result interpretation, including formulaic single-factor and multi-factor designs, Wald tests with multiple testing correction, optional apeGLM shrinkage, and integration with pandas and AnnData.
This skill should be used when:
For users who want to perform a standard differential expression analysis:
pythonimport pandas as pd from pydeseq2.dds import DeseqDataSet from pydeseq2.default_inference import DefaultInference from pydeseq2.ds import DeseqStats # 1. Load data counts_df = pd.read_csv("counts.csv", index_col=0).T # Transpose to samples × genes metadata = pd.read_csv("metadata.csv", index_col=0) # 2. Filter low-count genes genes_to_keep = counts_df.columns[counts_df.sum(axis=0) >= 10] counts_df = counts_df[genes_to_keep] # 3. Make the reference level explicit and fit DESeq2 metadata["condition"] = pd.Categorical( metadata["condition"], categories=["control", "treated"] ) inference = DefaultInference(n_cpus=4) dds = DeseqDataSet( counts=counts_df, metadata=metadata, design="~condition", refit_cooks=True, inference=inference, ) dds.deseq2() # 4. Perform statistical testing ds = DeseqStats( dds, contrast=["condition", "treated", "control"], inference=inference, ) ds.summary() # 5. Access results results = ds.results_df significant = results[results.padj < 0.05] print(f"Found {len(significant)} significant genes")
The six steps, with code, are in references/core_workflow_steps.md:
and matching metadata. Never feed normalized or transformed values to DESeq2.
Multi-factor designs, contrasts, and interaction terms are in references/analysis_patterns.md.
This skill includes a complete command-line script for standard analyses:
bash# Basic usage python scripts/run_deseq2_analysis.py \ --counts counts.csv \ --metadata metadata.csv \ --design "~condition" \ --contrast condition treated control \ --output results/ # With additional options python scripts/run_deseq2_analysis.py \ --counts counts.csv \ --metadata metadata.csv \ --design "~batch + condition" \ --contrast condition treated control \ --output results/ \ --min-counts 10 \ --alpha 0.05 \ --n-cpus 4 \ --shrink-coeff "condition[T.treated]" \ --plots
Script features:
Refer users to scripts/run_deseq2_analysis.py when they need a standalone analysis tool or want to batch process multiple datasets.
python# Filter by adjusted p-value significant = ds.results_df[ds.results_df.padj < 0.05] # Filter by both significance and effect size sig_and_large = ds.results_df[ (ds.results_df.padj < 0.05) & (abs(ds.results_df.log2FoldChange) > 1) ] # Separate up- and down-regulated upregulated = significant[significant.log2FoldChange > 0] downregulated = significant[significant.log2FoldChange < 0] print(f"Upregulated: {len(upregulated)}") print(f"Downregulated: {len(downregulated)}")
python# Sort by adjusted p-value top_by_padj = ds.results_df.sort_values("padj").head(20) # Sort by absolute fold change (use shrunk values) ds.lfc_shrink(coeff="condition[T.treated]") ds.results_df["abs_lfc"] = abs(ds.results_df.log2FoldChange) top_by_lfc = ds.results_df.sort_values("abs_lfc", ascending=False).head(20) # Sort by a combined metric ds.results_df["score"] = -np.log10(ds.results_df.padj) * abs(ds.results_df.log2FoldChange) top_combined = ds.results_df.sort_values("score", ascending=False).head(20)
python# Check normalization (size factors should be close to 1) print("Size factors:", dds.obs["size_factors"]) # Examine dispersion estimates import matplotlib.pyplot as plt plt.hist(dds.var["dispersions"], bins=50) plt.xlabel("Dispersion") plt.ylabel("Frequency") plt.title("Dispersion Distribution") plt.show() # Check p-value distribution (should be mostly flat with peak near 0) plt.hist(ds.results_df.pvalue.dropna(), bins=50) plt.xlabel("P-value") plt.ylabel("Frequency") plt.title("P-value Distribution") plt.show()
Visualize significance vs effect size:
pythonimport matplotlib.pyplot as plt import numpy as np results = ds.results_df.copy() results["-log10(padj)"] = -np.log10(results.padj) plt.figure(figsize=(10, 6)) significant = results.padj < 0.05 plt.scatter( results.loc[~significant, "log2FoldChange"], results.loc[~significant, "-log10(padj)"], alpha=0.3, s=10, c='gray', label='Not significant' ) plt.scatter( results.loc[significant, "log2FoldChange"], results.loc[significant, "-log10(padj)"], alpha=0.6, s=10, c='red', label='padj < 0.05' ) plt.axhline(-np.log10(0.05), color='blue', linestyle='--', alpha=0.5) plt.xlabel("Log2 Fold Change") plt.ylabel("-Log10(Adjusted P-value)") plt.title("Volcano Plot") plt.legend() plt.savefig("volcano_plot.png", dpi=300)
Show fold change vs mean expression:
pythonplt.figure(figsize=(10, 6)) plt.scatter( np.log10(results.loc[~significant, "baseMean"] + 1), results.loc[~significant, "log2FoldChange"], alpha=0.3, s=10, c='gray' ) plt.scatter( np.log10(results.loc[significant, "baseMean"] + 1), results.loc[significant, "log2FoldChange"], alpha=0.6, s=10, c='red' ) plt.axhline(0, color='blue', linestyle='--', alpha=0.5) plt.xlabel("Log10(Base Mean + 1)") plt.ylabel("Log2 Fold Change") plt.title("MA Plot") plt.savefig("ma_plot.png", dpi=300)
Issue: "Index mismatch between counts and metadata"
Solution: Ensure sample names match exactly
pythonprint("Counts samples:", counts_df.index.tolist()) print("Metadata samples:", metadata.index.tolist()) # Take intersection if needed common = counts_df.index.intersection(metadata.index) counts_df = counts_df.loc[common] metadata = metadata.loc[common]
Issue: "All genes have zero counts"
Solution: Check if data needs transposition
pythonprint(f"Counts shape: {counts_df.shape}") # If genes > samples, transpose is needed if counts_df.shape[1] < counts_df.shape[0]: counts_df = counts_df.T
Issue: "Design matrix is not full rank"
Cause: Confounded variables (e.g., all treated samples in one batch)
Solution: Remove confounded variable or add interaction term
python# Check confounding print(pd.crosstab(metadata.condition, metadata.batch)) # Either simplify design or add interaction design = "~condition" # Remove batch # OR design = "~condition + batch + condition:batch" # Model interaction
Diagnostics:
python# Check dispersion distribution plt.hist(dds.var["dispersions"], bins=50) plt.show() # Check size factors print(dds.obs["size_factors"]) # Look at top genes by raw p-value print(ds.results_df.nsmallest(20, "pvalue"))
Possible causes:
For comprehensive details beyond this workflow-oriented guide:
references/api_reference.md): Complete documentation of PyDESeq2 classes, methods, and data structures. Use when needing detailed parameter information or understanding object attributes.references/workflow_guide.md): In-depth guide covering complete analysis workflows, data loading patterns, multi-factor designs, troubleshooting, and best practices. Use when handling complex experimental designs or encountering issues.Load these references into context when users need:
Read references/api_reference.mdRead references/workflow_guide.mdRead references/workflow_guide.md (see Troubleshooting section).T if needed."~batch + condition" not "~condition + batch").padj < 0.05 for significance, not raw p-values. The Benjamini-Hochberg procedure controls false discovery rate.[variable, test_level, reference_level] where test_level is compared against reference_level.dds.to_picklable_anndata().write_h5ad("dds_result.h5ad") for portable outputs. Only load pickle files that you created yourself and trust.bashuv pip install pydeseq2==0.5.4
System requirements:
Optional for visualization:
Other measured skills in the registry, with their headline benchmark lift.