mcpField
P
pydeseq2-differential-expression

pydeseq2-differential-expression

github.com/jaechang-hits数据与数据库38440 次安装6 天前更新
代码仓库

关于 pydeseq2-differential-expression

pydeseq2-differential-expression 是指导使用 PyDESeq2 分析 bulk RNA-seq 计数数据的技能,面向需要在 Python 流程中识别差异表达基因的研究者。它涵盖从数据校验、模型拟合和统计检验到结果导出与可视化准备的步骤。

主要功能

  1. 01校验计数矩阵与样本元数据
  2. 02支持多因素设计及批次协变量
  3. 03执行 Wald 检验与 FDR 校正
  4. 04可选 log2 倍数变化收缩
  5. 05筛选并导出差异表达结果
  6. 06准备火山图和 MA 图数据

适用场景

  1. 01比较处理组与对照组的基因表达
  2. 02在分析中纳入批次或协变量
  3. 03从单个模型执行多个组间对比
  4. 04将 R DESeq2 工作流迁移到 Python

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill pydeseq2-differential-expression

安装后在 Claude Code 中输入/pydeseq2-differential-expression确认已加载。

详细介绍

PyDESeq2 差异表达分析

概述

PyDESeq2 是 R DESeq2 软件包的 Python 重实现,用于分析 bulk RNA-seq 计数数据中的差异基因表达。它为每个基因拟合负二项广义线性模型,使用经验贝叶斯收缩估计离散度,并通过 Benjamini-Hochberg FDR 校正执行 Wald 检验。本技能涵盖从原始计数到可用于发表的结果表和可视化的完整流程。

适用场景

  • 根据 bulk RNA-seq 数据,识别两个或更多实验条件之间的差异表达基因
  • 使用适当的统计检验进行两组比较(例如,处理组与对照组)
  • 运行多因素设计,纳入批次效应或协变量(例如,~batch + condition)
  • 应用 log2 倍数变化收缩(apeGLM),用于排序和可视化
  • DE 后使用 omics-plotting SKILL 为差异表达结果绘制适用于发表的图表
  • 将基于 R 的 DESeq2 工作流转换为纯 Python 环境
  • 将差异表达分析整合到更大的 Python 生物信息学流程中(例如,与 scanpy、pandas 配合)
  • 如需使用方法支持最广泛且经过社区验证的 R 参考实现,请改用 DESeq2(R/Bioconductor)或 edgeR

前提条件

  • Python 软件包:pydeseq2>=0.4、pandas>=1.4、numpy>=1.23、scipy>=1.11、scikit-learn>=1.1、anndata>=0.8
  • 数据要求:原始(未标准化)整数计数矩阵(样本 x 基因)+ 样本元数据 DataFrame
  • 环境:Python 3.10+;可选的可视化软件包 matplotlib、seaborn
pip install pydeseq2 matplotlib seaborn

预先确认

编写任何分析代码前,先与用户确认以下事项。

decisions:
  - id: D1
    param: design
    kind: required
    source: data
    ask: "Which column of the sample sheet separates the groups you want to compare?"
    default: null

  - id: D2
    param: contrast
    kind: required
    source: user
    depends_on: [D1]
    ask: "Within that column, which group is the baseline the others are measured against?"
    default: null

  - id: D3
    param: design
    kind: required
    source: data
    depends_on: [D1]
    ask: "Is there a nuisance variable to adjust for (batch, donor, sex, sequencing run)?"
    default: "none - compare groups without covariates"
    skip_if: "sample sheet carries no column besides the grouping variable"

  - id: D4
    param: alpha
    kind: optional
    source: user
    ask: "How strong must the evidence be before a gene counts as changed (false-discovery rate)?"
    default: 0.05

  - id: D5
    param: lfc_cutoff
    kind: optional
    source: user
    ask: "How large a fold change is worth reporting, on top of the statistical call?"
    default: "|log2FC| > 0.58 (1.5x change)"

  - id: D6
    param: min_total_counts
    kind: optional_conditional
    source: data
    ask: "Genes with very few reads cannot be tested - where should the floor sit?"
    default: 10

  - id: D7
    param: lfc_shrink
    kind: optional
    source: user
    ask: "Shrink fold-change estimates so low-count genes stop dominating the rankings and plots?"
    default: "applied for ranking and visualization, not for significance calls"

  - id: D8
    param: refit_cooks, cooks_filter, independent_filter
    kind: optional
    source: user
    ask: "Should DESeq2 retain its default outlier replacement and adaptive filtering behavior?"
    default: true

  - id: D9
    param: n_cpus
    kind: never_ask
    source: data
    reason: "Affects runtime only, not the result"
    default: 4

design 会出现两次,因为公式由 D1 和 D3 共同组装; 随后 D2 会选择要对拟合模型的哪个系数进行检验。

工作流

步骤 1:加载和验证数据

加载计数矩阵和元数据。PyDESeq2 要求计数数据为样本 x 基因的 DataFrame,且包含非负整数;元数据则为样本 x 变量的 DataFrame,并且索引必须匹配。

import pandas as pd

# Load data — typical CSV has genes as rows, samples as columns
counts_raw = pd.read_csv("counts.csv", index_col=0)
metadata = pd.read_csv("metadata.csv", index_col=0)

# Transpose if needed: PyDESeq2 requires samples x genes
if counts_raw.shape[0] > counts_raw.shape[1]:
    counts_df = counts_raw.T  # genes x samples → samples x genes
else:
    counts_df = counts_raw

# Validate alignment
common_samples = counts_df.index.intersection(metadata.index)
counts_df = counts_df.loc[common_samples]
metadata = metadata.loc[common_samples]

print(f"Samples: {counts_df.shape[0]}, Genes: {counts_df.shape[1]}")
print(f"Metadata columns: {list(metadata.columns)}")
print(f"Condition counts:\n{metadata['condition'].value_counts()}")
步骤 2:基因过滤

移除低表达基因,以提升统计效能并减少多重检验负担。

# Filter genes with total counts below threshold
min_total_counts = 10
gene_counts = counts_df.sum(axis=0)
genes_to_keep = gene_counts[gene_counts >= min_total_counts].index
counts_df = counts_df[genes_to_keep]

# Optional: require minimum counts in a minimum number of samples
min_count_per_sample = 5
min_samples = 3
genes_expressed = (counts_df >= min_count_per_sample).sum(axis=0) >= min_samples
counts_df = counts_df.loc[:, genes_expressed]

print(f"Genes after filtering: {counts_df.shape[1]}")
步骤 3:初始化和拟合 DeseqDataSet

创建 DESeq 数据集对象,指定设计公式,并运行完整流程(估计大小因子、估计离散度、拟合模型)。

from pydeseq2.dds import DeseqDataSet

dds = DeseqDataSet(
    counts=counts_df,
    metadata=metadata,
    design="~condition",   # Wilkinson-style formula
    refit_cooks=True,      # Refit after Cook's outlier removal
    n_cpus=4               # Parallel threads
)

# Run: size factors → dispersions → trend → MAP shrinkage → LFC fitting
dds.deseq2()

# Inspect normalization
print(f"Size factors (first 5): {dds.obsm['size_factors'][:5]}")
print(f"Size factor range: {dds.obsm['size_factors'].min():.2f} - {dds.obsm['size_factors'].max():.2f}")
步骤 4:统计检验(Wald 检验)

执行 Wald 检验以识别差异表达基因。将 contrast 指定为 [variable, test_level, reference_level]。

from pydeseq2.ds import DeseqStats

ds = DeseqStats(
    dds,
    contrast=["condition", "treated", "control"],
    alpha=0.05,              # FDR threshold
    cooks_filter=True,       # Filter Cook's outliers
    independent_filter=True  # Independent filtering for power
)

ds.summary()

# Access full results
results = ds.results_df
print(f"Total genes tested: {len(results)}")
print(f"Significant (padj < 0.05): {(results.padj < 0.05).sum()}")
步骤 5:LFC 收缩(可选)

应用 apeGLM 收缩,以降低 log2 倍数变化估计中的噪声。收缩后的数值用于可视化和排序,不用于判定显著性。

# Apply shrinkage — modifies results_df.log2FoldChange in place
ds.lfc_shrink()

# Compare pre/post shrinkage effect
print(f"Max |LFC| after shrinkage: {results.log2FoldChange.abs().max():.2f}")
print(f"Genes with |LFC| > 2: {(results.log2FoldChange.abs() > 2).sum()}")
步骤 6:筛选和导出结果

筛选显著基因,并导出结果以供下游分析。

import numpy as np

# Significance + effect size filter
significant = results[
    (results.padj < 0.05) &
    (results.log2FoldChange.abs() > 1.0)
].copy()

# Separate up/down-regulated
up = significant[significant.log2FoldChange > 0].sort_values("padj")
down = significant[significant.log2FoldChange < 0].sort_values("padj")

print(f"Upregulated: {len(up)}, Downregulated: {len(down)}")

# Export
results.to_csv("deseq2_all_results.csv")
significant.to_csv("deseq2_significant.csv")
up.to_csv("deseq2_upregulated.csv")
down.to_csv("deseq2_downregulated.csv")
print("Results exported to CSV files")
步骤 7:可视化 — 火山图

准备 DEG 表格,然后阅读 skills/data-visualization/omics-plotting/SKILL.md 并按照其中的“Volcano”配方,对导出的 CSV 作图(→ figures/volcano_plot.png)。

# Volcano input = log2FoldChange vs padj; saved in Step 6 as deseq2_all_results.csv
volcano_input = results[["log2FoldChange", "padj"]].dropna()
print(f"Volcano input: {len(volcano_input)} genes -> deseq2_all_results.csv")
# Render with the omics-plotting SKILL (`skills/data-visualization/omics-plotting/SKILL.md`) "Volcano" recipe.
步骤 8:可视化 — MA 图

准备 DEG 表格,然后阅读 skills/data-visualization/omics-plotting/SKILL.md 并按照其中的“MA plot”配方,对导出的 CSV 作图(→ figures/ma_plot.png)。

# MA input = log2FoldChange vs mean normalized count (baseMean); from deseq2_all_results.csv
ma_input = results[["baseMean", "log2FoldChange", "padj"]].dropna(subset=["padj"])
print(f"MA input: {len(ma_input)} genes -> deseq2_all_results.csv")
# Render with the omics-plotting SKILL (`skills/data-visualization/omics-plotting/SKILL.md`) "MA plot" recipe.

关键参数

参数默认值范围 / 选项作用
design(必填)Wilkinson 公式模型公式;将协变量放在关注变量之前
contrastNone[var, test, ref]要检验的比较;None 使用最后一个系数
alpha0.050.01–0.10显著性判定的 FDR 阈值
refit_cooksTrueTrue/False移除 Cook 距离离群值后重新拟合模型
cooks_filterTrueTrue/False检验时应用 Cook 距离筛选
independent_filterTrueTrue/False通过独立筛选优化检测效能
n_cpus11–N拟合离散度时使用的并行线程数
min_total_counts(用户)105–50基因筛选:所有样本中的最小总 reads 数
lfc_shrink()关闭在 summary() 后调用apeGLM 收缩;降低 LFC 估计中的噪声

常见用法

用法:从一个模型进行多重对比

比较多个处理组与同一个对照组时使用。

dds = DeseqDataSet(counts=counts_df, metadata=metadata, design="~condition")
dds.deseq2()

contrasts = {
    "A_vs_ctrl": ["condition", "treatment_A", "control"],
    "B_vs_ctrl": ["condition", "treatment_B", "control"],
    "C_vs_ctrl": ["condition", "treatment_C", "control"],
}

for name, contrast in contrasts.items():
    ds = DeseqStats(dds, contrast=contrast, alpha=0.05)
    ds.summary()
    n_sig = (ds.results_df.padj < 0.05).sum()
    ds.results_df.to_csv(f"results_{name}.csv")
    print(f"{name}: {n_sig} significant genes")
用法:批次校正分析

样本来自多个批次或测序运行时使用。

# Verify batch is not confounded with condition
print(pd.crosstab(metadata["batch"], metadata["condition"]))

dds = DeseqDataSet(
    counts=counts_df, metadata=metadata,
    design="~batch + condition"  # batch first, then variable of interest
)
dds.deseq2()

ds = DeseqStats(dds, contrast=["condition", "treated", "control"])
ds.summary()
print(f"Significant genes (batch-corrected): {(ds.results_df.padj < 0.05).sum()}")
用法:P 值分布质控

诊断检查——健康的分析会显示平坦的直方图,并在 0 附近出现峰值。

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# P-value histogram
axes[0].hist(ds.results_df.pvalue.dropna(), bins=50, edgecolor="black")
axes[0].set_xlabel("P-value")
axes[0].set_ylabel("Frequency")
axes[0].set_title("P-value Distribution")

# Dispersion plot
axes[1].scatter(
    np.log10(dds.varm["_normed_means"] + 1),
    np.log10(dds.varm["dispersions"]),
    alpha=0.3, s=5
)
axes[1].set_xlabel("Log10(Mean Expression + 1)")
axes[1].set_ylabel("Log10(Dispersion)")
axes[1].set_title("Dispersion vs Mean")

plt.tight_layout()
plt.savefig("qc_diagnostics.png", dpi=300)
print("Saved qc_diagnostics.png")
用法:保存和重新载入 DESeq 数据集

用于恢复分析,而无需重新执行耗时的拟合步骤。

import pickle

# Save
with open("dds_fitted.pkl", "wb") as f:
    pickle.dump(dds.to_picklable_anndata(), f)
print("Saved fitted DESeqDataSet")

# Reload
with open("dds_fitted.pkl", "rb") as f:
    adata = pickle.load(f)
# Note: reload requires re-constructing DeseqDataSet from the AnnData

预期输出

  • deseq2_all_results.csv — 所有受检基因的完整结果表(baseMean、log2FoldChange、lfcSE、stat、pvalue、padj)
  • deseq2_significant.csv — 筛选后的结果(padj < 0.05 且 |LFC| > 1)
  • deseq2_upregulated.csv — 按 padj 排序的显著上调基因
  • deseq2_downregulated.csv — 按 padj 排序的显著下调基因
  • figures/volcano_plot.png — 显示显著性和倍数变化阈值的火山图
  • figures/ma_plot.png — 显示倍数变化与平均表达量关系的 MA 图
  • figures/qc_diagnostics.png — P 值分布和离散度图

故障排除

问题原因解决方案
ValueError: Index mismatchcounts 与 metadata 中的样本名称不同使用 counts_df.index.intersection(metadata.index) 进行对齐
所有基因的 padj = NaN基因方差为零或计数全为零应用更严格的基因筛选(提高 min_total_counts)
Design matrix is not full rank变量混杂(例如,所有处理组样本都在同一个批次)使用 pd.crosstab() 检查;简化设计或移除混杂变量
未发现显著基因效应量小、变异性高或样本量少检查 P 值分布;放宽 alpha 或
拟合时出现 MemoryError基因过多或数据集过大更严格地进行预筛选;减少 n_cpus;使用 RAM 更多的计算机
size factor 非常大(>5)文库大小差异极端确认原始计数未经过归一化;检查是否存在污染或文库失败
收缩后 LFC 异常在 summary() 前调用 lfc_shrink()始终先调用 ds.summary(),然后调用 ds.lfc_shrink()

参考资料

评论

支持 Markdown