Files

228 lines
7.8 KiB
Python
Raw Permalink Normal View History

2026-08-03 18:42:47 +08:00
#!/home/xzq/miniconda3/envs/test4py311/bin/python
"""
PPI (Protein-Protein Interaction) Analysis v2
支持非模式植物:通过同源基因映射查询模式植物STRING数据库
方案2+3:输出TSV数据 + 生成STRING网页链接
"""
import os
import argparse
import json
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional
import hashlib
import urllib.parse
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(Path(__file__).parent))
from string_api import StringAPI
from gfa_core.mas.effective_members import (
MemberResolutionError,
candidate_member_prefixes,
resolve_effective_members,
)
# 添加conda环境bin目录到PATHCONDA_BIN = "/home/xzq/miniconda3/envs/test4py311/bin"if CONDA_BIN not in os.environ.get("PATH", ""): os.environ["PATH"] = CONDA_BIN + ":" + os.environ.get("PATH", "")
def parse_ortholog_mapping(mapping_file: Path) -> Dict[str, str]:
"""解析同源基因映射文件"""
mapping = {}
with open(mapping_file, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split('\t')
if len(parts) >= 2:
source_id = parts[0].strip()
target_id = parts[1].strip()
target_id_base = target_id.split('.')[0]
mapping[source_id] = target_id_base
return mapping
def generate_string_url(proteins: List[str], species: str = "3702") -> str:
"""生成STRING网页链接"""
base_url = "https://string-db.org/cgi/network"
params = {
"identifiers": "%0d".join(proteins),
"species": species,
"required_score": "400",
"network_type": "functional"
}
return f"{base_url}?{urllib.parse.urlencode(params)}"
def run_ppi_analysis_v2(
gene_ids: List[str],
ortholog_mapping: Optional[Dict[str, str]],
species: str,
output_dir: Path,
prefix: str,
required_score: int = 200
) -> Dict[str, Any]:
"""运行PPI分析"""
result = {
"success": False,
"network_file": None,
"string_url": None,
"stats": {},
"error": None
}
try:
# 映射基因ID
if ortholog_mapping:
mapped_ids = []
for gene_id in gene_ids:
if gene_id in ortholog_mapping:
mapped_ids.append(ortholog_mapping[gene_id])
print(f"[INFO] Mapped {len(mapped_ids)}/{len(gene_ids)} genes")
if not mapped_ids:
result["error"] = "No genes could be mapped"
return result
query_ids = mapped_ids
else:
query_ids = [g.split('.')[0] for g in gene_ids]
print(f"[INFO] Querying STRING for {len(query_ids)} genes...")
api = StringAPI(species=species)
network_data = api.get_network(
proteins=query_ids,
required_score=required_score
)
if isinstance(network_data, dict) and "error" in network_data:
result["error"] = network_data["error"]
return result
if not network_data:
result["error"] = f"No PPI interactions found"
return result
# 生成STRING网页链接
string_url = generate_string_url(query_ids, api.species_id)
result["string_url"] = string_url
print(f"[INFO] STRING URL: {string_url}")
# 保存TSV数据
network_file = output_dir / f"{prefix}.ppi_network.tsv"
with open(network_file, 'w') as f:
f.write("protein1\tprotein2\tcombined_score\n")
for interaction in network_data:
node1 = interaction.get('preferredName_A', '')
node2 = interaction.get('preferredName_B', '')
score = int(interaction.get('score', 0) * 1000)
f.write(f"{node1}\t{node2}\t{score}\n")
result["network_file"] = network_file
stats = {
"total_nodes": len(set([n for i in network_data for n in [i.get('preferredName_A'), i.get('preferredName_B')]])),
"total_edges": len(network_data)
}
result["stats"] = stats
print(f"[INFO] Network: {stats['total_nodes']} nodes, {stats['total_edges']} edges")
result["success"] = True
except Exception as e:
result["error"] = str(e)
print(f"[ERROR] {e}", file=sys.stderr)
return result
def main():
parser = argparse.ArgumentParser(description="PPI Network Analysis v2")
parser.add_argument("-i", "--input", required=True)
parser.add_argument("-s", "--species", default="ath")
parser.add_argument("-o", "--output", required=True)
parser.add_argument("-p", "--prefix", default="ppi")
parser.add_argument("--score", type=int, default=200)
parser.add_argument("--mapping", help="Ortholog mapping file")
args = parser.parse_args()
input_dir = Path(args.input)
output_dir = Path(args.output)
output_dir.mkdir(parents=True, exist_ok=True)
final_dir = output_dir.parent / "final"
final_dir.mkdir(exist_ok=True)
# 读取基因ID
gene_id_file = None
last_resolution_error = ""
for member_prefix in candidate_member_prefixes(final_dir, input_dir, args.prefix):
try:
resolved = resolve_effective_members(
workspace=output_dir.parent,
final_dir=final_dir,
input_dir=input_dir,
prefix=member_prefix,
phase="auto",
require="id",
)
if resolved.path_id:
gene_id_file = resolved.path_id
if resolved.warnings:
print(f"[WARN] {'; '.join(resolved.warnings)}")
print(f"[INFO] Member source: {resolved.source_kind} ({resolved.member_count} IDs)")
break
except MemberResolutionError as exc:
last_resolution_error = str(exc)
if not gene_id_file:
print(f"Error: Gene ID file not found via effective member resolver. {last_resolution_error}", file=sys.stderr)
sys.exit(1)
with open(gene_id_file, 'r') as f:
gene_ids = [line.strip() for line in f if line.strip()]
print(f"[INFO] Loaded {len(gene_ids)} gene IDs")
# 读取同源映射
ortholog_mapping = None
if args.mapping:
mapping_file = Path(args.mapping)
if mapping_file.exists():
ortholog_mapping = parse_ortholog_mapping(mapping_file)
print(f"[INFO] Loaded {len(ortholog_mapping)} mappings")
# 运行分析
result = run_ppi_analysis_v2(
gene_ids=gene_ids,
ortholog_mapping=ortholog_mapping,
species=args.species,
output_dir=final_dir,
prefix=args.prefix,
required_score=args.score
)
if result["success"]:
# 保存 STRING 链接到文件
if result["string_url"]:
url_file = final_dir / f"{args.prefix}.ppi_url.txt"
with open(url_file, 'w') as f:
f.write(f"STRING Protein Interaction Network\n")
f.write(f"{'='*50}\n\n")
f.write(f"View online: {result['string_url']}\n\n")
f.write(f"Network Statistics:\n")
f.write(f" - Nodes: {result['stats']['total_nodes']}\n")
f.write(f" - Edges: {result['stats']['total_edges']}\n")
print(f"[INFO] URL saved: {url_file}")
print(f"[INFO] View online: {result['string_url']}")
print(f"[INFO] Results saved to {final_dir}")
else:
print(f"Error: {result.get('error', 'Unknown')}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()