You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化Python生物信息学脚本中的__getitem__方法以提升性能

问题描述

我是一名刚接触编程的生物学家,正在编写Python生物信息学脚本,用于遍历多条序列、查找特定位置的氨基酸并计算其在基因组数据库中的频率。但脚本优化不足,处理普通文件需半分钟,大文件耗时更久。经性能分析发现,Bio.Seq中Seq类的__getitem__函数调用次数过多(达6900万+次)是主要性能瓶颈,尝试过优化但效果甚微,恳请提供可行的优化方案。

原脚本代码

def main(self):
    # Process input data and extract necessary information
    mutation_count, mutation_subjects, mutation_positions, alignment_file, seq_ID = self.process_input()

    # Initialize a dictionary to count amino acids at each mutation position
    aa_counts = {pos: {} for pos in mutation_positions}

    # Read the alignment file using Bio.AlignIO
    alignment = AlignIO.read(alignment_file, 'clustal')
    
    # Find the reference sequence in the alignment
    ref_seq = None
    for record in alignment:
        if record.id == seq_ID:
            ref_seq = record
            break

    # Convert sequences in the alignment to strings for easier processing
    ref_seq_str = str(ref_seq.seq)
    alignment_str = {record.id: str(record.seq) for record in alignment}

    # Count amino acids at each position in the alignment
    for seq in alignment_str.values():
        pos_in_ref_seq = 0
        for pos, aa in enumerate(seq):
            if ref_seq_str[pos] != '-':
                pos_in_ref_seq += 1

            if pos_in_ref_seq in aa_counts:
                aa_counts[pos_in_ref_seq][aa] = aa_counts[pos_in_ref_seq].get(aa, 0) + 1
                    
    # If a specific position is provided, calculate and print the amino acid frequencies at that position
    if self.args.position:
        position = self.args.position
        total_count = sum(aa_counts[position].values())
        for aa, count in aa_counts[position].items():
            freq = (count / total_count * 100) if total_count > 0 else 0
            print(f"Amino Acid: {aa} | Frequency: {freq:.2f}% | Count: {count}")
        return
                
    # Analyze mutations and calculate frequencies
    mutation_info = {}
    for mutation, count in sorted(mutation_count.items(), key=lambda x: int(x[0][1:-1])):
        seq_pos = int(mutation[1:-1])
        query_aa = mutation[0]
        subject_aa = mutation[-1]
        
        if query_aa == '-' or subject_aa == '-':
            continue
        
        real_count = aa_counts[seq_pos].get(subject_aa, 0)
        total_count = sum(aa_counts[seq_pos].values())
        query_aa_frequency = (aa_counts[seq_pos].get(query_aa, 0) / total_count * 100) if total_count > 0 else 0
        subject_aa_frequency = (aa_counts[seq_pos].get(subject_aa, 0) / total_count * 100) if total_count > 0 else 0
        
        if subject_aa_frequency <= 10 and real_count > 2:
            mutation_info[mutation] = {
                'query_aa_frequency': query_aa_frequency,
                'subject_aa_frequency': subject_aa_frequency,
                'real_count': real_count,
            }
    
    # Identify strains with specific mutations
    strains_with_mutations = {}
    for mutation in mutation_count:
        query_aa, pos, subject_aa = mutation[0], int(mutation[1:-1]), mutation[-1]
        strains_with_this_mutation = []

        for record in alignment:
            strain_name = record.id[:3] 
            sequence = str(record.seq)
            
            pos_in_ref_seq = 0
            
            for i, aa in enumerate(sequence):
                if ref_seq.seq[i] != '-':
                    pos_in_ref_seq += 1
                
                if pos_in_ref_seq == pos:
                    
                    if aa == subject_aa:
                        strains_with_this_mutation.append(strain_name[:3])
                    break  
        strains_with_mutations[mutation] = strains_with_this_mutation
            
    # Write mutation information to a text file
    with open('gapslist.txt', 'w') as f:
        for mutation, info in mutation_info.items():
            f.write(f"Mutation: {mutation} | {mutation[0]} Frequency: {info['query_aa_frequency']:.2f}% | {mutation[-1]} Frequency: {info['subject_aa_frequency']:.2f}% | Count: {info['real_count']}\n\n")

    # Write mutation and strain information to a CSV file
    with open('rare_mutations.csv', 'w', newline='') as csvfile:
        fieldnames = ['Mutation', 'Strains']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        
        for mutation, info in mutation_info.items():
            writer.writerow({
                'Mutation': mutation,
                'Strains': ';'.join(strains_with_mutations.get(mutation, []))
            })

性能分析结果

ncalls  tottime  percall  cumtime  percall filename:lineno(function)

 69006091  25.002  0.000     99.135   0.000   Seq.py:470(__getitem__)

 69098925  6.311   0.000     6.311    0.000   SeqRecord.py:334(<lambda>).

 69008225  10.165  0.000     49.965   0.000   <frozen abc>:117(__instancecheck__)

 69006091  10.330  0.000     22.310   0.000   <frozen abc>:121(__subclasscheck__)
优化方案

核心瓶颈来自反复调用Seq.__getitem__和重复遍历序列计算位置映射,以下是针对性优化:

1. 预计算比对位置到参考真实位置的映射

提前生成数组记录每个比对位置对应的参考序列真实位置,避免每次遍历序列时逐位判断gap:

# 替换原代码中ref_seq_str生成后的部分
ref_seq_str = str(ref_seq.seq)
# 生成比对位置→参考真实位置的映射
align_pos_to_ref_pos = []
current_ref_pos = 0
for aa in ref_seq_str:
    if aa != '-':
        current_ref_pos += 1
    align_pos_to_ref_pos.append(current_ref_pos)
# 生成参考真实位置→比对位置的反向映射(仅保留需要统计的位置)
target_ref_positions = set(mutation_positions)
ref_pos_to_align_positions = {pos: [] for pos in target_ref_positions}
for align_pos, ref_pos in enumerate(align_pos_to_ref_pos):
    if ref_pos in target_ref_positions:
        ref_pos_to_align_positions[ref_pos].append(align_pos)

2. 批量统计氨基酸频率,避免逐序列逐位遍历

利用预计算的位置映射,直接针对目标参考位置批量提取氨基酸并统计,结合collections.Counter提升效率:

from collections import Counter

# 替换原代码中的氨基酸统计循环
alignment_str_list = list(alignment_str.values())
for ref_pos, align_positions in ref_pos_to_align_positions.items():
    for align_pos in align_positions:
        # 批量提取所有序列在该比对位置的氨基酸
        aa_list = [seq[align_pos] for seq in alignment_str_list]
        aa_counts[ref_pos] = Counter(aa_list)

3. 提前构建菌株-位置-氨基酸映射,避免重复遍历序列

脚本初期就构建所有菌株在目标参考位置的氨基酸映射,后续查找突变菌株直接查询:

# 在生成alignment_str后添加
strain_pos_aa = {}
for strain_id, seq in alignment_str.items():
    strain_short = strain_id[:3]
    strain_pos_aa[strain_short] = {}
    for ref_pos in target_ref_positions:
        # 参考位置对应唯一比对位置
        align_pos = ref_pos_to_align_positions[ref_pos][0]
        strain_pos_aa[strain_short][ref_pos] = seq[align_pos]

# 替换原代码中的菌株查找循环
strains_with_mutations = {}
for mutation in mutation_count:
    query_aa, pos, subject_aa = mutation[0], int(mutation[1:-1]), mutation[-1]
    strains_with_this_mutation = [
        strain for strain, pos_map in strain_pos_aa.items()
        if pos_map.get(pos) == subject_aa
    ]
    strains_with_mutations[mutation] = strains_with_this_mutation

4. 彻底避免Seq对象的__getitem__调用

确保所有Seq对象在脚本初期就转换为字符串,后续仅操作字符串,移除所有直接访问record.seq或ref_seq.seq的代码。

优化后完整代码示例
from collections import Counter
import csv
from Bio import AlignIO

def main(self):
    # Process input data and extract necessary information
    mutation_count, mutation_subjects, mutation_positions, alignment_file, seq_ID = self.process_input()

    # Initialize a dictionary to count amino acids at each mutation position
    aa_counts = {pos: Counter() for pos in mutation_positions}

    # Read the alignment file using Bio.AlignIO
    alignment = AlignIO.read(alignment_file, 'clustal')
    
    # Find the reference sequence in the alignment
    ref_seq = None
    for record in alignment:
        if record.id == seq_ID:
            ref_seq = record
            break

    # Convert all sequences to strings once and for all
    ref_seq_str = str(ref_seq.seq)
    alignment_str = {record.id: str(record.seq) for record in alignment}
    alignment_str_list = list(alignment_str.values())

    # Precompute alignment position to reference position mapping
    align_pos_to_ref_pos = []
    current_ref_pos = 0
    for aa in ref_seq_str:
        if aa != '-':
            current_ref_pos += 1
        align_pos_to_ref_pos.append(current_ref_pos)

    # Precompute reference position to alignment positions mapping
    target_ref_positions = set(mutation_positions)
    ref_pos_to_align_positions = {pos: [] for pos in target_ref_positions}
    for align_pos, ref_pos in enumerate(align_pos_to_ref_pos):
        if ref_pos in target_ref_positions:
            ref_pos_to_align_positions[ref_pos].append(align_pos)

    # Count amino acids efficiently
    for ref_pos, align_positions in ref_pos_to_align_positions.items():
        for align_pos in align_positions:
            aa_list = [seq[align_pos] for seq in alignment_str_list]
            aa_counts[ref_pos].update(aa_list)

    # If a specific position is provided, calculate and print frequencies
    if self.args.position:
        position = self.args.position
        total_count = sum(aa_counts[position].values())
        for aa, count in aa_counts[position].items():
            freq = (count / total_count * 100) if total_count > 0 else 0
            print(f"Amino Acid: {aa} | Frequency: {freq:.2f}% | Count: {count}")
        return
                
    # Analyze mutations and calculate frequencies
    mutation_info = {}
    for mutation, count in sorted(mutation_count.items(), key=lambda x: int(x[0][1:-1])):
        seq_pos = int(mutation[1:-1])
        query_aa = mutation[0]
        subject_aa = mutation[-1]
        
        if query_aa == '-' or subject_aa == '-':
            continue
        
        total_count = sum(aa_counts[seq_pos].values())
        if total_count == 0:
            continue
        
        query_aa_count = aa_counts[seq_pos].get(query_aa, 0)
        subject_aa_count = aa_counts[seq_pos].get(subject_aa, 0)
        
        query_aa_frequency = (query_aa_count / total_count) * 100
        subject_aa_frequency = (subject_aa_count / total_count) * 100
        
        if subject_aa_frequency <= 10 and subject_aa_count > 2:
            mutation_info[mutation] = {
                'query_aa_frequency': query_aa_frequency,
                'subject_aa_frequency': subject_aa_frequency,
                'real_count': subject_aa_count,
            }
    
    # Prebuild strain to position-amino acid mapping
    strain_pos_aa = {}
    for strain_id, seq in alignment_str.items():
        strain_short = strain_id[:3]
        strain_pos_aa[strain_short] = {}
        for ref_pos in target_ref_positions:
            align_pos = ref_pos_to_align_positions[ref_pos][0]
            strain_pos_aa[strain_short][ref_pos] = seq[align_pos]

    # Identify strains with specific mutations quickly
    strains_with_mutations = {}
    for mutation in mutation_count:
        query_aa, pos, subject_aa = mutation[0], int(mutation[1:-1]), mutation[-1]
        strains_with_this_mutation = [
            strain for strain, pos_map in strain_pos_aa.items()
            if pos_map.get(pos) == subject_aa
        ]
        strains_with_mutations[mutation] = strains_with_this_mutation
            
    # Write mutation information to a text file
    with open('gapslist.txt', 'w') as f:
        for mutation, info in mutation_info.items():
            f.write(f"Mutation: {mutation} | {mutation[0]} Frequency: {info['query_aa_frequency']:.2f}% | {mutation[-1]} Frequency: {info['subject_aa_frequency']:.2f}% | Count: {info['real_count']}\n\n")

    # Write mutation and strain information to a CSV file
    with open('rare_mutations.csv', 'w', newline='') as csvfile:
        fieldnames = ['Mutation', 'Strains']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        
        for mutation, info in mutation_info.items():
            writer.writerow({
                'Mutation': mutation,
                'Strains': ';'.join(strains_with_mutations.get(mutation, []))
            })

内容的提问来源于stack exchange,提问作者edumenotti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 02:44:53