271 lines
8.5 KiB
Python
Executable File
271 lines
8.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
表达分析Pipeline
|
|
整合差异表达分析 + 功能注释
|
|
自动适配小/大家族
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import pandas as pd
|
|
import numpy as np
|
|
from collections import defaultdict
|
|
from dataclasses import dataclass, field
|
|
from typing import List, Dict, Optional, Tuple
|
|
from pathlib import Path
|
|
|
|
|
|
@dataclass
|
|
class ExpressionConfig:
|
|
"""表达分析配置"""
|
|
fpkm_file: str = ""
|
|
sample_file: str = ""
|
|
gene_list: str = ""
|
|
output_prefix: str = "expression"
|
|
r_script: str = "scripts/draw_expression.R"
|
|
|
|
# 差异表达参数
|
|
min_log2fc: float = 1.0 # 最小log2 fold change
|
|
min_expression: float = 1.0 # 最小表达量
|
|
|
|
# 功能注释
|
|
gene2go_file: str = ""
|
|
|
|
|
|
@dataclass
|
|
class ExpressionResult:
|
|
"""表达分析结果"""
|
|
exp_file: str # 表达量矩阵
|
|
diff_genes_x3: List[str] = field(default_factory=list) # X3差异基因
|
|
diff_genes_x4: List[str] = field(default_factory=list) # X4差异基因
|
|
all_diff_genes: List[str] = field(default_factory=list) # 所有差异基因
|
|
gene2go_file: str = ""
|
|
plots: List[str] = field(default_factory=list)
|
|
|
|
# 统计
|
|
total_genes: int = 0
|
|
diff_count_x3: int = 0
|
|
diff_count_x4: int = 0
|
|
|
|
|
|
def run_r_expression(config: ExpressionConfig) -> ExpressionResult:
|
|
"""运行R脚本进行差异表达分析"""
|
|
# 准备基因列表(去.T01后缀)
|
|
if config.gene_list:
|
|
gene_list_new = config.gene_list + "_new"
|
|
with open(config.gene_list) as f:
|
|
genes = [g.strip() for g in f if g.strip()]
|
|
genes_new = [g.replace('.T01', '') for g in genes]
|
|
with open(gene_list_new, 'w') as f:
|
|
f.write('\n'.join(genes_new))
|
|
config.gene_list = gene_list_new
|
|
|
|
# 运行R脚本
|
|
cmd = [
|
|
"Rscript", config.r_script,
|
|
config.fpkm_file,
|
|
config.gene_list,
|
|
config.sample_file,
|
|
config.output_prefix
|
|
]
|
|
|
|
# 确保使用正确的R
|
|
r_exe = "/home/xzq/miniconda3/envs/rnaR/bin/Rscript"
|
|
if os.path.exists(r_exe):
|
|
cmd[0] = r_exe
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.path.dirname(config.fpkm_file) or '.')
|
|
|
|
if result.returncode != 0:
|
|
print(f"R脚本警告: {result.stderr}")
|
|
|
|
# 读取结果文件
|
|
exp_file = config.output_prefix + ".gene-exp.txt"
|
|
if not os.path.exists(exp_file):
|
|
exp_file = os.path.join(os.path.dirname(config.fpkm_file), config.output_prefix + ".gene-exp.txt")
|
|
|
|
return exp_file
|
|
|
|
|
|
def find_differential_genes(exp_file: str, config: ExpressionConfig) -> ExpressionResult:
|
|
"""筛选差异表达基因"""
|
|
# 读取表达量
|
|
if not os.path.exists(exp_file):
|
|
raise FileNotFoundError(f"表达量文件不存在: {exp_file}")
|
|
|
|
exp = pd.read_csv(exp_file, sep='\t', index_col=0)
|
|
|
|
# 计算各组平均表达量
|
|
sample_info = {}
|
|
with open(config.sample_file) as f:
|
|
for line in f:
|
|
parts = line.strip().split('\t')
|
|
if len(parts) >= 2:
|
|
sample_info[parts[0]] = parts[1]
|
|
|
|
# 识别对照组和处理组
|
|
groups = set(sample_info.values())
|
|
if len(groups) < 2:
|
|
raise ValueError("样本分组不足")
|
|
|
|
control = min(groups) # 假设字母序最小的是对照
|
|
treatments = [g for g in groups if g != control]
|
|
|
|
# 计算平均表达量
|
|
for group in groups:
|
|
cols = [c for c in exp.columns if sample_info.get(c, '').startswith(group)]
|
|
if cols:
|
|
exp[f'{group}_mean'] = exp[cols].mean(axis=1)
|
|
|
|
# 计算log2FC
|
|
for treat in treatments:
|
|
if f'{treat}_mean' in exp.columns:
|
|
exp[f'log2FC_{treat}vs{control}'] = np.log2((exp[f'{treat}_mean'] + 0.1) / (exp[f'{control}_mean'] + 0.1))
|
|
|
|
# 筛选差异基因
|
|
diff_x3 = set()
|
|
diff_x4 = set()
|
|
|
|
if 'log2FC_X3vsX2' in exp.columns:
|
|
diff_x3 = set(exp[abs(exp['log2FC_X3vsX2']) >= config.min_log2fc].index)
|
|
if 'log2FC_X4vsX2' in exp.columns:
|
|
diff_x4 = set(exp[abs(exp['log2FC_X4vsX2']) >= config.min_log2fc].index)
|
|
|
|
all_diff = diff_x3 | diff_x4
|
|
|
|
# 过滤低表达基因
|
|
exp = exp[exp[[c for c in exp.columns if '_mean' in c]].max(axis=1) >= config.min_expression]
|
|
|
|
result = ExpressionResult(
|
|
exp_file=exp_file,
|
|
diff_genes_x3=sorted(diff_x3),
|
|
diff_genes_x4=sorted(diff_x4),
|
|
all_diff_genes=sorted(all_diff),
|
|
total_genes=len(exp),
|
|
diff_count_x3=len(diff_x3),
|
|
diff_count_x4=len(diff_x4)
|
|
)
|
|
|
|
# 查找生成的图片
|
|
out_dir = os.path.dirname(exp_file) or '.'
|
|
result.plots = [
|
|
f for f in os.listdir(out_dir)
|
|
if f.startswith(config.output_prefix) and f.endswith('.pdf')
|
|
]
|
|
|
|
return result
|
|
|
|
|
|
def add_functional_annotation(result: ExpressionResult, gene2go_file: str) -> ExpressionResult:
|
|
"""添加功能注释"""
|
|
result.gene2go_file = gene2go_file
|
|
|
|
if not os.path.exists(gene2go_file):
|
|
return result
|
|
|
|
# 读取GO注释
|
|
gene2go = defaultdict(list)
|
|
with open(gene2go_file) as f:
|
|
for line in f:
|
|
gene, go = line.strip().split('\t')
|
|
gene2go[gene].append(go)
|
|
|
|
result.gene2go = dict(gene2go)
|
|
return result
|
|
|
|
|
|
def generate_summary_report(result: ExpressionResult, output_file: str):
|
|
"""生成分析报告"""
|
|
with open(output_file, 'w') as f:
|
|
f.write("=" * 60 + "\n")
|
|
f.write("基因家族表达分析报告\n")
|
|
f.write("=" * 60 + "\n\n")
|
|
|
|
f.write(f"总基因数: {result.total_genes}\n")
|
|
f.write(f"差异基因 (X3 vs X2): {result.diff_count_x3}\n")
|
|
f.write(f"差异基因 (X4 vs X2): {result.diff_count_x4}\n\n")
|
|
|
|
if result.diff_genes_x3:
|
|
f.write("差异基因列表 (X3 vs X2):\n")
|
|
for g in result.diff_genes_x3:
|
|
f.write(f" {g}\n")
|
|
f.write("\n")
|
|
|
|
if result.diff_genes_x4:
|
|
f.write("差异基因列表 (X4 vs X2):\n")
|
|
for g in result.diff_genes_x4:
|
|
f.write(f" {g}\n")
|
|
f.write("\n")
|
|
|
|
# 功能注释统计
|
|
if hasattr(result, 'gene2go') and result.gene2go:
|
|
f.write("功能注释统计:\n")
|
|
diff_with_go = sum(1 for g in result.all_diff_genes if g in result.gene2go)
|
|
f.write(f" 差异基因有GO注释: {diff_with_go}/{len(result.all_diff_genes)}\n")
|
|
|
|
if result.plots:
|
|
f.write("\n生成的图表:\n")
|
|
for p in result.plots:
|
|
f.write(f" - {p}\n")
|
|
|
|
print(f"报告已保存: {output_file}")
|
|
|
|
|
|
def run_expression_pipeline(
|
|
fpkm_file: str,
|
|
sample_file: str,
|
|
gene_list: str,
|
|
gene2go_file: str = "",
|
|
output_dir: str = ".",
|
|
output_prefix: str = "expression"
|
|
) -> ExpressionResult:
|
|
"""
|
|
运行完整的表达分析Pipeline
|
|
"""
|
|
config = ExpressionConfig(
|
|
fpkm_file=fpkm_file,
|
|
sample_file=sample_file,
|
|
gene_list=gene_list,
|
|
output_prefix=output_prefix
|
|
)
|
|
|
|
# 1. 运行R差异表达分析
|
|
print("[表达] 运行差异表达分析...")
|
|
exp_file = run_r_expression(config)
|
|
|
|
# 2. 筛选差异基因
|
|
print("[表达] 筛选差异基因...")
|
|
result = find_differential_genes(exp_file, config)
|
|
|
|
# 3. 添加功能注释
|
|
if gene2go_file and os.path.exists(gene2go_file):
|
|
print("[表达] 添加功能注释...")
|
|
result = add_functional_annotation(result, gene2go_file)
|
|
|
|
# 4. 生成报告
|
|
report_file = os.path.join(output_dir, f"{output_prefix}_report.txt")
|
|
generate_summary_report(result, report_file)
|
|
|
|
print(f"\n=== 分析完成 ===")
|
|
print(f"总基因: {result.total_genes}")
|
|
print(f"差异基因 X3vsX2: {result.diff_count_x3}")
|
|
print(f"差异基因 X4vsX2: {result.diff_count_x4}")
|
|
|
|
return result
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
parser = argparse.ArgumentParser(description='表达分析Pipeline')
|
|
parser.add_argument('--fpkm', required=True, help='FPKM表达量文件')
|
|
parser.add_argument('--sample', required=True, help='样本分组文件')
|
|
parser.add_argument('--genes', required=True, help='基因ID列表')
|
|
parser.add_argument('--gene2go', default='', help='GO注释文件')
|
|
parser.add_argument('--output-dir', default='.', help='输出目录')
|
|
parser.add_argument('--prefix', default='expression', help='输出前缀')
|
|
args = parser.parse_args()
|
|
|
|
run_expression_pipeline(
|
|
args.fpkm, args.sample, args.genes, args.gene2go,
|
|
args.output_dir, args.prefix
|
|
) |