Claude Skill

alterlab-biopython

Manipulate biological sequences, parse FASTA/GenBank/PDB files, run phylogenetics, and access NCBI/PubMed programmatically via Biopython (Bio.SeqIO, Bio.Entrez, Bio.PDB, Bio.Blast). Use when scripting custom bioinformatics pipelines, batch-processing sequence files, automating BL

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alterlab-ieu-alterlab-academic-skills-skills_bioinformatics_alterlab-biopython-e4836c0.zip · 35 KB
Part of alterlab-ieu/alterlab-academic-skills — 94 skills

Install

skills CLI npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/bioinformatics/alterlab-biopython
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
Git git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Biopython: Computational Molecular Biology in Python

Overview

Biopython is a comprehensive set of freely available Python tools for biological computation. It provides functionality for sequence manipulation, file I/O, database access, structural bioinformatics, phylogenetics, and many other bioinformatics tasks. The current version is Biopython 1.88 (August 2026), which supports Python 3.10–3.14 and requires NumPy.

Removed / changed APIs to watch for

  • Command-line wrappers are gone. Bio.Application and everything built on it — Bio.Blast.Applications (Ncbiblastn/p/x…Commandline, NcbimakeblastdbCommandline) and Bio.Align.Applications (ClustalOmegaCommandline, MuscleCommandline) — were deprecated in 1.78 and removed in 1.86. Call the executables through subprocess (see references/blast.md and references/alignment.md).
  • PairwiseAligner gap scores changed in 1.86. The default gap score is now -1 (was 0), so an aligner left at defaults returns far fewer, non-degenerate alignments. The gap attributes were also renamed to insertion/deletion forms (open_internal_insertion_score, …); the *_gap_score names still work as meta-attributes. The alphabet attribute is deprecated and unused.
  • Bio.pairwise2 is deprecated — use Bio.Align.PairwiseAligner.
  • Bio.Blast.NCBIXML is declared obsolete as of the 1.89 development line in favour of the Bio.Blast parser added in 1.84 (Blast.parse/Blast.read, Blast.qblast). It still ships and works in 1.88; prefer the new API for new code.
  • Security fixes worth upgrading for: 1.87 fixed CVE-2025-68463 in Bio.Entrez.Parser, and 1.88 removed an eval in the Bio.Nexus parser that allowed code execution from a malicious NEXUS file. Treat downloaded records as untrusted input and keep Biopython current.

When to Use This Skill

Use this skill when:

  • Working with biological sequences (DNA, RNA, or protein)
  • Reading, writing, or converting biological file formats (FASTA, GenBank, FASTQ, PDB, mmCIF, etc.)
  • Accessing NCBI databases (GenBank, PubMed, Protein, Gene, etc.) via Entrez
  • Running BLAST searches or parsing BLAST results
  • Performing sequence alignments (pairwise or multiple sequence alignments)
  • Analyzing protein structures from PDB files
  • Creating, manipulating, or visualizing phylogenetic trees
  • Finding sequence motifs or analyzing motif patterns
  • Calculating sequence statistics (GC content, molecular weight, melting temperature, etc.)
  • Performing structural bioinformatics tasks
  • Working with population genetics data
  • Any other computational molecular biology task

Does NOT Trigger

Scenario Use Instead
Running local BLAST+ / makeblastdb from the command line, or DIAMOND alterlab-blast
A one-line lookup of a gene, sequence, or structure alterlab-gget
One call across many web services (UniProt + KEGG + ChEMBL in a pipeline) alterlab-bioservices
SAM/BAM/CRAM record access, pileups, and read filtering alterlab-pysam
Building an ML phylogeny from unaligned sequences (MAFFT + IQ-TREE) alterlab-phylogenetics

Core Capabilities

Biopython is organized into modular sub-packages, each addressing specific bioinformatics domains:

  1. Sequence Handling - Bio.Seq and Bio.SeqIO for sequence manipulation and file I/O
  2. Alignment Analysis - Bio.Align and Bio.AlignIO for pairwise and multiple sequence alignments
  3. Database Access - Bio.Entrez for programmatic access to NCBI databases
  4. BLAST Operations - Bio.Blast for running and parsing BLAST searches
  5. Structural Bioinformatics - Bio.PDB for working with 3D protein structures
  6. Phylogenetics - Bio.Phylo for phylogenetic tree manipulation and visualization
  7. Advanced Features - Motifs, population genetics, sequence utilities, and more

Installation and Setup

Install Biopython (requires Python 3 and NumPy). On this machine, prefer running scripts with uv run:

# Ad-hoc: run a script with Biopython available, no venv to manage
uv run --with biopython script.py

# Or add it to a project
uv add biopython

For NCBI database access, always set your email address (required by NCBI):

from Bio import Entrez
Entrez.email = "your.email@example.com"

# Optional: API key for higher rate limits (10 req/s instead of 3 req/s)
Entrez.api_key = "your_api_key_here"

Using This Skill

This skill provides comprehensive documentation organized by functionality area. When working on a task, consult the relevant reference documentation:

1. Sequence Handling (Bio.Seq & Bio.SeqIO)

Reference: references/sequence_io.md

Use for:

  • Creating and manipulating biological sequences
  • Reading and writing sequence files (FASTA, GenBank, FASTQ, etc.)
  • Converting between file formats
  • Extracting sequences from large files
  • Sequence translation, transcription, and reverse complement
  • Working with SeqRecord objects

Quick example:

from Bio import SeqIO

# Read sequences from FASTA file
for record in SeqIO.parse("sequences.fasta", "fasta"):
    print(f"{record.id}: {len(record.seq)} bp")

# Convert GenBank to FASTA
SeqIO.convert("input.gb", "genbank", "output.fasta", "fasta")

2. Alignment Analysis (Bio.Align & Bio.AlignIO)

Reference: references/alignment.md

Use for:

  • Pairwise sequence alignment (global and local)
  • Reading and writing multiple sequence alignments
  • Using substitution matrices (BLOSUM, PAM)
  • Calculating alignment statistics
  • Customizing alignment parameters

Quick example:

from Bio import Align

# Pairwise alignment
aligner = Align.PairwiseAligner()
aligner.mode = 'global'
alignments = aligner.align("ACCGGT", "ACGGT")
print(alignments[0])

3. Database Access (Bio.Entrez)

Reference: references/databases.md

Use for:

  • Searching NCBI databases (PubMed, GenBank, Protein, Gene, etc.)
  • Downloading sequences and records
  • Fetching publication information
  • Finding related records across databases
  • Batch downloading with proper rate limiting

Quick example:

from Bio import Entrez
Entrez.email = "your.email@example.com"

# Search PubMed
handle = Entrez.esearch(db="pubmed", term="biopython", retmax=10)
results = Entrez.read(handle)
handle.close()
print(f"Found {results['Count']} results")

4. BLAST Operations (Bio.Blast)

Reference: references/blast.md

Use for:

  • Running BLAST searches via NCBI web services
  • Running local BLAST searches
  • Parsing BLAST XML output
  • Filtering results by E-value or identity
  • Extracting hit sequences

Quick example (the Bio.Blast API introduced in 1.84 — NCBI requires a contact email):

from Bio import Blast

Blast.email = "your.email@example.com"
result_stream = Blast.qblast("blastn", "nt", "ATCGATCGATCG")
blast_record = Blast.read(result_stream)

# Each hit's alignments are Bio.Align objects; scores live in .annotations
for hit in blast_record[:5]:
    print(f"{hit.target.id}: E-value={hit[0].annotations['evalue']}")

5. Structural Bioinformatics (Bio.PDB)

Reference: references/structure.md

Use for:

  • Parsing PDB and mmCIF structure files
  • Navigating protein structure hierarchy (SMCRA: Structure/Model/Chain/Residue/Atom)
  • Calculating distances, angles, and dihedrals
  • Secondary structure assignment (DSSP)
  • Structure superimposition and RMSD calculation
  • Extracting sequences from structures

Quick example:

from Bio.PDB import PDBParser

# Parse structure
parser = PDBParser(QUIET=True)
structure = parser.get_structure("1crn", "1crn.pdb")

# Calculate distance between alpha carbons
chain = structure[0]["A"]
distance = chain[10]["CA"] - chain[20]["CA"]
print(f"Distance: {distance:.2f} Å")

6. Phylogenetics (Bio.Phylo)

Reference: references/phylogenetics.md

Use for:

  • Reading and writing phylogenetic trees (Newick, NEXUS, phyloXML)
  • Building trees from distance matrices or alignments
  • Tree manipulation (pruning, rerooting, ladderizing)
  • Calculating phylogenetic distances
  • Creating consensus trees
  • Visualizing trees

Quick example:

from Bio import Phylo

# Read and visualize tree
tree = Phylo.read("tree.nwk", "newick")
Phylo.draw_ascii(tree)

# Calculate distance
distance = tree.distance("Species_A", "Species_B")
print(f"Distance: {distance:.3f}")

7. Advanced Features

Reference: references/advanced.md

Use for:

  • Sequence motifs (Bio.motifs) - Finding and analyzing motif patterns
  • Population genetics (Bio.PopGen) - GenePop files, Fst calculations, Hardy-Weinberg tests
  • Sequence utilities (Bio.SeqUtils) - GC content, melting temperature, molecular weight, protein analysis
  • Restriction analysis (Bio.Restriction) - Finding restriction enzyme sites
  • Clustering (Bio.Cluster) - K-means and hierarchical clustering
  • Genome diagrams (GenomeDiagram) - Visualizing genomic features

Quick example:

from Bio.SeqUtils import gc_fraction, molecular_weight
from Bio.Seq import Seq

seq = Seq("ATCGATCGATCG")
print(f"GC content: {gc_fraction(seq):.2%}")
print(f"Molecular weight: {molecular_weight(seq, seq_type='DNA'):.2f} g/mol")

General Workflow Guidelines

Reading Documentation

When a user asks about a specific Biopython task:

  1. Identify the relevant module based on the task description
  2. Read the appropriate reference file using the Read tool
  3. Extract relevant code patterns and adapt them to the user's specific needs
  4. Combine multiple modules when the task requires it

Example search patterns for reference files:

# Find information about specific functions
grep -n "SeqIO.parse" references/sequence_io.md

# Find examples of specific tasks
grep -n "BLAST" references/blast.md

# Find information about specific concepts
grep -n "alignment" references/alignment.md

Writing Biopython Code

Follow these principles when writing Biopython code:

  1. Import modules explicitly

    from Bio import SeqIO, Entrez
    from Bio.Seq import Seq
    
  2. Set Entrez email when using NCBI databases

    Entrez.email = "your.email@example.com"
    
  3. Use appropriate file formats - Check which format best suits the task

    # Common formats: "fasta", "genbank", "fastq", "clustal", "phylip"
    
  4. Handle files properly - Close handles after use or use context managers

    with open("file.fasta") as handle:
        records = SeqIO.parse(handle, "fasta")
    
  5. Use iterators for large files - Avoid loading everything into memory

    for record in SeqIO.parse("large_file.fasta", "fasta"):
        # Process one record at a time
    
  6. Handle errors gracefully - Network operations and file parsing can fail

    try:
        handle = Entrez.efetch(db="nucleotide", id=accession)
    except HTTPError as e:
        print(f"Error: {e}")
    

Common Patterns

Pattern 1: Fetch Sequence from GenBank

from Bio import Entrez, SeqIO

Entrez.email = "your.email@example.com"

# Fetch sequence
handle = Entrez.efetch(db="nucleotide", id="EU490707", rettype="gb", retmode="text")
record = SeqIO.read(handle, "genbank")
handle.close()

print(f"Description: {record.description}")
print(f"Sequence length: {len(record.seq)}")

Pattern 2: Sequence Analysis Pipeline

from Bio import SeqIO
from Bio.SeqUtils import gc_fraction

for record in SeqIO.parse("sequences.fasta", "fasta"):
    # Calculate statistics
    gc = gc_fraction(record.seq)
    length = len(record.seq)

    # Find ORFs, translate, etc.
    protein = record.seq.translate()

    print(f"{record.id}: {length} bp, GC={gc:.2%}")

Pattern 3: BLAST and Fetch Top Hits

from Bio import Blast, Entrez, SeqIO

Entrez.email = Blast.email = "your.email@example.com"

# Run BLAST (Bio.Blast API, Biopython >= 1.84)
result_stream = Blast.qblast("blastn", "nt", sequence)
blast_record = Blast.read(result_stream)

# Get top hit accessions (hit.target is a SeqRecord)
accessions = [hit.target.name for hit in blast_record[:5]]

# Fetch sequences
for acc in accessions:
    handle = Entrez.efetch(db="nucleotide", id=acc, rettype="fasta", retmode="text")
    record = SeqIO.read(handle, "fasta")
    handle.close()
    print(f">{record.description}")

Pattern 4: Build Phylogenetic Tree from Sequences

from Bio import AlignIO, Phylo
from Bio.Phylo.TreeConstruction import DistanceCalculator, DistanceTreeConstructor

# Read alignment
alignment = AlignIO.read("alignment.fasta", "fasta")

# Calculate distances
calculator = DistanceCalculator("identity")
dm = calculator.get_distance(alignment)

# Build tree
constructor = DistanceTreeConstructor()
tree = constructor.nj(dm)

# Visualize
Phylo.draw_ascii(tree)

Best Practices

  1. Always read relevant reference documentation before writing code
  2. Use grep to search reference files for specific functions or examples
  3. Validate file formats before parsing
  4. Handle missing data gracefully - Not all records have all fields
  5. Cache downloaded data - Don't repeatedly download the same sequences
  6. Respect NCBI rate limits - Use API keys and proper delays
  7. Test with small datasets before processing large files
  8. Keep Biopython updated to get latest features and bug fixes
  9. Use appropriate genetic code tables for translation
  10. Document analysis parameters for reproducibility

Troubleshooting Common Issues

Issue: "No handlers could be found for logger 'Bio.Entrez'"

Solution: This is just a warning. Set Entrez.email to suppress it.

Issue: "HTTP Error 400" from NCBI

Solution: Check that IDs/accessions are valid and properly formatted.

Issue: "ValueError: EOF" when parsing files

Solution: Verify file format matches the specified format string.

Issue: Alignment fails with "sequences are not the same length"

Solution: Ensure sequences are aligned before using AlignIO or MultipleSeqAlignment.

Issue: BLAST searches are slow

Solution: Use local BLAST for large-scale searches, or cache results.

Issue: PDB parser warnings

Solution: Use PDBParser(QUIET=True) to suppress warnings, or investigate structure quality.

Additional Resources

Quick Reference

To locate information in reference files, use these search patterns:

# Search for specific functions
grep -n "function_name" references/*.md

# Find examples of specific tasks
grep -n "example" references/sequence_io.md

# Find all occurrences of a module
grep -n "Bio.Seq" references/*.md

Summary

Biopython provides comprehensive tools for computational molecular biology. When using this skill:

  1. Identify the task domain (sequences, alignments, databases, BLAST, structures, phylogenetics, or advanced)
  2. Consult the appropriate reference file in the references/ directory
  3. Adapt code examples to the specific use case
  4. Combine multiple modules when needed for complex workflows
  5. Follow best practices for file handling, error checking, and data management

The modular reference documentation ensures detailed, searchable information for every major Biopython capability.

Part of the AlterLab Academic Skills suite.

Files (alterlab-academic-skills)
  • evals
    • evals.json 4.2 KB
      {
        "skill": "alterlab-biopython",
        "evals": [
          {
            "id": "parse-genbank-batch",
            "prompt": "I have a folder of ~400 GenBank (.gb) files from a sequencing run. I want a Python script that walks the folder, reads each record with SeqIO, and writes one combined multi-FASTA with the accession as the header and the nucleotide sequence. Memory matters since some files are large.",
            "expected_output": "Triggers the Biopython skill. Should write a Python script using Bio.SeqIO.parse / SeqIO.read to read GenBank records and SeqIO.write (or SeqIO.convert) to emit FASTA, iterating records one at a time rather than loading everything into memory, and using record.id / record.seq. Should reference the 'genbank' and 'fasta' format strings and proper handle/context-manager handling.",
            "assertions": [
              {"type": "should_trigger", "value": true},
              {"type": "output_contains", "value": "SeqIO"},
              {"type": "behavior", "value": "Uses an iterator over records (SeqIO.parse) for memory efficiency rather than reading the whole set into a list."}
            ]
          },
          {
            "id": "entrez-efetch-pubmed",
            "prompt": "Write me a script that searches PubMed for papers on 'CRISPR base editing' from the last two years, gets the top 50 PMIDs, then fetches the title and abstract for each. I need to be a good NCBI citizen about rate limits.",
            "expected_output": "Triggers the Biopython skill. Should use Bio.Entrez with Entrez.esearch (db='pubmed') then Entrez.efetch/esummary, set Entrez.email (and mention the optional Entrez.api_key for 10 req/s), and respect NCBI rate limits with batching/delays. Should parse results with Entrez.read.",
            "assertions": [
              {"type": "should_trigger", "value": true},
              {"type": "output_contains", "value": "Entrez"},
              {"type": "behavior", "value": "Sets Entrez.email and addresses NCBI rate limiting (api_key and/or delays/batching)."}
            ]
          },
          {
            "id": "pdb-ca-distance",
            "prompt": "I downloaded 1CRN.pdb. Can you give me Python to parse the structure and compute the distance between the alpha carbons of residue 10 and residue 20 in chain A?",
            "expected_output": "Triggers the Biopython skill. Should use Bio.PDB.PDBParser (ideally with QUIET=True) to parse the structure, navigate the SMCRA hierarchy (structure[0]['A'][10]['CA']), and compute the CA-CA distance via atom subtraction. Should print the distance in angstroms.",
            "assertions": [
              {"type": "should_trigger", "value": true},
              {"type": "output_contains", "value": "PDBParser"},
              {"type": "behavior", "value": "Navigates the Structure/Model/Chain/Residue/Atom hierarchy and subtracts two CA atoms to get a distance."}
            ]
          },
          {
            "id": "nj-tree-from-alignment",
            "prompt": "I have a multiple sequence alignment in FASTA. Build a neighbor-joining tree from it in Python and draw it as ASCII so I can sanity-check the topology in my terminal.",
            "expected_output": "Triggers the Biopython skill. Should read the alignment with Bio.AlignIO.read, compute a distance matrix with DistanceCalculator, build the tree with DistanceTreeConstructor().nj(), and render it with Bio.Phylo.draw_ascii.",
            "assertions": [
              {"type": "should_trigger", "value": true},
              {"type": "output_contains", "value": "DistanceTreeConstructor"},
              {"type": "behavior", "value": "Uses AlignIO to read the alignment and Bio.Phylo to construct and ASCII-draw a neighbor-joining tree."}
            ]
          },
          {
            "id": "near-miss-gget",
            "prompt": "Quick one-off: I just need the UniProt ID and a one-line description for the human gene ENSG00000034713 — what's the fastest way to look that up from the terminal?",
            "expected_output": "Should NOT trigger the Biopython skill. This is a quick interactive single-gene database lookup, which is gget territory (gget info). The response should defer to the gget skill (e.g. `gget info ENSG00000034713`) rather than scripting a Bio.Entrez/UniProt request, since the skill description explicitly routes quick one-off lookups to gget.",
            "assertions": [
              {"type": "should_not_trigger", "value": true},
              {"type": "output_contains", "value": "gget"}
            ]
          }
        ]
      }
      
  • references
    • advanced.md 13.8 KB
      # Advanced Biopython Features
      
      ## Sequence Motifs with Bio.motifs
      
      ### Creating Motifs
      
      ```python
      from Bio import motifs
      from Bio.Seq import Seq
      
      # Create motif from instances
      instances = [
          Seq("TACAA"),
          Seq("TACGC"),
          Seq("TACAC"),
          Seq("TACCC"),
          Seq("AACCC"),
          Seq("AATGC"),
          Seq("AATGC"),
      ]
      
      motif = motifs.create(instances)
      ```
      
      ### Motif Consensus and Degenerate Sequences
      
      ```python
      # Get consensus sequence
      print(motif.counts.consensus)
      
      # Get degenerate consensus (IUPAC ambiguity codes)
      print(motif.counts.degenerate_consensus)
      
      # Access counts matrix
      print(motif.counts)
      ```
      
      ### Position Weight Matrix (PWM)
      
      ```python
      # Create position weight matrix
      pwm = motif.counts.normalize(pseudocounts=0.5)
      print(pwm)
      
      # Calculate information content
      ic = motif.counts.information_content()
      print(f"Information content: {ic:.2f} bits")
      ```
      
      ### Searching for Motifs
      
      ```python
      from Bio.Seq import Seq
      
      # Search sequence for motif
      test_seq = Seq("ATACAGGACAGACATACGCATACAACATTACAC")
      
      # Get Position Specific Scoring Matrix (PSSM)
      pssm = pwm.log_odds()
      
      # Search sequence
      for position, score in pssm.search(test_seq, threshold=5.0):
          print(f"Position {position}: score = {score:.2f}")
      ```
      
      ### Reading Motifs from Files
      
      ```python
      # Read motif from JASPAR format
      with open("motif.jaspar") as handle:
          motif = motifs.read(handle, "jaspar")
      
      # Read multiple motifs
      with open("motifs.jaspar") as handle:
          for m in motifs.parse(handle, "jaspar"):
              print(m.name)
      
      # Supported formats: jaspar, meme, transfac, pfm
      ```
      
      ### Writing Motifs
      
      ```python
      # Write motif in JASPAR format
      with open("output.jaspar", "w") as handle:
          handle.write(motif.format("jaspar"))
      ```
      
      ## Population Genetics with Bio.PopGen
      
      ### Working with GenePop Files
      
      ```python
      from Bio.PopGen import GenePop
      
      # Read GenePop file
      with open("data.gen") as handle:
          record = GenePop.read(handle)
      
      # Access populations
      print(f"Number of populations: {len(record.populations)}")
      print(f"Loci: {record.loci_list}")
      
      # Iterate through populations
      for pop_idx, pop in enumerate(record.populations):
          print(f"\nPopulation {pop_idx + 1}:")
          for individual in pop:
              print(f"  {individual[0]}: {individual[1]}")
      ```
      
      ### Calculating Population Statistics
      
      ```python
      from Bio.PopGen.GenePop.Controller import GenePopController
      
      # Create controller
      ctrl = GenePopController()
      
      # Calculate basic statistics
      result = ctrl.calc_allele_genotype_freqs("data.gen")
      
      # Calculate Fst
      fst_result = ctrl.calc_fst_all("data.gen")
      print(f"Fst: {fst_result}")
      
      # Test Hardy-Weinberg equilibrium
      hw_result = ctrl.test_hw_pop("data.gen", "probability")
      ```
      
      ## Sequence Utilities with Bio.SeqUtils
      
      ### GC Content
      
      ```python
      from Bio.SeqUtils import gc_fraction
      from Bio.Seq import Seq
      
      seq = Seq("ATCGATCGATCG")
      gc = gc_fraction(seq)
      print(f"GC content: {gc:.2%}")
      ```
      
      ### Molecular Weight
      
      ```python
      from Bio.SeqUtils import molecular_weight
      
      # DNA molecular weight
      dna_seq = Seq("ATCG")
      mw = molecular_weight(dna_seq, seq_type="DNA")
      print(f"DNA MW: {mw:.2f} g/mol")
      
      # Protein molecular weight
      protein_seq = Seq("ACDEFGHIKLMNPQRSTVWY")
      mw = molecular_weight(protein_seq, seq_type="protein")
      print(f"Protein MW: {mw:.2f} Da")
      ```
      
      ### Melting Temperature
      
      ```python
      from Bio.SeqUtils import MeltingTemp as mt
      
      # Calculate Tm using nearest-neighbor method
      seq = Seq("ATCGATCGATCG")
      tm = mt.Tm_NN(seq)
      print(f"Tm: {tm:.1f}°C")
      
      # Use different salt concentration
      tm = mt.Tm_NN(seq, Na=50, Mg=1.5)  # 50 mM Na+, 1.5 mM Mg2+
      
      # Wallace rule (for primers)
      tm_wallace = mt.Tm_Wallace(seq)
      ```
      
      ### GC Skew
      
      ```python
      from Bio.SeqUtils import gc_skew
      
      # Calculate GC skew
      seq = Seq("ATCGATCGGGCCCAAATTT")
      skew = gc_skew(seq, window=100)
      print(f"GC skew: {skew}")
      ```
      
      ### ProtParam - Protein Analysis
      
      ```python
      from Bio.SeqUtils.ProtParam import ProteinAnalysis
      
      protein_seq = "ACDEFGHIKLMNPQRSTVWY"
      analyzed_seq = ProteinAnalysis(protein_seq)
      
      # Molecular weight
      print(f"MW: {analyzed_seq.molecular_weight():.2f} Da")
      
      # Isoelectric point
      print(f"pI: {analyzed_seq.isoelectric_point():.2f}")
      
      # Amino acid composition
      print(f"Composition: {analyzed_seq.get_amino_acids_percent()}")
      
      # Instability index
      print(f"Instability: {analyzed_seq.instability_index():.2f}")
      
      # Aromaticity
      print(f"Aromaticity: {analyzed_seq.aromaticity():.2f}")
      
      # Secondary structure fraction
      ss = analyzed_seq.secondary_structure_fraction()
      print(f"Helix: {ss[0]:.2%}, Turn: {ss[1]:.2%}, Sheet: {ss[2]:.2%}")
      
      # Extinction coefficient (assumes Cys reduced, no disulfide bonds)
      print(f"Extinction coefficient: {analyzed_seq.molar_extinction_coefficient()}")
      
      # Gravy (grand average of hydropathy)
      print(f"GRAVY: {analyzed_seq.gravy():.3f}")
      ```
      
      ## Restriction Analysis with Bio.Restriction
      
      ```python
      from Bio import Restriction
      from Bio.Seq import Seq
      
      # Analyze sequence for restriction sites
      seq = Seq("GAATTCATCGATCGATGAATTC")
      
      # Use specific enzyme
      ecori = Restriction.EcoRI
      sites = ecori.search(seq)
      print(f"EcoRI sites at: {sites}")
      
      # Use multiple enzymes
      rb = Restriction.RestrictionBatch(["EcoRI", "BamHI", "PstI"])
      results = rb.search(seq)
      for enzyme, sites in results.items():
          if sites:
              print(f"{enzyme}: {sites}")
      
      # Get all enzymes that cut sequence
      all_enzymes = Restriction.Analysis(rb, seq)
      print(f"Cutting enzymes: {all_enzymes.with_sites()}")
      ```
      
      ## Sequence Translation Tables
      
      ```python
      from Bio.Data import CodonTable
      
      # Standard genetic code
      standard_table = CodonTable.unambiguous_dna_by_id[1]
      print(standard_table)
      
      # Mitochondrial code
      mito_table = CodonTable.unambiguous_dna_by_id[2]
      
      # Get specific codon
      print(f"ATG codes for: {standard_table.forward_table['ATG']}")
      
      # Get stop codons
      print(f"Stop codons: {standard_table.stop_codons}")
      
      # Get start codons
      print(f"Start codons: {standard_table.start_codons}")
      ```
      
      ## Cluster Analysis with Bio.Cluster
      
      ```python
      from Bio.Cluster import kcluster
      import numpy as np
      
      # Sample data matrix (genes x conditions)
      data = np.array([
          [1.2, 0.8, 0.5, 1.5],
          [0.9, 1.1, 0.7, 1.3],
          [0.2, 0.3, 2.1, 2.5],
          [0.1, 0.4, 2.3, 2.2],
      ])
      
      # Perform k-means clustering
      clusterid, error, nfound = kcluster(data, nclusters=2)
      print(f"Cluster assignments: {clusterid}")
      print(f"Error: {error}")
      ```
      
      ## Genome Diagrams with GenomeDiagram
      
      ```python
      from Bio.Graphics import GenomeDiagram
      from Bio.SeqFeature import SeqFeature, FeatureLocation
      from Bio import SeqIO
      from reportlab.lib import colors
      
      # Read GenBank file
      record = SeqIO.read("sequence.gb", "genbank")
      
      # Create diagram
      gd_diagram = GenomeDiagram.Diagram("Genome Diagram")
      gd_track = gd_diagram.new_track(1, greytrack=True)
      gd_feature_set = gd_track.new_set()
      
      # Add features
      for feature in record.features:
          if feature.type == "CDS":
              color = colors.blue
          elif feature.type == "gene":
              color = colors.lightblue
          else:
              color = colors.grey
      
          gd_feature_set.add_feature(
              feature,
              color=color,
              label=True,
              label_size=6,
              label_angle=45
          )
      
      # Draw and save
      gd_diagram.draw(format="linear", pagesize="A4", fragments=1)
      gd_diagram.write("genome_diagram.pdf", "PDF")
      ```
      
      ## Sequence Comparison with Bio.pairwise2
      
      **Note**: `Bio.pairwise2` has been deprecated since 1.80 and is slated for removal —
      `Bio.Align.PairwiseAligner` replaces it (see `alignment.md`), and the 1.89 line adds
      `Bio.Align.global_align`/`local_align` as near drop-in convenience wrappers. Port legacy
      code rather than extending it.
      
      For reading existing scripts:
      
      ```python
      from Bio import pairwise2
      from Bio.pairwise2 import format_alignment
      
      # Global alignment
      alignments = pairwise2.align.globalxx("ACCGT", "ACGT")
      
      # Print top alignments
      for alignment in alignments[:3]:
          print(format_alignment(*alignment))
      ```
      
      ## Working with PubChem
      
      ```python
      from Bio import Entrez
      
      Entrez.email = "your.email@example.com"
      
      # Search PubChem
      handle = Entrez.esearch(db="pccompound", term="aspirin")
      result = Entrez.read(handle)
      handle.close()
      
      compound_id = result["IdList"][0]
      
      # Get compound information
      handle = Entrez.efetch(db="pccompound", id=compound_id, retmode="xml")
      compound_data = handle.read()
      handle.close()
      ```
      
      ## Sequence Features with Bio.SeqFeature
      
      ```python
      from Bio.SeqFeature import SeqFeature, FeatureLocation
      from Bio.Seq import Seq
      from Bio.SeqRecord import SeqRecord
      
      # Create a feature
      feature = SeqFeature(
          location=FeatureLocation(start=10, end=50),
          type="CDS",
          strand=1,
          qualifiers={"gene": ["ABC1"], "product": ["ABC protein"]}
      )
      
      # Add feature to record
      record = SeqRecord(Seq("ATCG" * 20), id="seq1")
      record.features.append(feature)
      
      # Extract feature sequence
      feature_seq = feature.extract(record.seq)
      print(feature_seq)
      ```
      
      ## Sequence Ambiguity
      
      ```python
      from Bio.Data import IUPACData
      
      # DNA ambiguity codes
      print(IUPACData.ambiguous_dna_letters)
      
      # Protein ambiguity codes
      print(IUPACData.ambiguous_protein_letters)
      
      # Resolve ambiguous bases
      print(IUPACData.ambiguous_dna_values["N"])  # Any base
      print(IUPACData.ambiguous_dna_values["R"])  # A or G
      ```
      
      ## Quality Scores (FASTQ)
      
      ```python
      from Bio import SeqIO
      
      # Read FASTQ with quality scores
      for record in SeqIO.parse("reads.fastq", "fastq"):
          print(f"ID: {record.id}")
          print(f"Sequence: {record.seq}")
          print(f"Quality: {record.letter_annotations['phred_quality']}")
      
          # Calculate average quality
          avg_quality = sum(record.letter_annotations['phred_quality']) / len(record)
          print(f"Average quality: {avg_quality:.2f}")
      
          # Filter by quality
          min_quality = min(record.letter_annotations['phred_quality'])
          if min_quality >= 20:
              print("High quality read")
      ```
      
      ## Best Practices
      
      1. **Use appropriate modules** - Choose the right tool for your analysis
      2. **Handle pseudocounts** - Important for motif analysis
      3. **Validate input data** - Check file formats and data quality
      4. **Consider performance** - Some operations can be computationally intensive
      5. **Cache results** - Store intermediate results for large analyses
      6. **Use proper genetic codes** - Select appropriate translation tables
      7. **Document parameters** - Record thresholds and settings used
      8. **Validate statistical results** - Understand limitations of tests
      9. **Handle edge cases** - Check for empty results or invalid input
      10. **Combine modules** - Leverage multiple Biopython tools together
      
      ## Common Use Cases
      
      ### Find ORFs
      
      ```python
      from Bio import SeqIO
      
      def find_orfs(seq, min_length=100):
          """Find all ORFs in sequence."""
          orfs = []
      
          for strand, nuc in [(+1, seq), (-1, seq.reverse_complement())]:
              for frame in range(3):
                  trans = nuc[frame:].translate()
                  trans_len = len(trans)
      
                  aa_start = 0
                  while aa_start < trans_len:
                      aa_end = trans.find("*", aa_start)
                      if aa_end == -1:
                          aa_end = trans_len
      
                      if aa_end - aa_start >= min_length // 3:
                          start = frame + aa_start * 3
                          end = frame + aa_end * 3
                          orfs.append({
                              'start': start,
                              'end': end,
                              'strand': strand,
                              'frame': frame,
                              'length': end - start,
                              'sequence': nuc[start:end]
                          })
      
                      aa_start = aa_end + 1
      
          return orfs
      
      # Use it
      record = SeqIO.read("sequence.fasta", "fasta")
      orfs = find_orfs(record.seq, min_length=300)
      for orf in orfs:
          print(f"ORF: {orf['start']}-{orf['end']}, strand={orf['strand']}, length={orf['length']}")
      ```
      
      ### Analyze Codon Usage
      
      ```python
      from Bio import SeqIO
      
      def analyze_codon_usage(fasta_file):
          """Analyze codon usage in coding sequences."""
          codon_counts = {}
      
          for record in SeqIO.parse(fasta_file, "fasta"):
              # Ensure sequence is multiple of 3
              seq = record.seq[:len(record.seq) - len(record.seq) % 3]
      
              # Count codons
              for i in range(0, len(seq), 3):
                  codon = str(seq[i:i+3])
                  codon_counts[codon] = codon_counts.get(codon, 0) + 1
      
          # Calculate frequencies
          total = sum(codon_counts.values())
          codon_freq = {k: v/total for k, v in codon_counts.items()}
      
          return codon_freq
      ```
      
      ### Calculate Sequence Complexity
      
      ```python
      def sequence_complexity(seq, k=2):
          """Calculate k-mer complexity (Shannon entropy)."""
          import math
          from collections import Counter
      
          # Generate k-mers
          kmers = [str(seq[i:i+k]) for i in range(len(seq) - k + 1)]
      
          # Count k-mers
          counts = Counter(kmers)
          total = len(kmers)
      
          # Calculate entropy
          entropy = 0
          for count in counts.values():
              freq = count / total
              entropy -= freq * math.log2(freq)
      
          # Normalize by maximum possible entropy
          max_entropy = math.log2(4 ** k)  # For DNA
      
          return entropy / max_entropy if max_entropy > 0 else 0
      
      # Use it
      from Bio.Seq import Seq
      seq = Seq("ATCGATCGATCGATCG")
      complexity = sequence_complexity(seq, k=2)
      print(f"Sequence complexity: {complexity:.3f}")
      ```
      
      ### Extract Promoter Regions
      
      ```python
      def extract_promoters(genbank_file, upstream=500):
          """Extract promoter regions upstream of genes."""
          from Bio import SeqIO
      
          record = SeqIO.read(genbank_file, "genbank")
          promoters = []
      
          for feature in record.features:
              if feature.type == "gene":
                  if feature.strand == 1:
                      # Forward strand
                      start = max(0, feature.location.start - upstream)
                      end = feature.location.start
                  else:
                      # Reverse strand
                      start = feature.location.end
                      end = min(len(record.seq), feature.location.end + upstream)
      
                  promoter_seq = record.seq[start:end]
                  if feature.strand == -1:
                      promoter_seq = promoter_seq.reverse_complement()
      
                  promoters.append({
                      'gene': feature.qualifiers.get('gene', ['Unknown'])[0],
                      'sequence': promoter_seq,
                      'start': start,
                      'end': end
                  })
      
          return promoters
      ```
      
    • alignment.md 9.8 KB
      # Sequence Alignments with Bio.Align and Bio.AlignIO
      
      ## Overview
      
      Bio.Align provides tools for pairwise sequence alignment using various algorithms, while Bio.AlignIO handles reading and writing multiple sequence alignment files in various formats.
      
      ## Pairwise Alignment with Bio.Align
      
      ### The PairwiseAligner Class
      
      The `PairwiseAligner` class performs pairwise sequence alignments using Needleman-Wunsch (global), Smith-Waterman (local), Gotoh (three-state), and Waterman-Smith-Beyer algorithms. The appropriate algorithm is automatically selected based on gap score parameters.
      
      ### Creating an Aligner
      
      ```python
      from Bio import Align
      
      # Create aligner with default parameters
      aligner = Align.PairwiseAligner()
      
      # Default scores (Biopython >= 1.86, verified on 1.88):
      # - Match score: +1.0
      # - Mismatch score: 0.0
      # - All gap scores: -1.0   <- was 0.0 before 1.86
      ```
      
      The 1.86 change of the default gap score from 0 to -1 matters: with a 0 gap score a
      mismatch and an insertion+deletion pair score identically, so the aligner returned many
      trivially different alignments of the same score. Scripts written against the old default
      will now return fewer alignments and different scores — set the gap scores explicitly if
      you need the previous behaviour.
      
      ### Customizing Alignment Parameters
      
      ```python
      # Set scoring parameters
      aligner.match_score = 2.0
      aligner.mismatch_score = -1.0
      aligner.gap_score = -0.5
      
      # Or use separate gap opening/extension penalties
      aligner.open_gap_score = -2.0
      aligner.extend_gap_score = -0.5
      
      # Set internal gap scores separately
      aligner.internal_open_gap_score = -2.0
      aligner.internal_extend_gap_score = -0.5
      
      # Set end gap scores (for semi-global alignment)
      aligner.left_open_gap_score = 0.0
      aligner.left_extend_gap_score = 0.0
      aligner.right_open_gap_score = 0.0
      aligner.right_extend_gap_score = 0.0
      ```
      
      ### Alignment Modes
      
      ```python
      # Global alignment (default)
      aligner.mode = 'global'
      
      # Local alignment
      aligner.mode = 'local'
      ```
      
      ### Performing Alignments
      
      ```python
      from Bio.Seq import Seq
      
      seq1 = Seq("ACCGGT")
      seq2 = Seq("ACGGT")
      
      # Get all optimal alignments
      alignments = aligner.align(seq1, seq2)
      
      # Iterate through alignments
      for alignment in alignments:
          print(alignment)
          print(f"Score: {alignment.score}")
      
      # Get just the score
      score = aligner.score(seq1, seq2)
      ```
      
      ### Using Substitution Matrices
      
      ```python
      from Bio.Align import substitution_matrices
      
      # Load a substitution matrix
      matrix = substitution_matrices.load("BLOSUM62")
      aligner.substitution_matrix = matrix
      
      # Align protein sequences
      protein1 = Seq("KEVLA")
      protein2 = Seq("KSVLA")
      alignments = aligner.align(protein1, protein2)
      ```
      
      ### Available Substitution Matrices
      
      Common matrices include:
      - **BLOSUM** series (BLOSUM45, BLOSUM50, BLOSUM62, BLOSUM80, BLOSUM90)
      - **PAM** series (PAM30, PAM70, PAM250)
      - **MATCH** - Simple match/mismatch matrix
      
      ```python
      # List available matrices
      available = substitution_matrices.load()
      print(available)
      ```
      
      ## Multiple Sequence Alignments with Bio.AlignIO
      
      ### Reading Alignments
      
      Bio.AlignIO provides similar API to Bio.SeqIO but for alignment files:
      
      ```python
      from Bio import AlignIO
      
      # Read a single alignment
      alignment = AlignIO.read("alignment.aln", "clustal")
      
      # Parse multiple alignments from a file
      for alignment in AlignIO.parse("alignments.aln", "clustal"):
          print(f"Alignment with {len(alignment)} sequences")
          print(f"Alignment length: {alignment.get_alignment_length()}")
      ```
      
      ### Supported Alignment Formats
      
      Common formats include:
      - **clustal** - Clustal format
      - **phylip** - PHYLIP format
      - **phylip-relaxed** - Relaxed PHYLIP (longer names)
      - **stockholm** - Stockholm format
      - **fasta** - FASTA format (aligned)
      - **nexus** - NEXUS format
      - **emboss** - EMBOSS alignment format
      - **msf** - MSF format
      - **maf** - Multiple Alignment Format
      
      ### Writing Alignments
      
      ```python
      # Write alignment to file
      AlignIO.write(alignment, "output.aln", "clustal")
      
      # Convert between formats
      count = AlignIO.convert("input.aln", "clustal", "output.phy", "phylip")
      ```
      
      ### Working with Alignment Objects
      
      ```python
      from Bio import AlignIO
      
      alignment = AlignIO.read("alignment.aln", "clustal")
      
      # Get alignment properties
      print(f"Number of sequences: {len(alignment)}")
      print(f"Alignment length: {alignment.get_alignment_length()}")
      
      # Access individual sequences
      for record in alignment:
          print(f"{record.id}: {record.seq}")
      
      # Get alignment column
      column = alignment[:, 0]  # First column
      
      # Get alignment slice
      sub_alignment = alignment[:, 10:20]  # Positions 10-20
      
      # Get specific sequence
      seq_record = alignment[0]  # First sequence
      ```
      
      ### Alignment Analysis
      
      > **Removed API:** `Bio.Align.AlignInfo.SummaryInfo` lost its `gap_consensus`, `dumb_consensus`, and `pos_specific_score_matrix` methods (deprecated, then removed). As of 1.88 `SummaryInfo` only exposes `get_column`. Compute a consensus with `Bio.motifs` or directly over alignment columns.
      
      ```python
      from Bio import motifs
      
      # Consensus + position counts via Bio.motifs (sequences must be equal length)
      motif = motifs.create([record.seq for record in alignment])
      print(motif.consensus)             # plain consensus
      print(motif.degenerate_consensus)  # IUPAC-degenerate consensus
      print(motif.counts)                # per-base counts per column
      
      # Per-column majority/identity directly (handles gaps)
      length = alignment.get_alignment_length()
      for i in range(length):
          column = alignment[:, i]                       # e.g. "AAAG-"
          base, n = max(((b, column.count(b)) for b in set(column)),
                        key=lambda kv: kv[1])
          print(f"col {i}: {base} ({n}/{len(column)})")
      ```
      
      ## Creating Alignments Programmatically
      
      ### From SeqRecord Objects
      
      ```python
      from Bio.Align import MultipleSeqAlignment
      from Bio.SeqRecord import SeqRecord
      from Bio.Seq import Seq
      
      # Create records
      records = [
          SeqRecord(Seq("ACTGCTAGCTAG"), id="seq1"),
          SeqRecord(Seq("ACT-CTAGCTAG"), id="seq2"),
          SeqRecord(Seq("ACTGCTA-CTAG"), id="seq3"),
      ]
      
      # Create alignment
      alignment = MultipleSeqAlignment(records)
      ```
      
      ### Adding Sequences to Alignments
      
      ```python
      # Start with empty alignment
      alignment = MultipleSeqAlignment([])
      
      # Add sequences (must have same length)
      alignment.append(SeqRecord(Seq("ACTG"), id="seq1"))
      alignment.append(SeqRecord(Seq("ACTG"), id="seq2"))
      
      # Extend with another alignment
      alignment.extend(other_alignment)
      ```
      
      ## Advanced Alignment Operations
      
      ### Removing Gaps
      
      ```python
      # Collect columns that are not entirely gaps
      kept_columns = []
      for i in range(alignment.get_alignment_length()):
          column = alignment[:, i]
          if set(column) != {'-'}:  # Not all gaps
              kept_columns.append(column)
      ```
      
      ### Alignment Sorting
      
      ```python
      # Sort by sequence ID
      sorted_alignment = sorted(alignment, key=lambda x: x.id)
      alignment = MultipleSeqAlignment(sorted_alignment)
      ```
      
      ### Computing Pairwise Identities
      
      ```python
      def pairwise_identity(seq1, seq2):
          """Calculate percent identity between two sequences."""
          matches = sum(a == b for a, b in zip(seq1, seq2) if a != '-' and b != '-')
          length = sum(1 for a, b in zip(seq1, seq2) if a != '-' and b != '-')
          return matches / length if length > 0 else 0
      
      # Calculate all pairwise identities
      for i, record1 in enumerate(alignment):
          for record2 in alignment[i+1:]:
              identity = pairwise_identity(record1.seq, record2.seq)
              print(f"{record1.id} vs {record2.id}: {identity:.2%}")
      ```
      
      ## Running External Alignment Tools
      
      > **Removed API:** `Bio.Align.Applications` (`ClustalOmegaCommandline`, `MuscleCommandline`, etc.) was deprecated in Biopython 1.78 and **removed** — it no longer imports. Invoke the aligner executables directly with `subprocess`.
      
      ### Clustal Omega (via subprocess)
      
      ```python
      import subprocess
      from Bio import AlignIO
      
      subprocess.run(
          ["clustalo", "-i", "sequences.fasta", "-o", "alignment.aln",
           "--outfmt=clustal", "--force"],
          check=True,
      )
      
      alignment = AlignIO.read("alignment.aln", "clustal")
      ```
      
      ### MUSCLE (via subprocess)
      
      ```python
      import subprocess
      
      # MUSCLE v5 syntax (-align / -output)
      subprocess.run(
          ["muscle", "-align", "sequences.fasta", "-output", "alignment.afa"],
          check=True,
      )
      ```
      
      ## Best Practices
      
      1. **Choose appropriate scoring schemes** - Use BLOSUM62 for proteins, custom scores for DNA
      2. **Consider alignment mode** - Global for similar-length sequences, local for finding conserved regions
      3. **Set gap penalties carefully** - Higher penalties create fewer, longer gaps
      4. **Use appropriate formats** - FASTA for simple alignments, Stockholm for rich annotation
      5. **Validate alignment quality** - Check for conserved regions and percent identity
      6. **Handle large alignments carefully** - Use slicing and iteration for memory efficiency
      7. **Preserve metadata** - Maintain SeqRecord IDs and annotations through alignment operations
      
      ## Common Use Cases
      
      ### Find Best Local Alignment
      
      ```python
      from Bio.Align import PairwiseAligner
      from Bio.Seq import Seq
      
      aligner = PairwiseAligner()
      aligner.mode = 'local'
      aligner.match_score = 2
      aligner.mismatch_score = -1
      
      seq1 = Seq("AGCTTAGCTAGCTAGC")
      seq2 = Seq("CTAGCTAGC")
      
      alignments = aligner.align(seq1, seq2)
      print(alignments[0])
      ```
      
      ### Protein Sequence Alignment
      
      ```python
      from Bio.Align import PairwiseAligner, substitution_matrices
      
      aligner = PairwiseAligner()
      aligner.substitution_matrix = substitution_matrices.load("BLOSUM62")
      aligner.open_gap_score = -10
      aligner.extend_gap_score = -0.5
      
      protein1 = Seq("KEVLA")
      protein2 = Seq("KEVLAEQP")
      alignments = aligner.align(protein1, protein2)
      ```
      
      ### Extract Conserved Regions
      
      ```python
      from Bio import AlignIO
      
      alignment = AlignIO.read("alignment.aln", "clustal")
      
      # Find columns with >80% identity
      conserved_positions = []
      for i in range(alignment.get_alignment_length()):
          column = alignment[:, i]
          most_common = max(set(column), key=column.count)
          if column.count(most_common) / len(column) > 0.8:
              conserved_positions.append(i)
      
      print(f"Conserved positions: {conserved_positions}")
      ```
      
    • blast.md 13.7 KB
      # BLAST Operations with Bio.Blast
      
      ## Overview
      
      Bio.Blast provides tools for running BLAST searches (both locally and via NCBI web services) and parsing BLAST results in various formats. The module handles the complexity of submitting queries and parsing outputs.
      
      > **Two APIs (both ship in 1.88), and one of them is on the way out.** The examples below
      > use the long-standing `Bio.Blast.NCBIWWW.qblast` + `Bio.Blast.NCBIXML` parser, which still
      > works. The newer top-level API added in 1.84 — `Bio.Blast.qblast(...)` with
      > `Bio.Blast.read`/`Bio.Blast.parse` returning `Record`/`Records` — is where development has
      > moved, and **`Bio.Blast.NCBIXML` is declared obsolete in the 1.89 development line**.
      > Write new code against `Bio.Blast`; the differences that matter:
      >
      > - `Blast.qblast(...)` returns **bytes**, so the stream can be handed straight to the
      >   parser (XML encoding is declared inside the document) or written in binary mode.
      > - Results are `Bio.Blast.Record` objects: iterate hits directly (`for hit in record`),
      >   read `hit.target` (a `SeqRecord`), and pull scores from
      >   `hit[0].annotations["evalue"]` / `["bit score"]` rather than `hsp.expect`.
      > - Each HSP is a `Bio.Align.Alignment`, so alignment formatting/slicing works on it.
      > - Set `Blast.email` (and optionally `Blast.tool`) exactly as you would `Entrez.email`.
      
      ## Running BLAST via NCBI Web Services
      
      ### Bio.Blast.NCBIWWW
      
      The `qblast()` function submits sequences to NCBI's online BLAST service:
      
      ```python
      from Bio.Blast import NCBIWWW
      from Bio import SeqIO
      
      # Read sequence from file
      record = SeqIO.read("sequence.fasta", "fasta")
      
      # Run BLAST search
      result_handle = NCBIWWW.qblast(
          program="blastn",           # BLAST program
          database="nt",              # Database to search
          sequence=str(record.seq)    # Query sequence
      )
      
      # Save results
      with open("blast_results.xml", "w") as out_file:
          out_file.write(result_handle.read())
      result_handle.close()
      ```
      
      ### BLAST Programs Available
      
      - **blastn** - Nucleotide vs nucleotide
      - **blastp** - Protein vs protein
      - **blastx** - Translated nucleotide vs protein
      - **tblastn** - Protein vs translated nucleotide
      - **tblastx** - Translated nucleotide vs translated nucleotide
      
      ### Common Databases
      
      **Nucleotide databases:**
      - `nt` - All GenBank+EMBL+DDBJ+PDB sequences
      - `refseq_rna` - RefSeq RNA sequences
      
      **Protein databases:**
      - `nr` - All non-redundant GenBank CDS translations
      - `refseq_protein` - RefSeq protein sequences
      - `pdb` - Protein Data Bank sequences
      - `swissprot` - Curated UniProtKB/Swiss-Prot
      
      ### Advanced qblast Parameters
      
      ```python
      result_handle = NCBIWWW.qblast(
          program="blastn",
          database="nt",
          sequence=str(record.seq),
          expect=0.001,              # E-value threshold
          hitlist_size=50,           # Number of hits to return
          alignments=25,             # Number of alignments to show
          word_size=11,              # Word size for initial match
          gapcosts="5 2",            # Gap costs (open extend)
          format_type="XML"          # Output format (default)
      )
      ```
      
      ### Using Sequence Files or IDs
      
      ```python
      # Use FASTA format string
      fasta_string = open("sequence.fasta").read()
      result_handle = NCBIWWW.qblast("blastn", "nt", fasta_string)
      
      # Use GenBank ID
      result_handle = NCBIWWW.qblast("blastn", "nt", "EU490707")
      
      # Use GI number
      result_handle = NCBIWWW.qblast("blastn", "nt", "160418")
      ```
      
      ## Parsing BLAST Results
      
      ### Bio.Blast.NCBIXML
      
      NCBIXML provides parsers for BLAST XML output (the recommended format):
      
      ```python
      from Bio.Blast import NCBIXML
      
      # Parse single BLAST result
      with open("blast_results.xml") as result_handle:
          blast_record = NCBIXML.read(result_handle)
      ```
      
      ### Accessing BLAST Record Data
      
      ```python
      # Query information
      print(f"Query: {blast_record.query}")
      print(f"Query length: {blast_record.query_length}")
      print(f"Database: {blast_record.database}")
      print(f"Number of sequences in database: {blast_record.database_sequences}")
      
      # Iterate through alignments (hits)
      for alignment in blast_record.alignments:
          print(f"\nHit: {alignment.title}")
          print(f"Length: {alignment.length}")
          print(f"Accession: {alignment.accession}")
      
          # Each alignment can have multiple HSPs (high-scoring pairs)
          for hsp in alignment.hsps:
              print(f"  E-value: {hsp.expect}")
              print(f"  Score: {hsp.score}")
              print(f"  Bits: {hsp.bits}")
              print(f"  Identities: {hsp.identities}/{hsp.align_length}")
              print(f"  Gaps: {hsp.gaps}")
              print(f"  Query: {hsp.query}")
              print(f"  Match: {hsp.match}")
              print(f"  Subject: {hsp.sbjct}")
      ```
      
      ### Filtering Results
      
      ```python
      # Only show hits with E-value < 0.001
      E_VALUE_THRESH = 0.001
      
      for alignment in blast_record.alignments:
          for hsp in alignment.hsps:
              if hsp.expect < E_VALUE_THRESH:
                  print(f"Hit: {alignment.title}")
                  print(f"E-value: {hsp.expect}")
                  print(f"Identities: {hsp.identities}/{hsp.align_length}")
                  print()
      ```
      
      ### Multiple BLAST Results
      
      For files containing multiple BLAST results (e.g., from batch searches):
      
      ```python
      from Bio.Blast import NCBIXML
      
      with open("batch_blast_results.xml") as result_handle:
          blast_records = NCBIXML.parse(result_handle)
      
          for blast_record in blast_records:
              print(f"\nQuery: {blast_record.query}")
              print(f"Hits: {len(blast_record.alignments)}")
      
              if blast_record.alignments:
                  # Get best hit
                  best_alignment = blast_record.alignments[0]
                  best_hsp = best_alignment.hsps[0]
                  print(f"Best hit: {best_alignment.title}")
                  print(f"E-value: {best_hsp.expect}")
      ```
      
      ## Running Local BLAST
      
      ### Prerequisites
      
      Local BLAST requires:
      1. BLAST+ command-line tools installed
      2. BLAST databases downloaded locally
      
      > **Removed API:** The `Bio.Blast.Applications` wrappers (`NcbiblastnCommandline`, `NcbiblastpCommandline`, `NcbimakeblastdbCommandline`, etc.) were deprecated in Biopython 1.78 and **removed** — they no longer import. Call the BLAST+ executables directly with `subprocess` and parse the XML output with `Bio.Blast`.
      
      ### Running BLAST+ via subprocess
      
      ```python
      import subprocess
      from Bio.Blast import NCBIXML
      
      # Run blastn against a local database, XML output (outfmt 5)
      subprocess.run(
          [
              "blastn",
              "-query", "input.fasta",
              "-db", "local_database",
              "-evalue", "0.001",
              "-outfmt", "5",
              "-out", "results.xml",
          ],
          check=True,
      )
      
      # Parse results
      with open("results.xml") as result_handle:
          blast_record = NCBIXML.read(result_handle)
      ```
      
      Swap the executable name for `blastp`, `blastx`, `tblastn`, or `tblastx` as needed; the flags are the same.
      
      ### Creating BLAST Databases
      
      ```python
      import subprocess
      
      # Create a nucleotide database from a FASTA file
      subprocess.run(
          [
              "makeblastdb",
              "-in", "sequences.fasta",
              "-dbtype", "nucl",       # "prot" for protein
              "-out", "my_database",
          ],
          check=True,
      )
      ```
      
      ## Analyzing BLAST Results
      
      ### Extract Best Hits
      
      ```python
      def get_best_hits(blast_record, num_hits=10, e_value_thresh=0.001):
          """Extract best hits from BLAST record."""
          hits = []
          for alignment in blast_record.alignments[:num_hits]:
              for hsp in alignment.hsps:
                  if hsp.expect < e_value_thresh:
                      hits.append({
                          'title': alignment.title,
                          'accession': alignment.accession,
                          'length': alignment.length,
                          'e_value': hsp.expect,
                          'score': hsp.score,
                          'identities': hsp.identities,
                          'align_length': hsp.align_length,
                          'query_start': hsp.query_start,
                          'query_end': hsp.query_end,
                          'sbjct_start': hsp.sbjct_start,
                          'sbjct_end': hsp.sbjct_end
                      })
                      break  # Only take best HSP per alignment
          return hits
      ```
      
      ### Calculate Percent Identity
      
      ```python
      def calculate_percent_identity(hsp):
          """Calculate percent identity for an HSP."""
          return (hsp.identities / hsp.align_length) * 100
      
      # Use it
      for alignment in blast_record.alignments:
          for hsp in alignment.hsps:
              if hsp.expect < 0.001:
                  identity = calculate_percent_identity(hsp)
                  print(f"{alignment.title}: {identity:.2f}% identity")
      ```
      
      ### Extract Hit Sequences
      
      ```python
      from Bio import Entrez, SeqIO
      
      Entrez.email = "your.email@example.com"
      
      def fetch_hit_sequences(blast_record, num_sequences=5):
          """Fetch sequences for top BLAST hits."""
          sequences = []
      
          for alignment in blast_record.alignments[:num_sequences]:
              accession = alignment.accession
      
              # Fetch sequence from GenBank
              handle = Entrez.efetch(
                  db="nucleotide",
                  id=accession,
                  rettype="fasta",
                  retmode="text"
              )
              record = SeqIO.read(handle, "fasta")
              handle.close()
      
              sequences.append(record)
      
          return sequences
      ```
      
      ## Parsing Other BLAST Formats
      
      ### Tab-Delimited Output (outfmt 6/7)
      
      ```python
      import subprocess
      
      # Run BLAST with tabular output
      subprocess.run(
          ["blastn", "-query", "input.fasta", "-db", "database",
           "-outfmt", "6", "-out", "results.txt"],
          check=True,
      )
      
      # Parse tabular results
      with open("results.txt") as f:
          for line in f:
              fields = line.strip().split('\t')
              query_id = fields[0]
              subject_id = fields[1]
              percent_identity = float(fields[2])
              align_length = int(fields[3])
              e_value = float(fields[10])
              bit_score = float(fields[11])
      
              print(f"{query_id} -> {subject_id}: {percent_identity}% identity, E={e_value}")
      ```
      
      ### Custom Output Formats
      
      ```python
      import subprocess
      
      # Specify custom columns (outfmt 6 with custom fields) -- pass the whole
      # format spec as one argument
      subprocess.run(
          ["blastn", "-query", "input.fasta", "-db", "database",
           "-outfmt", "6 qseqid sseqid pident length evalue bitscore qseq sseq",
           "-out", "results.txt"],
          check=True,
      )
      ```
      
      ## Best Practices
      
      1. **Use XML format** for parsing (outfmt 5) - most reliable and complete
      2. **Save BLAST results** - Don't re-run searches unnecessarily
      3. **Set appropriate E-value thresholds** - Default is 10, but 0.001-0.01 is often better
      4. **Handle rate limits** - NCBI limits request frequency
      5. **Use local BLAST** for large-scale searches or repeated queries
      6. **Cache results** - Save parsed data to avoid re-parsing
      7. **Check for empty results** - Handle cases with no hits gracefully
      8. **Consider alternatives** - For large datasets, consider DIAMOND or other fast aligners
      9. **Batch searches** - Submit multiple sequences together when possible
      10. **Filter by identity** - E-value alone may not be sufficient
      
      ## Common Use Cases
      
      ### Basic BLAST Search and Parse
      
      ```python
      from Bio.Blast import NCBIWWW, NCBIXML
      from Bio import SeqIO
      
      # Read query sequence
      record = SeqIO.read("query.fasta", "fasta")
      
      # Run BLAST
      print("Running BLAST search...")
      result_handle = NCBIWWW.qblast("blastn", "nt", str(record.seq))
      
      # Parse results
      blast_record = NCBIXML.read(result_handle)
      
      # Display top 5 hits
      print(f"\nTop 5 hits for {blast_record.query}:")
      for i, alignment in enumerate(blast_record.alignments[:5], 1):
          hsp = alignment.hsps[0]
          identity = (hsp.identities / hsp.align_length) * 100
          print(f"{i}. {alignment.title}")
          print(f"   E-value: {hsp.expect}, Identity: {identity:.1f}%")
      ```
      
      ### Find Orthologs
      
      ```python
      from Bio.Blast import NCBIWWW, NCBIXML
      from Bio import Entrez, SeqIO
      
      Entrez.email = "your.email@example.com"
      
      # Query gene sequence
      query_record = SeqIO.read("gene.fasta", "fasta")
      
      # BLAST against specific organism
      result_handle = NCBIWWW.qblast(
          "blastn",
          "nt",
          str(query_record.seq),
          entrez_query="Mus musculus[Organism]"  # Restrict to mouse
      )
      
      blast_record = NCBIXML.read(result_handle)
      
      # Find best hit
      if blast_record.alignments:
          best_hit = blast_record.alignments[0]
          print(f"Potential ortholog: {best_hit.title}")
          print(f"Accession: {best_hit.accession}")
      ```
      
      ### Batch BLAST Multiple Sequences
      
      ```python
      from Bio.Blast import NCBIWWW, NCBIXML
      from Bio import SeqIO
      
      # Read multiple sequences
      sequences = list(SeqIO.parse("queries.fasta", "fasta"))
      
      # Create batch results file
      with open("batch_results.xml", "w") as out_file:
          for seq_record in sequences:
              print(f"Searching for {seq_record.id}...")
      
              result_handle = NCBIWWW.qblast("blastn", "nt", str(seq_record.seq))
              out_file.write(result_handle.read())
              result_handle.close()
      
      # Parse batch results
      with open("batch_results.xml") as result_handle:
          for blast_record in NCBIXML.parse(result_handle):
              print(f"\n{blast_record.query}: {len(blast_record.alignments)} hits")
      ```
      
      ### Reciprocal Best Hits
      
      ```python
      def reciprocal_best_hit(seq1_id, seq2_id, database="nr", program="blastp"):
          """Check if two sequences are reciprocal best hits."""
          from Bio.Blast import NCBIWWW, NCBIXML
          from Bio import Entrez
      
          Entrez.email = "your.email@example.com"
      
          # Forward BLAST
          result1 = NCBIWWW.qblast(program, database, seq1_id)
          record1 = NCBIXML.read(result1)
          best_hit1 = record1.alignments[0].accession if record1.alignments else None
      
          # Reverse BLAST
          result2 = NCBIWWW.qblast(program, database, seq2_id)
          record2 = NCBIXML.read(result2)
          best_hit2 = record2.alignments[0].accession if record2.alignments else None
      
          # Check reciprocity
          return best_hit1 == seq2_id and best_hit2 == seq1_id
      ```
      
      ## Error Handling
      
      ```python
      from Bio.Blast import NCBIWWW, NCBIXML
      from urllib.error import HTTPError
      
      try:
          result_handle = NCBIWWW.qblast("blastn", "nt", "ATCGATCGATCG")
          blast_record = NCBIXML.read(result_handle)
          result_handle.close()
      except HTTPError as e:
          print(f"HTTP Error: {e.code}")
      except Exception as e:
          print(f"Error running BLAST: {e}")
      ```
      
    • databases.md 11.2 KB
      # Database Access with Bio.Entrez
      
      ## Overview
      
      Bio.Entrez provides programmatic access to NCBI's Entrez databases, including PubMed, GenBank, Gene, Protein, Nucleotide, and many others. It handles all the complexity of API calls, rate limiting, and data parsing.
      
      ## Setup and Configuration
      
      ### Email Address (Required)
      
      NCBI requires an email address to track usage and contact users if issues arise:
      
      ```python
      from Bio import Entrez
      
      # Always set your email
      Entrez.email = "your.email@example.com"
      ```
      
      ### API Key (Recommended)
      
      Using an API key increases rate limits from 3 to 10 requests per second:
      
      ```python
      # Get API key from: https://www.ncbi.nlm.nih.gov/account/settings/
      Entrez.api_key = "your_api_key_here"
      ```
      
      ### Rate Limiting
      
      Biopython automatically respects NCBI rate limits:
      - **Without API key**: 3 requests per second
      - **With API key**: 10 requests per second
      
      The module handles this automatically, so you don't need to add delays between requests.
      
      ## Core Entrez Functions
      
      ### EInfo - Database Information
      
      Get information about available databases and their statistics:
      
      ```python
      # List all databases
      handle = Entrez.einfo()
      result = Entrez.read(handle)
      print(result["DbList"])
      
      # Get information about a specific database
      handle = Entrez.einfo(db="pubmed")
      result = Entrez.read(handle)
      print(result["DbInfo"]["Description"])
      print(result["DbInfo"]["Count"])  # Number of records
      ```
      
      ### ESearch - Search Databases
      
      Search for records and retrieve their IDs:
      
      ```python
      # Search PubMed
      handle = Entrez.esearch(db="pubmed", term="biopython")
      result = Entrez.read(handle)
      handle.close()
      
      id_list = result["IdList"]
      count = result["Count"]
      print(f"Found {count} results")
      print(f"Retrieved IDs: {id_list}")
      ```
      
      ### Advanced ESearch Parameters
      
      ```python
      # Search with additional parameters
      handle = Entrez.esearch(
          db="pubmed",
          term="biopython[Title]",
          retmax=100,           # Return up to 100 IDs
          sort="relevance",     # Sort by relevance
          reldate=365,          # Only results from last year
          datetype="pdat"       # Use publication date
      )
      result = Entrez.read(handle)
      handle.close()
      ```
      
      ### ESummary - Get Record Summaries
      
      Retrieve summary information for a list of IDs:
      
      ```python
      # Get summaries for multiple records
      handle = Entrez.esummary(db="pubmed", id="19304878,18606172")
      results = Entrez.read(handle)
      handle.close()
      
      for record in results:
          print(f"Title: {record['Title']}")
          print(f"Authors: {record['AuthorList']}")
          print(f"Journal: {record['Source']}")
          print()
      ```
      
      ### EFetch - Retrieve Full Records
      
      Fetch complete records in various formats:
      
      ```python
      # Fetch a GenBank record
      handle = Entrez.efetch(db="nucleotide", id="EU490707", rettype="gb", retmode="text")
      record_text = handle.read()
      handle.close()
      
      # Parse with SeqIO
      from Bio import SeqIO
      handle = Entrez.efetch(db="nucleotide", id="EU490707", rettype="gb", retmode="text")
      record = SeqIO.read(handle, "genbank")
      handle.close()
      print(record.description)
      ```
      
      ### EFetch Return Types
      
      Different databases support different return types:
      
      **Nucleotide/Protein:**
      - `rettype="fasta"` - FASTA format
      - `rettype="gb"` or `"genbank"` - GenBank format
      - `rettype="gp"` - GenPept format (proteins)
      
      **PubMed:**
      - `rettype="medline"` - MEDLINE format
      - `rettype="abstract"` - Abstract text
      
      **Common modes:**
      - `retmode="text"` - Plain text
      - `retmode="xml"` - XML format
      
      ### ELink - Find Related Records
      
      Find links between records in different databases:
      
      ```python
      # Find protein records linked to a nucleotide record
      handle = Entrez.elink(dbfrom="nucleotide", db="protein", id="EU490707")
      result = Entrez.read(handle)
      handle.close()
      
      # Extract linked IDs
      for linkset in result[0]["LinkSetDb"]:
          if linkset["LinkName"] == "nucleotide_protein":
              protein_ids = [link["Id"] for link in linkset["Link"]]
              print(f"Linked protein IDs: {protein_ids}")
      ```
      
      ### EPost - Upload ID Lists
      
      Upload large lists of IDs to the server for later use:
      
      ```python
      # Post IDs to server
      id_list = ["19304878", "18606172", "16403221"]
      handle = Entrez.epost(db="pubmed", id=",".join(id_list))
      result = Entrez.read(handle)
      handle.close()
      
      # Get query_key and WebEnv for later use
      query_key = result["QueryKey"]
      webenv = result["WebEnv"]
      
      # Use in subsequent queries
      handle = Entrez.efetch(
          db="pubmed",
          query_key=query_key,
          WebEnv=webenv,
          rettype="medline",
          retmode="text"
      )
      ```
      
      ### EGQuery - Global Query
      
      Search across all Entrez databases at once:
      
      ```python
      handle = Entrez.egquery(term="biopython")
      result = Entrez.read(handle)
      handle.close()
      
      for row in result["eGQueryResult"]:
          print(f"{row['DbName']}: {row['Count']} results")
      ```
      
      ### ESpell - Spelling Suggestions
      
      Get spelling suggestions for search terms:
      
      ```python
      handle = Entrez.espell(db="pubmed", term="biopythn")
      result = Entrez.read(handle)
      handle.close()
      
      print(f"Original: {result['Query']}")
      print(f"Suggestion: {result['CorrectedQuery']}")
      ```
      
      ## Working with Different Databases
      
      ### PubMed
      
      ```python
      # Search for articles
      handle = Entrez.esearch(db="pubmed", term="cancer genomics", retmax=10)
      result = Entrez.read(handle)
      handle.close()
      
      # Fetch abstracts
      handle = Entrez.efetch(
          db="pubmed",
          id=result["IdList"],
          rettype="medline",
          retmode="text"
      )
      records = handle.read()
      handle.close()
      print(records)
      ```
      
      ### GenBank / Nucleotide
      
      ```python
      # Search for sequences
      handle = Entrez.esearch(db="nucleotide", term="Cypripedioideae[Orgn] AND matK[Gene]")
      result = Entrez.read(handle)
      handle.close()
      
      # Fetch sequences
      if result["IdList"]:
          handle = Entrez.efetch(
              db="nucleotide",
              id=result["IdList"][:5],
              rettype="fasta",
              retmode="text"
          )
          sequences = handle.read()
          handle.close()
      ```
      
      ### Protein
      
      ```python
      # Search for protein sequences
      handle = Entrez.esearch(db="protein", term="human insulin")
      result = Entrez.read(handle)
      handle.close()
      
      # Fetch protein records
      from Bio import SeqIO
      handle = Entrez.efetch(
          db="protein",
          id=result["IdList"][:5],
          rettype="gp",
          retmode="text"
      )
      records = SeqIO.parse(handle, "genbank")
      for record in records:
          print(f"{record.id}: {record.description}")
      handle.close()
      ```
      
      ### Gene
      
      ```python
      # Search for gene records
      handle = Entrez.esearch(db="gene", term="BRCA1[Gene] AND human[Organism]")
      result = Entrez.read(handle)
      handle.close()
      
      # Get gene information
      handle = Entrez.efetch(db="gene", id=result["IdList"][0], retmode="xml")
      record = Entrez.read(handle)
      handle.close()
      ```
      
      ### Taxonomy
      
      ```python
      # Search for organism
      handle = Entrez.esearch(db="taxonomy", term="Homo sapiens")
      result = Entrez.read(handle)
      handle.close()
      
      # Fetch taxonomic information
      handle = Entrez.efetch(db="taxonomy", id=result["IdList"][0], retmode="xml")
      records = Entrez.read(handle)
      handle.close()
      
      for record in records:
          print(f"TaxID: {record['TaxId']}")
          print(f"Scientific Name: {record['ScientificName']}")
          print(f"Lineage: {record['Lineage']}")
      ```
      
      ## Parsing Entrez Results
      
      ### Reading XML Results
      
      ```python
      # Most results can be parsed with Entrez.read()
      handle = Entrez.efetch(db="pubmed", id="19304878", retmode="xml")
      records = Entrez.read(handle)
      handle.close()
      
      # Access parsed data
      article = records['PubmedArticle'][0]['MedlineCitation']['Article']
      print(article['ArticleTitle'])
      ```
      
      ### Handling Large Result Sets
      
      ```python
      # Batch processing for large searches
      search_term = "cancer[Title]"
      handle = Entrez.esearch(db="pubmed", term=search_term, retmax=0)
      result = Entrez.read(handle)
      handle.close()
      
      total_count = int(result["Count"])
      batch_size = 500
      
      for start in range(0, total_count, batch_size):
          # Fetch batch
          handle = Entrez.esearch(
              db="pubmed",
              term=search_term,
              retstart=start,
              retmax=batch_size
          )
          result = Entrez.read(handle)
          handle.close()
      
          # Process IDs
          id_list = result["IdList"]
          print(f"Processing IDs {start} to {start + len(id_list)}")
      ```
      
      ## Advanced Patterns
      
      ### Search History with WebEnv
      
      ```python
      # Perform search and store on server
      handle = Entrez.esearch(
          db="pubmed",
          term="biopython",
          usehistory="y"
      )
      result = Entrez.read(handle)
      handle.close()
      
      webenv = result["WebEnv"]
      query_key = result["QueryKey"]
      count = int(result["Count"])
      
      # Fetch results in batches using history
      batch_size = 100
      for start in range(0, count, batch_size):
          handle = Entrez.efetch(
              db="pubmed",
              retstart=start,
              retmax=batch_size,
              rettype="medline",
              retmode="text",
              webenv=webenv,
              query_key=query_key
          )
          data = handle.read()
          handle.close()
          # Process data
      ```
      
      ### Combining Searches
      
      ```python
      # Use boolean operators
      complex_search = "(cancer[Title]) AND (genomics[Title]) AND 2020:2025[PDAT]"
      handle = Entrez.esearch(db="pubmed", term=complex_search, retmax=100)
      result = Entrez.read(handle)
      handle.close()
      ```
      
      ## Best Practices
      
      1. **Always set Entrez.email** - Required by NCBI
      2. **Use API key** for higher rate limits (10 req/s vs 3 req/s)
      3. **Close handles** after reading to free resources
      4. **Batch large requests** - Use retstart and retmax for pagination
      5. **Use WebEnv for large downloads** - Store results on server
      6. **Cache locally** - Download once and save to avoid repeated requests
      7. **Handle errors gracefully** - Network issues and API limits can occur
      8. **Respect NCBI guidelines** - Don't overwhelm the service
      9. **Use appropriate rettype** - Choose format that matches your needs
      10. **Parse XML carefully** - Structure varies by database and record type
      
      ## Error Handling
      
      ```python
      from urllib.error import HTTPError
      from Bio import Entrez
      
      Entrez.email = "your.email@example.com"
      
      try:
          handle = Entrez.efetch(db="nucleotide", id="invalid_id", rettype="gb")
          record = handle.read()
          handle.close()
      except HTTPError as e:
          print(f"HTTP Error: {e.code} - {e.reason}")
      except Exception as e:
          print(f"Error: {e}")
      ```
      
      ## Common Use Cases
      
      ### Download GenBank Records
      
      ```python
      from Bio import Entrez, SeqIO
      
      Entrez.email = "your.email@example.com"
      
      # List of accession numbers
      accessions = ["EU490707", "EU490708", "EU490709"]
      
      for acc in accessions:
          handle = Entrez.efetch(db="nucleotide", id=acc, rettype="gb", retmode="text")
          record = SeqIO.read(handle, "genbank")
          handle.close()
      
          # Save to file
          SeqIO.write(record, f"{acc}.gb", "genbank")
      ```
      
      ### Search and Download Papers
      
      ```python
      # Search PubMed
      handle = Entrez.esearch(db="pubmed", term="machine learning bioinformatics", retmax=20)
      result = Entrez.read(handle)
      handle.close()
      
      # Get details
      handle = Entrez.efetch(db="pubmed", id=result["IdList"], retmode="xml")
      papers = Entrez.read(handle)
      handle.close()
      
      # Extract information
      for paper in papers['PubmedArticle']:
          article = paper['MedlineCitation']['Article']
          print(f"Title: {article['ArticleTitle']}")
          print(f"Journal: {article['Journal']['Title']}")
          print()
      ```
      
      ### Find Related Sequences
      
      ```python
      # Start with one sequence
      handle = Entrez.efetch(db="nucleotide", id="EU490707", rettype="gb", retmode="text")
      record = SeqIO.read(handle, "genbank")
      handle.close()
      
      # Find similar sequences
      handle = Entrez.elink(dbfrom="nucleotide", db="nucleotide", id="EU490707")
      result = Entrez.read(handle)
      handle.close()
      
      # Get related IDs
      related_ids = []
      for linkset in result[0]["LinkSetDb"]:
          for link in linkset["Link"]:
              related_ids.append(link["Id"])
      ```
      
    • phylogenetics.md 13.7 KB
      # Phylogenetics with Bio.Phylo
      
      ## Overview
      
      Bio.Phylo provides a unified toolkit for reading, writing, analyzing, and visualizing phylogenetic trees. It supports multiple file formats including Newick, NEXUS, phyloXML, NeXML, and CDAO.
      
      ## Supported File Formats
      
      - **Newick** - Simple tree representation (most common)
      - **NEXUS** - Extended format with additional data
      - **phyloXML** - XML-based format with rich annotations
      - **NeXML** - Modern XML format
      - **CDAO** - Comparative Data Analysis Ontology
      
      ## Reading and Writing Trees
      
      ### Reading Trees
      
      ```python
      from Bio import Phylo
      
      # Read a tree from file
      tree = Phylo.read("tree.nwk", "newick")
      
      # Parse multiple trees from a file
      trees = list(Phylo.parse("trees.nwk", "newick"))
      print(f"Found {len(trees)} trees")
      ```
      
      ### Writing Trees
      
      ```python
      # Write tree to file
      Phylo.write(tree, "output.nwk", "newick")
      
      # Write multiple trees
      Phylo.write(trees, "output.nex", "nexus")
      ```
      
      ### Format Conversion
      
      ```python
      # Convert between formats
      count = Phylo.convert("input.nwk", "newick", "output.xml", "phyloxml")
      print(f"Converted {count} trees")
      ```
      
      ## Tree Structure and Navigation
      
      ### Basic Tree Components
      
      Trees consist of:
      - **Clade** - A node (internal or terminal) in the tree
      - **Terminal clades** - Leaves/tips (taxa)
      - **Internal clades** - Internal nodes
      - **Branch length** - Evolutionary distance
      
      ### Accessing Tree Properties
      
      ```python
      # Tree root
      root = tree.root
      
      # Terminal nodes (leaves)
      terminals = tree.get_terminals()
      print(f"Number of taxa: {len(terminals)}")
      
      # Non-terminal nodes
      nonterminals = tree.get_nonterminals()
      print(f"Number of internal nodes: {len(nonterminals)}")
      
      # All clades
      all_clades = list(tree.find_clades())
      print(f"Total clades: {len(all_clades)}")
      ```
      
      ### Traversing Trees
      
      ```python
      # Iterate through all clades
      for clade in tree.find_clades():
          if clade.name:
              print(f"Clade: {clade.name}, Branch length: {clade.branch_length}")
      
      # Iterate through terminals only
      for terminal in tree.get_terminals():
          print(f"Taxon: {terminal.name}")
      
      # Depth-first traversal
      for clade in tree.find_clades(order="preorder"):
          print(clade.name)
      
      # Level-order (breadth-first) traversal
      for clade in tree.find_clades(order="level"):
          print(clade.name)
      ```
      
      ### Finding Specific Clades
      
      ```python
      # Find clade by name
      clade = tree.find_any(name="Species_A")
      
      # Find all clades matching criteria
      def is_long_branch(clade):
          return clade.branch_length and clade.branch_length > 0.5
      
      long_branches = tree.find_clades(is_long_branch)
      ```
      
      ## Tree Analysis
      
      ### Tree Statistics
      
      ```python
      # Total branch length
      total_length = tree.total_branch_length()
      print(f"Total tree length: {total_length:.3f}")
      
      # Tree depth (root to furthest leaf)
      depths = tree.depths()
      max_depth = max(depths.values())
      print(f"Maximum depth: {max_depth:.3f}")
      
      # Terminal count
      terminal_count = tree.count_terminals()
      print(f"Number of taxa: {terminal_count}")
      ```
      
      ### Distance Calculations
      
      ```python
      # Distance between two taxa
      distance = tree.distance("Species_A", "Species_B")
      print(f"Distance: {distance:.3f}")
      
      # Create distance matrix
      from Bio import Phylo
      
      terminals = tree.get_terminals()
      taxa_names = [t.name for t in terminals]
      
      print("Distance Matrix:")
      for taxon1 in taxa_names:
          row = []
          for taxon2 in taxa_names:
              if taxon1 == taxon2:
                  row.append(0)
              else:
                  dist = tree.distance(taxon1, taxon2)
                  row.append(dist)
          print(f"{taxon1}: {row}")
      ```
      
      ### Common Ancestors
      
      ```python
      # Find common ancestor of two clades
      clade1 = tree.find_any(name="Species_A")
      clade2 = tree.find_any(name="Species_B")
      ancestor = tree.common_ancestor(clade1, clade2)
      print(f"Common ancestor: {ancestor.name}")
      
      # Find common ancestor of multiple clades
      clades = [tree.find_any(name=n) for n in ["Species_A", "Species_B", "Species_C"]]
      ancestor = tree.common_ancestor(*clades)
      ```
      
      ### Tree Comparison
      
      ```python
      # Compare tree topologies
      def compare_trees(tree1, tree2):
          """Compare two trees."""
          # Get terminal names
          taxa1 = set(t.name for t in tree1.get_terminals())
          taxa2 = set(t.name for t in tree2.get_terminals())
      
          # Check if they have same taxa
          if taxa1 != taxa2:
              return False, "Different taxa"
      
          # Compare distances
          differences = []
          for taxon1 in taxa1:
              for taxon2 in taxa1:
                  if taxon1 < taxon2:
                      dist1 = tree1.distance(taxon1, taxon2)
                      dist2 = tree2.distance(taxon1, taxon2)
                      if abs(dist1 - dist2) > 0.01:
                          differences.append((taxon1, taxon2, dist1, dist2))
      
          return len(differences) == 0, differences
      ```
      
      ## Tree Manipulation
      
      ### Pruning Trees
      
      ```python
      # Prune (remove) specific taxa
      tree_copy = tree.copy()
      tree_copy.prune("Species_A")
      
      # Keep only specific taxa
      taxa_to_keep = ["Species_B", "Species_C", "Species_D"]
      terminals = tree_copy.get_terminals()
      for terminal in terminals:
          if terminal.name not in taxa_to_keep:
              tree_copy.prune(terminal)
      ```
      
      ### Collapsing Short Branches
      
      ```python
      # Collapse branches shorter than threshold
      def collapse_short_branches(tree, threshold=0.01):
          """Collapse branches shorter than threshold."""
          for clade in tree.find_clades():
              if clade.branch_length and clade.branch_length < threshold:
                  clade.branch_length = 0
          return tree
      ```
      
      ### Ladderizing Trees
      
      ```python
      # Ladderize tree (sort branches by size)
      tree.ladderize()  # ascending order
      tree.ladderize(reverse=True)  # descending order
      ```
      
      ### Rerooting Trees
      
      ```python
      # Reroot at midpoint
      tree.root_at_midpoint()
      
      # Reroot with outgroup
      outgroup = tree.find_any(name="Outgroup_Species")
      tree.root_with_outgroup(outgroup)
      
      # Reroot at internal node
      internal = tree.get_nonterminals()[0]
      tree.root_with_outgroup(internal)
      ```
      
      ## Tree Visualization
      
      ### Basic ASCII Drawing
      
      ```python
      # Draw tree to console
      Phylo.draw_ascii(tree)
      
      # Draw with custom format
      Phylo.draw_ascii(tree, column_width=80)
      ```
      
      ### Matplotlib Visualization
      
      ```python
      import matplotlib.pyplot as plt
      from Bio import Phylo
      
      # Simple plot
      fig = plt.figure(figsize=(10, 8))
      axes = fig.add_subplot(1, 1, 1)
      Phylo.draw(tree, axes=axes)
      plt.show()
      
      # Customize plot
      fig = plt.figure(figsize=(10, 8))
      axes = fig.add_subplot(1, 1, 1)
      Phylo.draw(tree, axes=axes, do_show=False)
      axes.set_title("Phylogenetic Tree")
      plt.tight_layout()
      plt.savefig("tree.png", dpi=300)
      ```
      
      ### Advanced Visualization Options
      
      ```python
      # Radial (circular) tree
      Phylo.draw(tree, branch_labels=lambda c: c.branch_length)
      
      # Show branch support values
      Phylo.draw(tree, label_func=lambda n: str(n.confidence) if n.confidence else "")
      
      # Color branches
      def color_by_length(clade):
          if clade.branch_length:
              if clade.branch_length > 0.5:
                  return "red"
              elif clade.branch_length > 0.2:
                  return "orange"
          return "black"
      
      # Note: Direct branch coloring requires custom matplotlib code
      ```
      
      ## Building Trees
      
      ### From Distance Matrix
      
      ```python
      from Bio.Phylo.TreeConstruction import DistanceTreeConstructor, DistanceMatrix
      
      # Create distance matrix. The matrix must be lower-triangular *including the
      # diagonal* -- row i has i+1 entries and ends in 0.0 (the self-distance).
      # Omitting the diagonal zeros raises "'matrix' should be in lower triangle format".
      dm = DistanceMatrix(
          names=["Alpha", "Beta", "Gamma", "Delta"],
          matrix=[
              [0.0],
              [0.23, 0.0],
              [0.45, 0.34, 0.0],
              [0.67, 0.58, 0.29, 0.0]
          ]
      )
      
      # Build tree using UPGMA
      constructor = DistanceTreeConstructor()
      tree = constructor.upgma(dm)
      Phylo.draw_ascii(tree)
      
      # Build tree using Neighbor-Joining
      tree = constructor.nj(dm)
      ```
      
      ### From Multiple Sequence Alignment
      
      ```python
      from Bio import AlignIO, Phylo
      from Bio.Phylo.TreeConstruction import DistanceCalculator, DistanceTreeConstructor
      
      # Read alignment
      alignment = AlignIO.read("alignment.fasta", "fasta")
      
      # Calculate distance matrix
      calculator = DistanceCalculator("identity")
      distance_matrix = calculator.get_distance(alignment)
      
      # Build tree
      constructor = DistanceTreeConstructor()
      tree = constructor.upgma(distance_matrix)
      
      # Write tree
      Phylo.write(tree, "output_tree.nwk", "newick")
      ```
      
      ### Distance Models
      
      Available distance calculation models:
      - **identity** - Simple identity
      - **blastn** - BLASTN identity
      - **trans** - Transition/transversion ratio
      - **blosum62** - BLOSUM62 matrix
      - **pam250** - PAM250 matrix
      
      ```python
      # Use different model
      calculator = DistanceCalculator("blosum62")
      dm = calculator.get_distance(alignment)
      ```
      
      ## Consensus Trees
      
      ```python
      from Bio.Phylo.Consensus import majority_consensus, strict_consensus
      
      # Read multiple trees
      trees = list(Phylo.parse("bootstrap_trees.nwk", "newick"))
      
      # Majority-rule consensus
      consensus = majority_consensus(trees, cutoff=0.5)
      
      # Strict consensus
      strict_cons = strict_consensus(trees)
      
      # Write consensus tree
      Phylo.write(consensus, "consensus.nwk", "newick")
      ```
      
      ## PhyloXML Features
      
      PhyloXML format supports rich annotations:
      
      ```python
      from Bio.Phylo.PhyloXML import Phylogeny, Clade
      
      # Create PhyloXML tree
      tree = Phylogeny(rooted=True)
      tree.name = "Example Tree"
      tree.description = "A sample phylogenetic tree"
      
      # Add clades with rich annotations
      clade = Clade(branch_length=0.5)
      clade.name = "Species_A"
      clade.color = "red"
      clade.width = 2.0
      
      # Add taxonomy information
      from Bio.Phylo.PhyloXML import Taxonomy
      taxonomy = Taxonomy(scientific_name="Homo sapiens", common_name="Human")
      clade.taxonomies.append(taxonomy)
      ```
      
      ## Bootstrap Support
      
      ```python
      # Add bootstrap support values to tree
      def add_bootstrap_support(tree, support_values):
          """Add bootstrap support to internal nodes."""
          internal_nodes = tree.get_nonterminals()
          for node, support in zip(internal_nodes, support_values):
              node.confidence = support
          return tree
      
      # Example
      support_values = [95, 87, 76, 92]
      tree_with_support = add_bootstrap_support(tree, support_values)
      ```
      
      ## Best Practices
      
      1. **Choose appropriate file format** - Newick for simple trees, phyloXML for annotations
      2. **Validate tree topology** - Check for polytomies and negative branch lengths
      3. **Root trees appropriately** - Use midpoint or outgroup rooting
      4. **Handle bootstrap values** - Store as clade confidence
      5. **Consider tree size** - Large trees may need special handling
      6. **Use tree copies** - Call `.copy()` before modifications
      7. **Export publication-ready figures** - Use matplotlib for high-quality output
      8. **Document tree construction** - Record alignment and parameters used
      9. **Compare multiple trees** - Use consensus methods for bootstrap trees
      10. **Validate taxon names** - Ensure consistent naming across files
      
      ## Common Use Cases
      
      ### Build Tree from Sequences
      
      ```python
      from Bio import AlignIO, Phylo
      from Bio.Phylo.TreeConstruction import DistanceCalculator, DistanceTreeConstructor
      
      # Read aligned sequences
      alignment = AlignIO.read("sequences.aln", "clustal")
      
      # Calculate distances
      calculator = DistanceCalculator("identity")
      dm = calculator.get_distance(alignment)
      
      # Build neighbor-joining tree
      constructor = DistanceTreeConstructor()
      tree = constructor.nj(dm)
      
      # Root at midpoint
      tree.root_at_midpoint()
      
      # Save tree
      Phylo.write(tree, "tree.nwk", "newick")
      
      # Visualize
      import matplotlib.pyplot as plt
      fig = plt.figure(figsize=(10, 8))
      Phylo.draw(tree)
      plt.show()
      ```
      
      ### Extract Subtree
      
      ```python
      def extract_subtree(tree, taxa_list):
          """Extract subtree containing specific taxa."""
          # Create a copy
          subtree = tree.copy()
      
          # Get all terminals
          all_terminals = subtree.get_terminals()
      
          # Prune taxa not in list
          for terminal in all_terminals:
              if terminal.name not in taxa_list:
                  subtree.prune(terminal)
      
          return subtree
      
      # Use it
      subtree = extract_subtree(tree, ["Species_A", "Species_B", "Species_C"])
      Phylo.write(subtree, "subtree.nwk", "newick")
      ```
      
      ### Calculate Phylogenetic Diversity
      
      ```python
      def phylogenetic_diversity(tree, taxa_subset=None):
          """Calculate phylogenetic diversity (sum of branch lengths)."""
          if taxa_subset:
              # Prune to subset
              tree = extract_subtree(tree, taxa_subset)
      
          # Sum all branch lengths
          total = 0
          for clade in tree.find_clades():
              if clade.branch_length:
                  total += clade.branch_length
      
          return total
      
      # Calculate PD for all taxa
      pd_all = phylogenetic_diversity(tree)
      print(f"Total phylogenetic diversity: {pd_all:.3f}")
      
      # Calculate PD for subset
      pd_subset = phylogenetic_diversity(tree, ["Species_A", "Species_B"])
      print(f"Subset phylogenetic diversity: {pd_subset:.3f}")
      ```
      
      ### Annotate Tree with External Data
      
      ```python
      def annotate_tree_from_csv(tree, csv_file):
          """Annotate tree leaves with data from CSV."""
          import csv
      
          # Read annotation data
          annotations = {}
          with open(csv_file) as f:
              reader = csv.DictReader(f)
              for row in reader:
                  annotations[row["species"]] = row
      
          # Annotate tree
          for terminal in tree.get_terminals():
              if terminal.name in annotations:
                  # Add custom attributes
                  for key, value in annotations[terminal.name].items():
                      setattr(terminal, key, value)
      
          return tree
      ```
      
      ### Compare Tree Topologies
      
      ```python
      def robinson_foulds_distance(tree1, tree2):
          """Calculate Robinson-Foulds distance between two trees."""
          # Get bipartitions for each tree
          def get_bipartitions(tree):
              bipartitions = set()
              for clade in tree.get_nonterminals():
                  terminals = frozenset(t.name for t in clade.get_terminals())
                  bipartitions.add(terminals)
              return bipartitions
      
          bp1 = get_bipartitions(tree1)
          bp2 = get_bipartitions(tree2)
      
          # Symmetric difference
          diff = len(bp1.symmetric_difference(bp2))
          return diff
      
      # Use it
      tree1 = Phylo.read("tree1.nwk", "newick")
      tree2 = Phylo.read("tree2.nwk", "newick")
      rf_dist = robinson_foulds_distance(tree1, tree2)
      print(f"Robinson-Foulds distance: {rf_dist}")
      ```
      
    • sequence_io.md 7.1 KB
      # Sequence Handling with Bio.Seq and Bio.SeqIO
      
      ## Overview
      
      Bio.Seq provides the `Seq` object for biological sequences with specialized methods, while Bio.SeqIO offers a unified interface for reading, writing, and converting sequence files across multiple formats.
      
      ## The Seq Object
      
      ### Creating Sequences
      
      ```python
      from Bio.Seq import Seq
      
      # Create a basic sequence
      my_seq = Seq("AGTACACTGGT")
      
      # Sequences support string-like operations
      print(len(my_seq))  # Length
      print(my_seq[0:5])  # Slicing
      ```
      
      ### Core Sequence Operations
      
      ```python
      # Complement and reverse complement
      complement = my_seq.complement()
      rev_comp = my_seq.reverse_complement()
      
      # Transcription (DNA to RNA)
      rna = my_seq.transcribe()
      
      # Translation (to protein)
      protein = my_seq.translate()
      
      # Back-transcription (RNA to DNA)
      dna = rna_seq.back_transcribe()
      ```
      
      ### Sequence Methods
      
      - `complement()` - Returns complementary strand
      - `reverse_complement()` - Returns reverse complement
      - `transcribe()` - DNA to RNA transcription
      - `back_transcribe()` - RNA to DNA conversion
      - `translate()` - Translate to protein sequence
      - `translate(table=N)` - Use specific genetic code table
      - `translate(to_stop=True)` - Stop at first stop codon
      
      ## Bio.SeqIO: Sequence File I/O
      
      ### Core Functions
      
      **Bio.SeqIO.parse()**: The primary workhorse for reading sequence files as an iterator of `SeqRecord` objects.
      
      ```python
      from Bio import SeqIO
      
      # Parse a FASTA file
      for record in SeqIO.parse("sequences.fasta", "fasta"):
          print(record.id)
          print(record.seq)
          print(len(record))
      ```
      
      **Bio.SeqIO.read()**: For single-record files (validates exactly one record exists).
      
      ```python
      record = SeqIO.read("single.fasta", "fasta")
      ```
      
      **Bio.SeqIO.write()**: Output SeqRecord objects to files.
      
      ```python
      # Write records to file
      count = SeqIO.write(seq_records, "output.fasta", "fasta")
      print(f"Wrote {count} records")
      ```
      
      **Bio.SeqIO.convert()**: Streamlined format conversion.
      
      ```python
      # Convert between formats
      count = SeqIO.convert("input.gbk", "genbank", "output.fasta", "fasta")
      ```
      
      ### Supported File Formats
      
      Common formats include:
      - **fasta** - FASTA format
      - **fastq** - FASTQ format (with quality scores)
      - **genbank** or **gb** - GenBank format
      - **embl** - EMBL format
      - **swiss** - SwissProt format
      - **fasta-2line** - FASTA with sequence on one line
      - **tab** - Simple tab-separated format
      
      ### The SeqRecord Object
      
      `SeqRecord` objects combine sequence data with annotations:
      
      ```python
      record.id          # Primary identifier
      record.name        # Short name
      record.description # Description line
      record.seq         # The actual sequence (Seq object)
      record.annotations # Dictionary of additional info
      record.features    # List of SeqFeature objects
      record.letter_annotations  # Per-letter annotations (e.g., quality scores)
      ```
      
      ### Modifying Records
      
      ```python
      # Modify record attributes
      record.id = "new_id"
      record.description = "New description"
      
      # Extract subsequences
      sub_record = record[10:30]  # Slicing preserves annotations
      
      # Modify sequence
      record.seq = record.seq.reverse_complement()
      ```
      
      ## Working with Large Files
      
      ### Memory-Efficient Parsing
      
      Use iterators to avoid loading entire files into memory:
      
      ```python
      # Good for large files
      for record in SeqIO.parse("large_file.fasta", "fasta"):
          if len(record.seq) > 1000:
              print(record.id)
      ```
      
      ### Dictionary-Based Access
      
      Three approaches for random access:
      
      **1. Bio.SeqIO.to_dict()** - Loads all records into memory:
      
      ```python
      seq_dict = SeqIO.to_dict(SeqIO.parse("sequences.fasta", "fasta"))
      record = seq_dict["sequence_id"]
      ```
      
      **2. Bio.SeqIO.index()** - Lazy-loaded dictionary (memory efficient):
      
      ```python
      seq_index = SeqIO.index("sequences.fasta", "fasta")
      record = seq_index["sequence_id"]
      seq_index.close()
      ```
      
      **3. Bio.SeqIO.index_db()** - SQLite-based index for very large files:
      
      ```python
      seq_index = SeqIO.index_db("index.idx", "sequences.fasta", "fasta")
      record = seq_index["sequence_id"]
      seq_index.close()
      ```
      
      ### Low-Level Parsers for High Performance
      
      For high-throughput sequencing data, use low-level parsers that return tuples instead of objects:
      
      ```python
      from Bio.SeqIO.FastaIO import SimpleFastaParser
      
      with open("sequences.fasta") as handle:
          for title, sequence in SimpleFastaParser(handle):
              print(title, len(sequence))
      
      from Bio.SeqIO.QualityIO import FastqGeneralIterator
      
      with open("reads.fastq") as handle:
          for title, sequence, quality in FastqGeneralIterator(handle):
              print(title)
      ```
      
      ## Compressed Files
      
      Bio.SeqIO automatically handles compressed files:
      
      ```python
      # Works with gzip compression
      for record in SeqIO.parse("sequences.fasta.gz", "fasta"):
          print(record.id)
      
      # BGZF format for random access
      from Bio import bgzf
      with bgzf.open("sequences.fasta.bgz", "r") as handle:
          records = SeqIO.parse(handle, "fasta")
      ```
      
      ## Data Extraction Patterns
      
      ### Extract Specific Information
      
      ```python
      # Get all IDs
      ids = [record.id for record in SeqIO.parse("file.fasta", "fasta")]
      
      # Get sequences above length threshold
      long_seqs = [record for record in SeqIO.parse("file.fasta", "fasta")
                   if len(record.seq) > 500]
      
      # Extract organism from GenBank
      for record in SeqIO.parse("file.gbk", "genbank"):
          organism = record.annotations.get("organism", "Unknown")
          print(f"{record.id}: {organism}")
      ```
      
      ### Filter and Write
      
      ```python
      # Filter sequences by criteria
      long_sequences = (record for record in SeqIO.parse("input.fasta", "fasta")
                        if len(record) > 500)
      SeqIO.write(long_sequences, "filtered.fasta", "fasta")
      ```
      
      ## Best Practices
      
      1. **Use iterators** for large files rather than loading everything into memory
      2. **Prefer index()** for repeated random access to large files
      3. **Use index_db()** for millions of records or multi-file scenarios
      4. **Use low-level parsers** for high-throughput data when speed is critical
      5. **Download once, reuse locally** rather than repeated network access
      6. **Close indexed files** explicitly or use context managers
      7. **Validate input** before writing with SeqIO.write()
      8. **Use appropriate format strings** - always lowercase (e.g., "fasta", not "FASTA")
      
      ## Common Use Cases
      
      ### Format Conversion
      
      ```python
      # GenBank to FASTA
      SeqIO.convert("input.gbk", "genbank", "output.fasta", "fasta")
      
      # Multiple format conversion
      for fmt in ["fasta", "genbank", "embl"]:
          SeqIO.convert("input.fasta", "fasta", f"output.{fmt}", fmt)
      ```
      
      ### Quality Filtering (FASTQ)
      
      ```python
      from Bio import SeqIO
      
      good_reads = (record for record in SeqIO.parse("reads.fastq", "fastq")
                    if min(record.letter_annotations["phred_quality"]) >= 20)
      count = SeqIO.write(good_reads, "filtered.fastq", "fastq")
      ```
      
      ### Sequence Statistics
      
      ```python
      from Bio.SeqUtils import gc_fraction
      
      for record in SeqIO.parse("sequences.fasta", "fasta"):
          gc = gc_fraction(record.seq)
          print(f"{record.id}: GC={gc:.2%}, Length={len(record)}")
      ```
      
      ### Creating Records Programmatically
      
      ```python
      from Bio.Seq import Seq
      from Bio.SeqRecord import SeqRecord
      
      # Create a new record
      new_record = SeqRecord(
          Seq("ATGCGATCGATCG"),
          id="seq001",
          name="MySequence",
          description="Test sequence"
      )
      
      # Write to file
      SeqIO.write([new_record], "new.fasta", "fasta")
      ```
      
    • structure.md 12.7 KB
      # Structural Bioinformatics with Bio.PDB
      
      ## Overview
      
      Bio.PDB provides tools for working with macromolecular 3D structures from PDB and mmCIF files. The module uses the SMCRA (Structure/Model/Chain/Residue/Atom) architecture to represent protein structures hierarchically.
      
      ## SMCRA Architecture
      
      The Bio.PDB module organizes structures hierarchically:
      
      ```
      Structure
        └── Model       (multiple models for NMR structures)
            └── Chain   (e.g., chain A, B, C)
                └── Residue  (amino acids, nucleotides, heteroatoms)
                    └── Atom (individual atoms)
      ```
      
      ## Parsing Structure Files
      
      ### PDB Format
      
      ```python
      from Bio.PDB import PDBParser
      
      # Create parser
      parser = PDBParser(QUIET=True)  # QUIET=True suppresses warnings
      
      # Parse structure
      structure = parser.get_structure("1crn", "1crn.pdb")
      
      # Access basic information
      print(f"Structure ID: {structure.id}")
      print(f"Number of models: {len(structure)}")
      ```
      
      ### mmCIF Format
      
      mmCIF format is more modern and handles large structures better:
      
      ```python
      from Bio.PDB import MMCIFParser
      
      # Create parser
      parser = MMCIFParser(QUIET=True)
      
      # Parse structure
      structure = parser.get_structure("1crn", "1crn.cif")
      ```
      
      ### Download from PDB
      
      ```python
      from Bio.PDB import PDBList
      
      # Create PDB list object
      pdbl = PDBList()
      
      # Download PDB file
      pdbl.retrieve_pdb_file("1CRN", file_format="pdb", pdir="structures/")
      
      # Download mmCIF file
      pdbl.retrieve_pdb_file("1CRN", file_format="mmCif", pdir="structures/")
      
      # Download obsolete structure
      pdbl.retrieve_pdb_file("1CRN", obsolete=True, pdir="structures/")
      ```
      
      ## Navigating Structure Hierarchy
      
      ### Access Models
      
      ```python
      # Get first model
      model = structure[0]
      
      # Iterate through all models
      for model in structure:
          print(f"Model {model.id}")
      ```
      
      ### Access Chains
      
      ```python
      # Get specific chain
      chain = model["A"]
      
      # Iterate through all chains
      for chain in model:
          print(f"Chain {chain.id}")
      ```
      
      ### Access Residues
      
      ```python
      # Iterate through residues in a chain
      for residue in chain:
          print(f"Residue: {residue.resname} {residue.id[1]}")
      
      # Get specific residue by ID
      # Residue ID is tuple: (hetfield, sequence_id, insertion_code)
      residue = chain[(" ", 10, " ")]  # Standard amino acid at position 10
      ```
      
      ### Access Atoms
      
      ```python
      # Iterate through atoms in a residue
      for atom in residue:
          print(f"Atom: {atom.name}, Coordinates: {atom.coord}")
      
      # Get specific atom
      ca_atom = residue["CA"]  # Alpha carbon
      print(f"CA coordinates: {ca_atom.coord}")
      ```
      
      ### Alternative Access Patterns
      
      ```python
      # Direct access through hierarchy
      atom = structure[0]["A"][10]["CA"]
      
      # Get all atoms
      atoms = list(structure.get_atoms())
      print(f"Total atoms: {len(atoms)}")
      
      # Get all residues
      residues = list(structure.get_residues())
      
      # Get all chains
      chains = list(structure.get_chains())
      ```
      
      ## Working with Atom Coordinates
      
      ### Accessing Coordinates
      
      ```python
      # Get atom coordinates
      coord = atom.coord
      print(f"X: {coord[0]}, Y: {coord[1]}, Z: {coord[2]}")
      
      # Get B-factor (temperature factor)
      b_factor = atom.bfactor
      
      # Get occupancy
      occupancy = atom.occupancy
      
      # Get element
      element = atom.element
      ```
      
      ### Calculate Distances
      
      ```python
      from Bio.PDB import Vector
      
      # Calculate distance between two atoms
      atom1 = residue1["CA"]
      atom2 = residue2["CA"]
      
      distance = atom1 - atom2  # Returns distance in Angstroms
      print(f"Distance: {distance:.2f} Å")
      ```
      
      ### Calculate Angles
      
      ```python
      from Bio.PDB.vectors import calc_angle
      
      # Calculate angle between three atoms
      angle = calc_angle(
          atom1.get_vector(),
          atom2.get_vector(),
          atom3.get_vector()
      )
      print(f"Angle: {angle:.2f} radians")
      ```
      
      ### Calculate Dihedrals
      
      ```python
      from Bio.PDB.vectors import calc_dihedral
      
      # Calculate dihedral angle between four atoms
      dihedral = calc_dihedral(
          atom1.get_vector(),
          atom2.get_vector(),
          atom3.get_vector(),
          atom4.get_vector()
      )
      print(f"Dihedral: {dihedral:.2f} radians")
      ```
      
      ## Structure Analysis
      
      ### Secondary Structure (DSSP)
      
      DSSP assigns secondary structure to protein structures:
      
      ```python
      from Bio.PDB import DSSP, PDBParser
      
      # Parse structure
      parser = PDBParser()
      structure = parser.get_structure("1crn", "1crn.pdb")
      
      # Run DSSP (requires DSSP executable installed)
      model = structure[0]
      dssp = DSSP(model, "1crn.pdb")
      
      # Access results
      for residue_key in dssp:
          dssp_data = dssp[residue_key]
          residue_id = residue_key[1]
          ss = dssp_data[2]  # Secondary structure code
          phi = dssp_data[4]  # Phi angle
          psi = dssp_data[5]  # Psi angle
          print(f"Residue {residue_id}: {ss}, φ={phi:.1f}°, ψ={psi:.1f}°")
      ```
      
      Secondary structure codes:
      - `H` - Alpha helix
      - `B` - Beta bridge
      - `E` - Strand
      - `G` - 3-10 helix
      - `I` - Pi helix
      - `T` - Turn
      - `S` - Bend
      - `-` - Coil/loop
      
      ### Solvent Accessibility (DSSP)
      
      ```python
      # Get relative solvent accessibility
      for residue_key in dssp:
          acc = dssp[residue_key][3]  # Relative accessibility
          print(f"Residue {residue_key[1]}: {acc:.2f} relative accessibility")
      ```
      
      ### Neighbor Search
      
      Find nearby atoms efficiently:
      
      ```python
      from Bio.PDB import NeighborSearch
      
      # Get all atoms
      atoms = list(structure.get_atoms())
      
      # Create neighbor search object
      ns = NeighborSearch(atoms)
      
      # Find atoms within radius
      center_atom = structure[0]["A"][10]["CA"]
      nearby_atoms = ns.search(center_atom.coord, 5.0)  # 5 Å radius
      print(f"Found {len(nearby_atoms)} atoms within 5 Å")
      
      # Find residues within radius
      nearby_residues = ns.search(center_atom.coord, 5.0, level="R")
      
      # Find chains within radius
      nearby_chains = ns.search(center_atom.coord, 10.0, level="C")
      ```
      
      ### Contact Map
      
      ```python
      def calculate_contact_map(chain, distance_threshold=8.0):
          """Calculate residue-residue contact map."""
          residues = list(chain.get_residues())
          n = len(residues)
          contact_map = [[0] * n for _ in range(n)]
      
          for i, res1 in enumerate(residues):
              for j, res2 in enumerate(residues):
                  if i < j:
                      # Get CA atoms
                      if res1.has_id("CA") and res2.has_id("CA"):
                          dist = res1["CA"] - res2["CA"]
                          if dist < distance_threshold:
                              contact_map[i][j] = 1
                              contact_map[j][i] = 1
      
          return contact_map
      ```
      
      ## Structure Quality Assessment
      
      ### Ramachandran Plot Data
      
      ```python
      from Bio.PDB import Polypeptide
      
      def get_phi_psi(structure):
          """Extract phi and psi angles for Ramachandran plot."""
          phi_psi = []
      
          for model in structure:
              for chain in model:
                  polypeptides = Polypeptide.PPBuilder().build_peptides(chain)
                  for poly in polypeptides:
                      angles = poly.get_phi_psi_list()
                      for residue, (phi, psi) in zip(poly, angles):
                          if phi and psi:  # Skip None values
                              phi_psi.append((residue.resname, phi, psi))
      
          return phi_psi
      ```
      
      ### Check for Missing Atoms
      
      ```python
      def check_missing_atoms(structure):
          """Identify residues with missing atoms."""
          missing = []
      
          for residue in structure.get_residues():
              if residue.id[0] == " ":  # Standard amino acid
                  resname = residue.resname
      
                  # Expected backbone atoms
                  expected = ["N", "CA", "C", "O"]
      
                  for atom_name in expected:
                      if not residue.has_id(atom_name):
                          missing.append((residue.full_id, atom_name))
      
          return missing
      ```
      
      ## Structure Manipulation
      
      ### Select Specific Atoms
      
      ```python
      from Bio.PDB import Select
      
      class CASelect(Select):
          """Select only CA atoms."""
          def accept_atom(self, atom):
              return atom.name == "CA"
      
      class ChainASelect(Select):
          """Select only chain A."""
          def accept_chain(self, chain):
              return chain.id == "A"
      
      # Use with PDBIO
      from Bio.PDB import PDBIO
      
      io = PDBIO()
      io.set_structure(structure)
      io.save("ca_only.pdb", CASelect())
      io.save("chain_a.pdb", ChainASelect())
      ```
      
      ### Transform Structures
      
      ```python
      import numpy as np
      
      # Rotate structure
      from Bio.PDB.vectors import rotaxis
      
      # Define rotation axis and angle
      axis = Vector(1, 0, 0)  # X-axis
      angle = np.pi / 4  # 45 degrees
      
      # Create rotation matrix
      rotation = rotaxis(angle, axis)
      
      # Apply rotation to all atoms
      for atom in structure.get_atoms():
          atom.transform(rotation, Vector(0, 0, 0))
      ```
      
      ### Superimpose Structures
      
      ```python
      from Bio.PDB import Superimposer, PDBParser
      
      # Parse two structures
      parser = PDBParser()
      structure1 = parser.get_structure("ref", "reference.pdb")
      structure2 = parser.get_structure("mov", "mobile.pdb")
      
      # Get CA atoms from both structures
      ref_atoms = [atom for atom in structure1.get_atoms() if atom.name == "CA"]
      mov_atoms = [atom for atom in structure2.get_atoms() if atom.name == "CA"]
      
      # Superimpose
      super_imposer = Superimposer()
      super_imposer.set_atoms(ref_atoms, mov_atoms)
      
      # Apply transformation
      super_imposer.apply(structure2.get_atoms())
      
      # Get RMSD
      rmsd = super_imposer.rms
      print(f"RMSD: {rmsd:.2f} Å")
      
      # Save superimposed structure
      from Bio.PDB import PDBIO
      io = PDBIO()
      io.set_structure(structure2)
      io.save("superimposed.pdb")
      ```
      
      ## Writing Structure Files
      
      ### Save PDB Files
      
      ```python
      from Bio.PDB import PDBIO
      
      io = PDBIO()
      io.set_structure(structure)
      io.save("output.pdb")
      ```
      
      ### Save mmCIF Files
      
      ```python
      from Bio.PDB import MMCIFIO
      
      io = MMCIFIO()
      io.set_structure(structure)
      io.save("output.cif")
      ```
      
      ## Sequence from Structure
      
      ### Extract Sequence
      
      ```python
      from Bio.PDB import Polypeptide
      
      # Get polypeptides from structure
      ppb = Polypeptide.PPBuilder()
      
      for model in structure:
          for chain in model:
              for pp in ppb.build_peptides(chain):
                  sequence = pp.get_sequence()
                  print(f"Chain {chain.id}: {sequence}")
      ```
      
      ### Map to FASTA
      
      ```python
      from Bio import SeqIO
      from Bio.SeqRecord import SeqRecord
      
      # Extract sequences and create FASTA
      records = []
      ppb = Polypeptide.PPBuilder()
      
      for model in structure:
          for chain in model:
              for pp in ppb.build_peptides(chain):
                  seq_record = SeqRecord(
                      pp.get_sequence(),
                      id=f"{structure.id}_{chain.id}",
                      description=f"Chain {chain.id}"
                  )
                  records.append(seq_record)
      
      SeqIO.write(records, "structure_sequences.fasta", "fasta")
      ```
      
      ## Best Practices
      
      1. **Use mmCIF** for large structures and modern data
      2. **Set QUIET=True** to suppress parser warnings
      3. **Check for missing atoms** before analysis
      4. **Use NeighborSearch** for efficient spatial queries
      5. **Validate structure quality** with DSSP or Ramachandran analysis
      6. **Handle multiple models** appropriately (NMR structures)
      7. **Be aware of heteroatoms** - they have different residue IDs
      8. **Use Select classes** for targeted structure output
      9. **Cache downloaded structures** locally
      10. **Consider alternative conformations** - some residues have multiple positions
      
      ## Common Use Cases
      
      ### Calculate RMSD Between Structures
      
      ```python
      from Bio.PDB import PDBParser, Superimposer
      
      parser = PDBParser()
      structure1 = parser.get_structure("s1", "structure1.pdb")
      structure2 = parser.get_structure("s2", "structure2.pdb")
      
      # Get CA atoms
      atoms1 = [atom for atom in structure1[0]["A"].get_atoms() if atom.name == "CA"]
      atoms2 = [atom for atom in structure2[0]["A"].get_atoms() if atom.name == "CA"]
      
      # Ensure same number of atoms
      min_len = min(len(atoms1), len(atoms2))
      atoms1 = atoms1[:min_len]
      atoms2 = atoms2[:min_len]
      
      # Calculate RMSD
      sup = Superimposer()
      sup.set_atoms(atoms1, atoms2)
      print(f"RMSD: {sup.rms:.3f} Å")
      ```
      
      ### Find Binding Site Residues
      
      ```python
      def find_binding_site(structure, ligand_chain, ligand_res_id, distance=5.0):
          """Find residues near a ligand."""
          from Bio.PDB import NeighborSearch
      
          # Get ligand atoms
          ligand = structure[0][ligand_chain][ligand_res_id]
          ligand_atoms = list(ligand.get_atoms())
      
          # Get all protein atoms
          protein_atoms = []
          for chain in structure[0]:
              if chain.id != ligand_chain:
                  for residue in chain:
                      if residue.id[0] == " ":  # Standard residue
                          protein_atoms.extend(residue.get_atoms())
      
          # Find nearby atoms
          ns = NeighborSearch(protein_atoms)
          binding_site = set()
      
          for ligand_atom in ligand_atoms:
              nearby = ns.search(ligand_atom.coord, distance, level="R")
              binding_site.update(nearby)
      
          return list(binding_site)
      ```
      
      ### Calculate Center of Mass
      
      ```python
      import numpy as np
      
      def center_of_mass(entity):
          """Calculate center of mass for structure entity."""
          masses = []
          coords = []
      
          # Atomic masses (simplified)
          mass_dict = {"C": 12.0, "N": 14.0, "O": 16.0, "S": 32.0}
      
          for atom in entity.get_atoms():
              mass = mass_dict.get(atom.element, 12.0)
              masses.append(mass)
              coords.append(atom.coord)
      
          masses = np.array(masses)
          coords = np.array(coords)
      
          com = np.sum(coords * masses[:, np.newaxis], axis=0) / np.sum(masses)
          return com
      ```
      
  • SKILL.md 16.3 KB
    ---
    name: alterlab-biopython
    description: Manipulate biological sequences, parse FASTA/GenBank/PDB files, run phylogenetics, and access NCBI/PubMed programmatically via Biopython (Bio.SeqIO, Bio.Entrez, Bio.PDB, Bio.Blast). Use when scripting custom bioinformatics pipelines, batch-processing sequence files, automating BLAST, or fetching records from Entrez — for quick one-off database lookups use gget, for unified multi-service integration use bioservices. Part of the AlterLab Academic Skills suite.
    license: MIT
    allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*)
    compatibility: "Self-contained — runs under `uv run python` with Biopython installed (1.88 as of 2026-08; supports Python 3.10–3.14, with 3.10 support deprecated). NCBI Entrez access needs a contact email; an NCBI API key is optional (raises the rate limit from 3 to 10 req/s)."
    metadata:
        skill-author: AlterLab
        version: "1.1.0"
        last_updated: "2026-09-23"
    ---
    
    # Biopython: Computational Molecular Biology in Python
    
    ## Overview
    
    Biopython is a comprehensive set of freely available Python tools for biological computation. It provides functionality for sequence manipulation, file I/O, database access, structural bioinformatics, phylogenetics, and many other bioinformatics tasks. The current version is **Biopython 1.88** (August 2026), which supports Python 3.10–3.14 and requires NumPy.
    
    > **Removed / changed APIs to watch for**
    >
    > - **Command-line wrappers are gone.** `Bio.Application` and everything built on it —
    >   `Bio.Blast.Applications` (`Ncbiblastn/p/x…Commandline`, `NcbimakeblastdbCommandline`) and
    >   `Bio.Align.Applications` (`ClustalOmegaCommandline`, `MuscleCommandline`) — were
    >   deprecated in 1.78 and **removed in 1.86**. Call the executables through `subprocess`
    >   (see `references/blast.md` and `references/alignment.md`).
    > - **PairwiseAligner gap scores changed in 1.86.** The default gap score is now **-1**
    >   (was 0), so an aligner left at defaults returns far fewer, non-degenerate alignments.
    >   The gap attributes were also renamed to insertion/deletion forms
    >   (`open_internal_insertion_score`, …); the `*_gap_score` names still work as
    >   meta-attributes. The `alphabet` attribute is deprecated and unused.
    > - **`Bio.pairwise2` is deprecated** — use `Bio.Align.PairwiseAligner`.
    > - **`Bio.Blast.NCBIXML` is declared obsolete** as of the 1.89 development line in favour
    >   of the `Bio.Blast` parser added in 1.84 (`Blast.parse`/`Blast.read`, `Blast.qblast`).
    >   It still ships and works in 1.88; prefer the new API for new code.
    > - **Security fixes worth upgrading for:** 1.87 fixed CVE-2025-68463 in
    >   `Bio.Entrez.Parser`, and 1.88 removed an `eval` in the `Bio.Nexus` parser that allowed
    >   code execution from a malicious NEXUS file. Treat downloaded records as untrusted input
    >   and keep Biopython current.
    
    ## When to Use This Skill
    
    Use this skill when:
    
    - Working with biological sequences (DNA, RNA, or protein)
    - Reading, writing, or converting biological file formats (FASTA, GenBank, FASTQ, PDB, mmCIF, etc.)
    - Accessing NCBI databases (GenBank, PubMed, Protein, Gene, etc.) via Entrez
    - Running BLAST searches or parsing BLAST results
    - Performing sequence alignments (pairwise or multiple sequence alignments)
    - Analyzing protein structures from PDB files
    - Creating, manipulating, or visualizing phylogenetic trees
    - Finding sequence motifs or analyzing motif patterns
    - Calculating sequence statistics (GC content, molecular weight, melting temperature, etc.)
    - Performing structural bioinformatics tasks
    - Working with population genetics data
    - Any other computational molecular biology task
    
    ### Does NOT Trigger
    
    | Scenario | Use Instead |
    |----------|-------------|
    | Running local BLAST+ / `makeblastdb` from the command line, or DIAMOND | `alterlab-blast` |
    | A one-line lookup of a gene, sequence, or structure | `alterlab-gget` |
    | One call across many web services (UniProt + KEGG + ChEMBL in a pipeline) | `alterlab-bioservices` |
    | SAM/BAM/CRAM record access, pileups, and read filtering | `alterlab-pysam` |
    | Building an ML phylogeny from unaligned sequences (MAFFT + IQ-TREE) | `alterlab-phylogenetics` |
    
    ## Core Capabilities
    
    Biopython is organized into modular sub-packages, each addressing specific bioinformatics domains:
    
    1. **Sequence Handling** - Bio.Seq and Bio.SeqIO for sequence manipulation and file I/O
    2. **Alignment Analysis** - Bio.Align and Bio.AlignIO for pairwise and multiple sequence alignments
    3. **Database Access** - Bio.Entrez for programmatic access to NCBI databases
    4. **BLAST Operations** - Bio.Blast for running and parsing BLAST searches
    5. **Structural Bioinformatics** - Bio.PDB for working with 3D protein structures
    6. **Phylogenetics** - Bio.Phylo for phylogenetic tree manipulation and visualization
    7. **Advanced Features** - Motifs, population genetics, sequence utilities, and more
    
    ## Installation and Setup
    
    Install Biopython (requires Python 3 and NumPy). On this machine, prefer running scripts with `uv run`:
    
    ```bash
    # Ad-hoc: run a script with Biopython available, no venv to manage
    uv run --with biopython script.py
    
    # Or add it to a project
    uv add biopython
    ```
    
    For NCBI database access, always set your email address (required by NCBI):
    
    ```python
    from Bio import Entrez
    Entrez.email = "your.email@example.com"
    
    # Optional: API key for higher rate limits (10 req/s instead of 3 req/s)
    Entrez.api_key = "your_api_key_here"
    ```
    
    ## Using This Skill
    
    This skill provides comprehensive documentation organized by functionality area. When working on a task, consult the relevant reference documentation:
    
    ### 1. Sequence Handling (Bio.Seq & Bio.SeqIO)
    
    **Reference:** `references/sequence_io.md`
    
    Use for:
    - Creating and manipulating biological sequences
    - Reading and writing sequence files (FASTA, GenBank, FASTQ, etc.)
    - Converting between file formats
    - Extracting sequences from large files
    - Sequence translation, transcription, and reverse complement
    - Working with SeqRecord objects
    
    **Quick example:**
    ```python
    from Bio import SeqIO
    
    # Read sequences from FASTA file
    for record in SeqIO.parse("sequences.fasta", "fasta"):
        print(f"{record.id}: {len(record.seq)} bp")
    
    # Convert GenBank to FASTA
    SeqIO.convert("input.gb", "genbank", "output.fasta", "fasta")
    ```
    
    ### 2. Alignment Analysis (Bio.Align & Bio.AlignIO)
    
    **Reference:** `references/alignment.md`
    
    Use for:
    - Pairwise sequence alignment (global and local)
    - Reading and writing multiple sequence alignments
    - Using substitution matrices (BLOSUM, PAM)
    - Calculating alignment statistics
    - Customizing alignment parameters
    
    **Quick example:**
    ```python
    from Bio import Align
    
    # Pairwise alignment
    aligner = Align.PairwiseAligner()
    aligner.mode = 'global'
    alignments = aligner.align("ACCGGT", "ACGGT")
    print(alignments[0])
    ```
    
    ### 3. Database Access (Bio.Entrez)
    
    **Reference:** `references/databases.md`
    
    Use for:
    - Searching NCBI databases (PubMed, GenBank, Protein, Gene, etc.)
    - Downloading sequences and records
    - Fetching publication information
    - Finding related records across databases
    - Batch downloading with proper rate limiting
    
    **Quick example:**
    ```python
    from Bio import Entrez
    Entrez.email = "your.email@example.com"
    
    # Search PubMed
    handle = Entrez.esearch(db="pubmed", term="biopython", retmax=10)
    results = Entrez.read(handle)
    handle.close()
    print(f"Found {results['Count']} results")
    ```
    
    ### 4. BLAST Operations (Bio.Blast)
    
    **Reference:** `references/blast.md`
    
    Use for:
    - Running BLAST searches via NCBI web services
    - Running local BLAST searches
    - Parsing BLAST XML output
    - Filtering results by E-value or identity
    - Extracting hit sequences
    
    **Quick example** (the `Bio.Blast` API introduced in 1.84 — NCBI requires a contact email):
    ```python
    from Bio import Blast
    
    Blast.email = "your.email@example.com"
    result_stream = Blast.qblast("blastn", "nt", "ATCGATCGATCG")
    blast_record = Blast.read(result_stream)
    
    # Each hit's alignments are Bio.Align objects; scores live in .annotations
    for hit in blast_record[:5]:
        print(f"{hit.target.id}: E-value={hit[0].annotations['evalue']}")
    ```
    
    ### 5. Structural Bioinformatics (Bio.PDB)
    
    **Reference:** `references/structure.md`
    
    Use for:
    - Parsing PDB and mmCIF structure files
    - Navigating protein structure hierarchy (SMCRA: Structure/Model/Chain/Residue/Atom)
    - Calculating distances, angles, and dihedrals
    - Secondary structure assignment (DSSP)
    - Structure superimposition and RMSD calculation
    - Extracting sequences from structures
    
    **Quick example:**
    ```python
    from Bio.PDB import PDBParser
    
    # Parse structure
    parser = PDBParser(QUIET=True)
    structure = parser.get_structure("1crn", "1crn.pdb")
    
    # Calculate distance between alpha carbons
    chain = structure[0]["A"]
    distance = chain[10]["CA"] - chain[20]["CA"]
    print(f"Distance: {distance:.2f} Å")
    ```
    
    ### 6. Phylogenetics (Bio.Phylo)
    
    **Reference:** `references/phylogenetics.md`
    
    Use for:
    - Reading and writing phylogenetic trees (Newick, NEXUS, phyloXML)
    - Building trees from distance matrices or alignments
    - Tree manipulation (pruning, rerooting, ladderizing)
    - Calculating phylogenetic distances
    - Creating consensus trees
    - Visualizing trees
    
    **Quick example:**
    ```python
    from Bio import Phylo
    
    # Read and visualize tree
    tree = Phylo.read("tree.nwk", "newick")
    Phylo.draw_ascii(tree)
    
    # Calculate distance
    distance = tree.distance("Species_A", "Species_B")
    print(f"Distance: {distance:.3f}")
    ```
    
    ### 7. Advanced Features
    
    **Reference:** `references/advanced.md`
    
    Use for:
    - **Sequence motifs** (Bio.motifs) - Finding and analyzing motif patterns
    - **Population genetics** (Bio.PopGen) - GenePop files, Fst calculations, Hardy-Weinberg tests
    - **Sequence utilities** (Bio.SeqUtils) - GC content, melting temperature, molecular weight, protein analysis
    - **Restriction analysis** (Bio.Restriction) - Finding restriction enzyme sites
    - **Clustering** (Bio.Cluster) - K-means and hierarchical clustering
    - **Genome diagrams** (GenomeDiagram) - Visualizing genomic features
    
    **Quick example:**
    ```python
    from Bio.SeqUtils import gc_fraction, molecular_weight
    from Bio.Seq import Seq
    
    seq = Seq("ATCGATCGATCG")
    print(f"GC content: {gc_fraction(seq):.2%}")
    print(f"Molecular weight: {molecular_weight(seq, seq_type='DNA'):.2f} g/mol")
    ```
    
    ## General Workflow Guidelines
    
    ### Reading Documentation
    
    When a user asks about a specific Biopython task:
    
    1. **Identify the relevant module** based on the task description
    2. **Read the appropriate reference file** using the Read tool
    3. **Extract relevant code patterns** and adapt them to the user's specific needs
    4. **Combine multiple modules** when the task requires it
    
    Example search patterns for reference files:
    ```bash
    # Find information about specific functions
    grep -n "SeqIO.parse" references/sequence_io.md
    
    # Find examples of specific tasks
    grep -n "BLAST" references/blast.md
    
    # Find information about specific concepts
    grep -n "alignment" references/alignment.md
    ```
    
    ### Writing Biopython Code
    
    Follow these principles when writing Biopython code:
    
    1. **Import modules explicitly**
       ```python
       from Bio import SeqIO, Entrez
       from Bio.Seq import Seq
       ```
    
    2. **Set Entrez email** when using NCBI databases
       ```python
       Entrez.email = "your.email@example.com"
       ```
    
    3. **Use appropriate file formats** - Check which format best suits the task
       ```python
       # Common formats: "fasta", "genbank", "fastq", "clustal", "phylip"
       ```
    
    4. **Handle files properly** - Close handles after use or use context managers
       ```python
       with open("file.fasta") as handle:
           records = SeqIO.parse(handle, "fasta")
       ```
    
    5. **Use iterators for large files** - Avoid loading everything into memory
       ```python
       for record in SeqIO.parse("large_file.fasta", "fasta"):
           # Process one record at a time
       ```
    
    6. **Handle errors gracefully** - Network operations and file parsing can fail
       ```python
       try:
           handle = Entrez.efetch(db="nucleotide", id=accession)
       except HTTPError as e:
           print(f"Error: {e}")
       ```
    
    ## Common Patterns
    
    ### Pattern 1: Fetch Sequence from GenBank
    
    ```python
    from Bio import Entrez, SeqIO
    
    Entrez.email = "your.email@example.com"
    
    # Fetch sequence
    handle = Entrez.efetch(db="nucleotide", id="EU490707", rettype="gb", retmode="text")
    record = SeqIO.read(handle, "genbank")
    handle.close()
    
    print(f"Description: {record.description}")
    print(f"Sequence length: {len(record.seq)}")
    ```
    
    ### Pattern 2: Sequence Analysis Pipeline
    
    ```python
    from Bio import SeqIO
    from Bio.SeqUtils import gc_fraction
    
    for record in SeqIO.parse("sequences.fasta", "fasta"):
        # Calculate statistics
        gc = gc_fraction(record.seq)
        length = len(record.seq)
    
        # Find ORFs, translate, etc.
        protein = record.seq.translate()
    
        print(f"{record.id}: {length} bp, GC={gc:.2%}")
    ```
    
    ### Pattern 3: BLAST and Fetch Top Hits
    
    ```python
    from Bio import Blast, Entrez, SeqIO
    
    Entrez.email = Blast.email = "your.email@example.com"
    
    # Run BLAST (Bio.Blast API, Biopython >= 1.84)
    result_stream = Blast.qblast("blastn", "nt", sequence)
    blast_record = Blast.read(result_stream)
    
    # Get top hit accessions (hit.target is a SeqRecord)
    accessions = [hit.target.name for hit in blast_record[:5]]
    
    # Fetch sequences
    for acc in accessions:
        handle = Entrez.efetch(db="nucleotide", id=acc, rettype="fasta", retmode="text")
        record = SeqIO.read(handle, "fasta")
        handle.close()
        print(f">{record.description}")
    ```
    
    ### Pattern 4: Build Phylogenetic Tree from Sequences
    
    ```python
    from Bio import AlignIO, Phylo
    from Bio.Phylo.TreeConstruction import DistanceCalculator, DistanceTreeConstructor
    
    # Read alignment
    alignment = AlignIO.read("alignment.fasta", "fasta")
    
    # Calculate distances
    calculator = DistanceCalculator("identity")
    dm = calculator.get_distance(alignment)
    
    # Build tree
    constructor = DistanceTreeConstructor()
    tree = constructor.nj(dm)
    
    # Visualize
    Phylo.draw_ascii(tree)
    ```
    
    ## Best Practices
    
    1. **Always read relevant reference documentation** before writing code
    2. **Use grep to search reference files** for specific functions or examples
    3. **Validate file formats** before parsing
    4. **Handle missing data gracefully** - Not all records have all fields
    5. **Cache downloaded data** - Don't repeatedly download the same sequences
    6. **Respect NCBI rate limits** - Use API keys and proper delays
    7. **Test with small datasets** before processing large files
    8. **Keep Biopython updated** to get latest features and bug fixes
    9. **Use appropriate genetic code tables** for translation
    10. **Document analysis parameters** for reproducibility
    
    ## Troubleshooting Common Issues
    
    ### Issue: "No handlers could be found for logger 'Bio.Entrez'"
    **Solution:** This is just a warning. Set Entrez.email to suppress it.
    
    ### Issue: "HTTP Error 400" from NCBI
    **Solution:** Check that IDs/accessions are valid and properly formatted.
    
    ### Issue: "ValueError: EOF" when parsing files
    **Solution:** Verify file format matches the specified format string.
    
    ### Issue: Alignment fails with "sequences are not the same length"
    **Solution:** Ensure sequences are aligned before using AlignIO or MultipleSeqAlignment.
    
    ### Issue: BLAST searches are slow
    **Solution:** Use local BLAST for large-scale searches, or cache results.
    
    ### Issue: PDB parser warnings
    **Solution:** Use `PDBParser(QUIET=True)` to suppress warnings, or investigate structure quality.
    
    ## Additional Resources
    
    - **Official Documentation**: https://biopython.org/docs/latest/
    - **Tutorial**: https://biopython.org/docs/latest/Tutorial/
    - **Cookbook**: https://biopython.org/docs/latest/Tutorial/ (advanced examples)
    - **GitHub**: https://github.com/biopython/biopython
    - **Mailing List**: biopython@biopython.org
    
    ## Quick Reference
    
    To locate information in reference files, use these search patterns:
    
    ```bash
    # Search for specific functions
    grep -n "function_name" references/*.md
    
    # Find examples of specific tasks
    grep -n "example" references/sequence_io.md
    
    # Find all occurrences of a module
    grep -n "Bio.Seq" references/*.md
    ```
    
    ## Summary
    
    Biopython provides comprehensive tools for computational molecular biology. When using this skill:
    
    1. **Identify the task domain** (sequences, alignments, databases, BLAST, structures, phylogenetics, or advanced)
    2. **Consult the appropriate reference file** in the `references/` directory
    3. **Adapt code examples** to the specific use case
    4. **Combine multiple modules** when needed for complex workflows
    5. **Follow best practices** for file handling, error checking, and data management
    
    The modular reference documentation ensures detailed, searchable information for every major Biopython capability.
    
    Part of the AlterLab Academic Skills suite.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related