SpatialAgent_N2_DEEPAGENT / spatialagent /skill /sequence_analysis.md
jkkkkyuedtrt's picture
Upload 381 files
bd52a47 verified
|
Raw
History Blame Contribute Delete
23.9 kB
# Sequence Analysis
Analyze and identify DNA, RNA, and protein sequences using Biopython and BLAST.
## When to Use This Skill
**Sequence Identification:**
- Given a protein/DNA sequence, need to identify what gene/protein it is
- ClinVar questions that provide sequences (need to identify protein first)
- Any question asking about a specific sequence's identity
**Molecular Biology Analysis:**
- Restriction enzyme digestion (counting fragments, finding cut sites)
- Primer design and verification
- Sequence alignment and comparison
- PCR product prediction
- Finding open reading frames (ORFs)
- Calculating GC content, melting temperature
**Keywords**: sequence, BLAST, blastp, blastn, identify, protein, DNA, restriction enzyme, digest, fragment, primer, PCR, ORF, GC content, melting temperature, Tm, reverse complement, clone, Gibson, Kozak, translation efficiency, pUC19, plasmid
---
## Section 1: Fetch Gene and Plasmid Sequences
### Fetch Gene Coding Sequence from NCBI
```python
from Bio import Entrez, SeqIO
Entrez.email = "your@email.com"
def get_gene_sequence(gene_name, organism="Escherichia coli"):
"""Fetch gene coding sequence from NCBI."""
# Search for gene
search_term = f"{gene_name}[Gene Name] AND {organism}[Organism]"
handle = Entrez.esearch(db="gene", term=search_term, retmax=1)
record = Entrez.read(handle)
if not record["IdList"]:
print(f"Gene {gene_name} not found")
return None
gene_id = record["IdList"][0]
# Get gene record
handle = Entrez.efetch(db="gene", id=gene_id, rettype="gene_table", retmode="text")
gene_info = handle.read()
print(f"Gene ID: {gene_id}")
# For coding sequence, search nucleotide database
search_term = f"{gene_name}[Gene Name] AND {organism}[Organism] AND CDS[Feature Key]"
handle = Entrez.esearch(db="nucleotide", term=search_term, retmax=5)
nuc_record = Entrez.read(handle)
if nuc_record["IdList"]:
handle = Entrez.efetch(db="nucleotide", id=nuc_record["IdList"][0],
rettype="fasta", retmode="text")
seq_record = SeqIO.read(handle, "fasta")
return str(seq_record.seq)
return None
# Example: Get rsxD from E. coli
gene_seq = get_gene_sequence("rsxD", "Escherichia coli")
print(f"Start: {gene_seq[:50]}")
print(f"End: {gene_seq[-50:]}")
```
### Fetch Plasmid Sequence (pUC19, pET, etc.)
```python
from Bio import Entrez, SeqIO
Entrez.email = "your@email.com"
def get_plasmid_sequence(plasmid_name):
"""Fetch plasmid sequence from NCBI."""
handle = Entrez.esearch(db="nucleotide", term=f"{plasmid_name}[Title] AND vector", retmax=1)
record = Entrez.read(handle)
if record["IdList"]:
handle = Entrez.efetch(db="nucleotide", id=record["IdList"][0],
rettype="fasta", retmode="text")
seq_record = SeqIO.read(handle, "fasta")
return str(seq_record.seq)
return None
```
### pUC19 Multiple Cloning Site (MCS) - Reference Sequence
**IMPORTANT**: Use this reference for Gibson/restriction cloning into pUC19:
```python
# pUC19 MCS region (positions ~230-290 in full plasmid)
# Format: enzyme sites are marked with their recognition sequences
PUC19_MCS = """
EcoRI SacI KpnI SmaI/XmaI BamHI XbaI SalI PstI SphI HindIII
| | | | | | | | | |
5'-GAATTCGAGCTCGGTACCCGGGGATCCTCTAGAGTCGACCTGCAGGCATGCAAGCTT-3'
"""
# For Gibson assembly after HindIII linearization:
# The plasmid is cut at AAGCTT, creating these flanking sequences:
PUC19_GIBSON_HOMOLOGY = {
"HindIII": {
# 30bp UPSTREAM of HindIII cut (use for forward primer 5' end)
"upstream": "GATTACGCCAAGCTTGCATGCCTGCAGGTC",
# 30bp DOWNSTREAM of HindIII cut (take RC for reverse primer 5' end)
"downstream": "GACTCTAGAGGATCCCCGGGTACCGAGCTC",
"downstream_rc": "GAGCTCGGTACCCGGGGATCCTCTAGAGTC" # Pre-computed RC
},
"HindII": { # Note: HindII cuts at GTY^RAC (blunt), different from HindIII
"upstream": "GATTACGCCAAGCTTGCATGCCTGCAGGTC",
"downstream": "GACTCTAGAGGATCCCCGGGTACCGAGCTC",
"downstream_rc": "GAGCTCGGTACCCGGGGATCCTCTAGAGTC"
}
}
# CRITICAL for Gibson primer verification:
# Forward primer = [upstream homology ~30bp] + [gene start with ATG]
# Reverse primer = [downstream_rc ~30bp] + [gene end RC with stop codon RC]
```
### E. coli Gene Database (Local)
**IMPORTANT**: For E. coli cloning questions, use the local gene database:
```python
import json
import os
# Load E. coli gene database (contains common cloning genes)
ECOLI_GENES_PATH = os.path.join(data_path, "gene_databases", "ecoli_genes.json")
with open(ECOLI_GENES_PATH) as f:
ECOLI_GENES = json.load(f)
# Available genes: agp, gpp, gudX, hslO, intE, menA, mlaE, napF, plsB, rsxD, torA, ubiI, waaA
def get_ecoli_gene(gene_name):
"""Get E. coli gene sequence from local database.
Returns dict with: sequence, start, end, length
"""
gene_name_lower = gene_name.lower()
for name, data in ECOLI_GENES.items():
if name.lower() == gene_name_lower:
return data
return None
# Example:
rsxd = get_ecoli_gene("rsxD")
if rsxd:
print(f"rsxD: {rsxd['length']} bp")
print(f"Start: {rsxd['start']}") # ATGGTATTCAGAATAGCTAGCTCCCCTTA
print(f"End: {rsxd['end']}") # ...ends with stop codon
```
### E. coli Gene Reference Table
| Gene | Length | Start Sequence (first 25bp) |
| ---- | ------- | ----------------------------- |
| rsxD | 1059 bp | `ATGGTATTCAGAATAGCTAGCTCCC` |
| napF | 495 bp | `GTGAAGATTGATGCATCCCGTCGG` |
| torA | 2547 bp | `ATGAACAATAACGATCTCTTTCAGG` |
| agp | 1242 bp | `ATGAACAAAACGCTAATCGCCGCC` |
| menA | 927 bp | `ATGACTGAACAACAAATTAGTGCC` |
| mlaE | 783 bp | `ATGCTGTTAAATGCGCTGGCGCTG` |
**For Gibson assembly primer verification**:
1. Extract gene start (first ~25bp after homology arm)
2. Compare to the gene's actual start sequence from database
3. The primer with matching gene start is correct
---
## Section 2: Sequence Identification with BLAST
### Identify Protein Sequence
```python
from Bio.Blast import NCBIWWW, NCBIXML
def identify_protein(sequence, max_results=3):
"""Identify a protein sequence using NCBI BLAST.
Args:
sequence: Protein sequence (amino acids)
max_results: Number of top hits to return
Returns:
List of top matching proteins with gene names
"""
print("Submitting BLAST search (may take 30-60 seconds)...")
result_handle = NCBIWWW.qblast(
"blastp", # Program: blastp for protein
"nr", # Database: non-redundant protein
sequence[:500], # Use first 500 aa for speed
expect=10,
hitlist_size=max_results
)
blast_records = NCBIXML.parse(result_handle)
blast_record = next(blast_records)
results = []
for alignment in blast_record.alignments[:max_results]:
hsp = alignment.hsps[0]
identity_pct = (hsp.identities / hsp.align_length) * 100
results.append({
"title": alignment.hit_def,
"accession": alignment.accession,
"identity": f"{identity_pct:.1f}%",
"e_value": hsp.expect
})
print(f"Match: {alignment.hit_def[:80]}...")
print(f" Identity: {identity_pct:.1f}%, E-value: {hsp.expect}")
return results
# Example
sequence = "MLLAVLYCLLWSFQTSAGHFPRACVSS..."
results = identify_protein(sequence)
# Extract gene name from top hit
if results:
import re
top_hit = results[0]['title']
gene_match = re.search(r'\b([A-Z][A-Z0-9]{2,10})\b', top_hit)
if gene_match:
gene_name = gene_match.group(1)
print(f"Gene: {gene_name}")
```
### Identify DNA Sequence
```python
def identify_dna(sequence, max_results=3):
"""Identify a DNA sequence using NCBI BLAST."""
print("Submitting BLAST search...")
result_handle = NCBIWWW.qblast(
"blastn", # Program: blastn for nucleotide
"nt", # Database: nucleotide collection
sequence[:1000], # Use first 1000 bp for speed
expect=10,
hitlist_size=max_results
)
blast_records = NCBIXML.parse(result_handle)
blast_record = next(blast_records)
for alignment in blast_record.alignments[:max_results]:
hsp = alignment.hsps[0]
print(f"Match: {alignment.hit_def[:80]}")
print(f" Identity: {hsp.identities}/{hsp.align_length}")
return blast_record.alignments
```
### Quick UniProt Search (Faster Alternative)
```python
import requests
def search_uniprot(sequence, max_results=5):
"""Search UniProt by sequence (faster than BLAST for exact matches)."""
url = "https://rest.uniprot.org/uniprotkb/search"
params = {
"query": f"sequence:{sequence[:50]}",
"format": "json",
"size": max_results
}
response = requests.get(url, params=params)
data = response.json()
for entry in data.get("results", []):
gene = entry.get("genes", [{}])[0].get("geneName", {}).get("value", "N/A")
protein = entry.get("proteinDescription", {}).get("recommendedName", {}).get("fullName", {}).get("value", "N/A")
print(f"Gene: {gene}, Protein: {protein}")
return data.get("results", [])
```
---
## Section 3: Restriction Enzyme Digestion
### Count Fragments After Digestion
```python
from Bio import Restriction
from Bio.Seq import Seq
# Example: Digest with MboI (cuts at GATC)
sequence = "AGCATATGGAAGACCAATACATGAGGGGGCATACGCTAGAACGCCCC..."
seq = Seq(sequence.upper())
# Single enzyme
enzyme = Restriction.MboI
cut_sites = enzyme.search(seq, linear=True) # linear=True for linear DNA
print(f"Cut positions: {cut_sites}")
print(f"Number of fragments: {len(cut_sites) + 1}")
# Multiple enzymes
rb = Restriction.RestrictionBatch(["EcoRI", "BamHI"])
analysis = rb.search(seq, linear=True)
for enzyme, positions in analysis.items():
print(f"{enzyme}: cuts at {positions}")
```
**Key parameters**:
- `linear=True` for linear DNA, `linear=False` for circular plasmids
- Fragment count = cuts + 1 (linear) or cuts (circular)
### Common Restriction Enzymes
| Enzyme | Recognition Site | Notes |
| ------- | ---------------- | ----------- |
| EcoRI | GAATTC | Sticky ends |
| BamHI | GGATCC | Sticky ends |
| HindIII | AAGCTT | Sticky ends |
| XbaI | TCTAGA | Sticky ends |
| SmaI | CCCGGG | Blunt ends |
| NheI | GCTAGC | Sticky ends |
| MboI | GATC | Sticky ends |
| BsaI | GGTCTC | Type IIS |
### Find All Restriction Sites
```python
from Bio import Restriction
from Bio.Seq import Seq
seq = Seq("YOUR_SEQUENCE_HERE".upper())
# Check multiple enzymes
enzymes = ["EcoRI", "BamHI", "HindIII", "XbaI", "SmaI", "NheI"]
rb = Restriction.RestrictionBatch(enzymes)
analysis = rb.search(seq, linear=True)
for enzyme, positions in analysis.items():
if positions:
print(f"{enzyme} ({enzyme.site}): cuts at {positions}")
```
---
## Section 4: Sequence Properties
### GC Content and Melting Temperature
```python
from Bio.Seq import Seq
from Bio.SeqUtils import gc_fraction, MeltingTemp
seq = Seq("ATGCGATCGATCGATCG")
# GC content
gc = gc_fraction(seq) * 100
print(f"GC content: {gc:.1f}%")
# Melting temperature (for primers)
tm = MeltingTemp.Tm_Wallace(seq) # Simple calculation
print(f"Tm (Wallace): {tm:.1f}C")
# Reverse complement
rc = seq.reverse_complement()
print(f"Reverse complement: {rc}")
# Translate DNA to protein
protein = seq.translate()
print(f"Protein: {protein}")
```
---
## Section 5: Open Reading Frame (ORF) Detection
```python
from Bio.Seq import Seq
def find_orfs(sequence, min_length=100):
"""Find all ORFs in a DNA sequence."""
seq = Seq(sequence.upper())
orfs = []
# Check all three reading frames
for frame in range(3):
for i in range(frame, len(seq) - 2, 3):
codon = str(seq[i:i+3])
if codon == "ATG": # Start codon
for j in range(i + 3, len(seq) - 2, 3):
stop = str(seq[j:j+3])
if stop in ["TAA", "TAG", "TGA"]:
orf_seq = str(seq[i:j+3])
if len(orf_seq) >= min_length:
orfs.append({
"start": i,
"end": j + 3,
"length": len(orf_seq),
"sequence": orf_seq
})
break
return sorted(orfs, key=lambda x: x["length"], reverse=True)
orfs = find_orfs(sequence, min_length=60)
print(f"Found {len(orfs)} ORFs")
for orf in orfs[:5]:
print(f" Position {orf['start']}-{orf['end']}: {orf['length']} bp")
```
---
## Section 6: Primer Design and Analysis
### Check Primer Properties
```python
from Bio.Seq import Seq
from Bio.SeqUtils import gc_fraction, MeltingTemp
def check_primer(primer_seq):
"""Check primer properties."""
seq = Seq(primer_seq.upper())
gc = gc_fraction(seq) * 100
tm = MeltingTemp.Tm_Wallace(seq)
issues = []
if gc < 40 or gc > 60:
issues.append(f"GC content {gc:.0f}% outside ideal range (40-60%)")
if tm < 55 or tm > 65:
issues.append(f"Tm {tm:.0f}C outside ideal range (55-65C)")
if len(seq) < 18 or len(seq) > 25:
issues.append(f"Length {len(seq)} outside ideal range (18-25 bp)")
return {
"sequence": str(seq),
"length": len(seq),
"gc_content": gc,
"tm": tm,
"issues": issues
}
result = check_primer("ATGCGATCGATCGATCGATCG")
print(f"Length: {result['length']} bp, GC: {result['gc_content']:.1f}%, Tm: {result['tm']:.1f}C")
```
### Find Primer Binding Sites
```python
from Bio.Seq import Seq
def find_primer_binding(template, primer, max_mismatches=1):
"""Find where primer binds to template (both strands)."""
template = template.upper()
primer = primer.upper()
primer_rc = str(Seq(primer).reverse_complement())
matches = []
for seq, strand in [(primer, "+"), (primer_rc, "-")]:
for i in range(len(template) - len(seq) + 1):
window = template[i:i + len(seq)]
mismatches = sum(1 for a, b in zip(seq, window) if a != b)
if mismatches <= max_mismatches:
matches.append({
"position": i,
"strand": strand,
"mismatches": mismatches
})
return matches
```
---
## Section 7: Variant Detection (for ClinVar Queries)
### Compare Sequences to Find Variants
```python
def find_variants(sequences):
"""Compare sequences to identify amino acid differences.
Args:
sequences: Dict mapping option label to sequence string
Returns:
Dict mapping label to list of variants
"""
labels = list(sequences.keys())
ref_seq = sequences[labels[0]]
variants = {labels[0]: []} # Reference has no variants
for label in labels[1:]:
seq = sequences[label]
diffs = []
for i, (ref_aa, var_aa) in enumerate(zip(ref_seq, seq)):
if ref_aa != var_aa:
diffs.append(f"{ref_aa}{i+1}{var_aa}")
variants[label] = diffs
if not diffs:
print(f"Option {label}: REFERENCE (wild-type)")
else:
print(f"Option {label}: {diffs}")
return variants
# Example
seqs = {
'A': "MLLAVLYCLLWSFQTS...",
'B': "MLLAVLYCLLWSFQTS...", # with R317C
'C': "MLLAVLYCLLWSFQTS...", # with D175Y
}
variants = find_variants(seqs)
```
### Biochemical Analysis of Variants
```python
def analyze_variant(variant):
"""Analyze variant by amino acid properties."""
from_aa = variant[0]
to_aa = variant[-1]
# Conservative substitutions (likely benign)
conservative_pairs = [
("E", "D"), ("K", "R"), ("V", "L"), ("V", "I"),
("L", "I"), ("S", "T"), ("F", "Y")
]
for pair in conservative_pairs:
if (from_aa, to_aa) in [pair, pair[::-1]]:
return f"{variant}: CONSERVATIVE (likely benign)"
# Disruptive changes
if to_aa == "P":
return f"{variant}: Proline introduction (likely pathogenic)"
if to_aa == "C":
return f"{variant}: Cysteine introduction (check disulfide)"
return f"{variant}: Non-conservative change"
```
---
## Section 8: Translation Efficiency (Kozak Consensus)
**Use for questions about "translation efficiency" or "most likely to be translated"**
The Kozak sequence determines how efficiently a ribosome initiates translation.
### Optimal Kozak Sequence
```
Position: -6 -5 -4 -3 -2 -1 A U G +4 +5 +6
Optimal: G C C A/G C C A U G G N N
↑ ↑
Critical Critical
(purine) (G)
```
**Critical positions**:
- Position -3: Must be A or G (purine) - MOST IMPORTANT
- Position +4: Should be G
### Score Kozak Strength
```python
def score_kozak(sequence, aug_position):
"""Score Kozak consensus strength (0-4 scale).
Args:
sequence: RNA or DNA sequence (U or T accepted)
aug_position: Position of A in AUG (0-indexed)
Returns:
Score from 0 (weak) to 4 (optimal)
"""
seq = sequence.upper().replace('T', 'U')
score = 0
if aug_position < 3:
return 0 # Not enough context
# Position -3 (most critical): should be A or G
pos_minus_3 = seq[aug_position - 3]
if pos_minus_3 in ['A', 'G']:
score += 2 # Worth 2 points (most important)
# Position +4: should be G
if aug_position + 3 < len(seq):
pos_plus_4 = seq[aug_position + 3]
if pos_plus_4 == 'G':
score += 1
# Position -6 to -4: GCC is optimal
if aug_position >= 6:
context = seq[aug_position-6:aug_position-3]
if context == 'GCC':
score += 1
return score
# Example: Compare sequences for translation efficiency
sequences = {
'A': "CCCUGAUGCCUGCUAGC...", # C at -3, weak
'E': "CCACCAUGGCUAAUGAC...", # A at -3, G at +4, optimal
}
for name, seq in sequences.items():
aug_pos = seq.find('AUG')
if aug_pos >= 0:
score = score_kozak(seq, aug_pos)
context = seq[max(0,aug_pos-6):aug_pos+7]
print(f"Sequence {name}: score={score}, context={context}")
```
**Interpretation**:
- Score 4: Optimal Kozak (highest translation efficiency)
- Score 2-3: Good Kozak
- Score 0-1: Weak Kozak (poor translation initiation)
---
## Section 9: Cloning Primer Design
### Gibson Assembly Primers
For Gibson assembly into a linearized vector:
- **Forward primer**: [upstream vector homology ~30bp] + [gene start sequence]
- **Reverse primer**: [downstream vector RC homology ~30bp] + [gene end RC sequence]
```python
from Bio.Seq import Seq
def design_gibson_primers(gene_seq, vector_upstream, vector_downstream, homology_len=30):
"""Design primers for Gibson assembly.
Args:
gene_seq: Gene coding sequence (start with ATG, end with stop)
vector_upstream: Sequence upstream of cut site (same strand)
vector_downstream: Sequence downstream of cut site (same strand)
homology_len: Length of homology arms (default 30bp)
Returns:
Forward and reverse primers
"""
# Forward primer: upstream homology + gene start
fwd_homology = vector_upstream[-homology_len:]
fwd_gene = gene_seq[:25] # First 25bp of gene
forward = fwd_homology + fwd_gene
# Reverse primer: downstream homology (RC) + gene end (RC)
downstream_rc = str(Seq(vector_downstream[:homology_len]).reverse_complement())
gene_end_rc = str(Seq(gene_seq[-25:]).reverse_complement())
reverse = downstream_rc + gene_end_rc
return {
"forward": forward,
"reverse": reverse,
"fwd_length": len(forward),
"rev_length": len(reverse)
}
# Example: Clone gene into pUC19 linearized with HindIII
gene = "ATGGTATTCAGAATAGCTAGCTCCCC...GGCCATCGCAAAGGGTAA" # rsxD
upstream = "GATTACGCCAAGCTTGCATGCCTGCAGGTC" # pUC19 upstream of HindIII
downstream = "GACTCTAGAGGATCCCCGGGTACCGAGCTC" # pUC19 downstream
primers = design_gibson_primers(gene, upstream, downstream)
print(f"Forward: {primers['forward']}")
print(f"Reverse: {primers['reverse']}")
```
### Verify Gibson Primer Homology
```python
def verify_gibson_primers(primer, vector_seq, gene_seq, is_forward=True):
"""Verify that primer has correct homology for Gibson assembly.
Args:
primer: Primer sequence
vector_seq: Full vector sequence
gene_seq: Gene sequence
is_forward: True for forward primer, False for reverse
Returns:
Dict with verification results
"""
primer = primer.upper()
vector_seq = vector_seq.upper()
gene_seq = gene_seq.upper()
results = {"homology_found": False, "gene_match": False}
# Check first 25-35bp against vector
for homology_len in range(35, 20, -1):
homology = primer[:homology_len]
if homology in vector_seq:
results["homology_found"] = True
results["homology_len"] = homology_len
results["homology_seq"] = homology
# Check remaining matches gene
gene_part = primer[homology_len:]
if is_forward:
if gene_seq.startswith(gene_part):
results["gene_match"] = True
else:
# Reverse primer: gene part should be RC of gene end
gene_part_rc = str(Seq(gene_part).reverse_complement())
if gene_seq.endswith(gene_part_rc):
results["gene_match"] = True
break
return results
```
### Restriction-Ligation Cloning Primers
For traditional restriction cloning:
- Add restriction site + 2-4 flanking bases to each primer
```python
RESTRICTION_SITES = {
"EcoRI": "GAATTC",
"BamHI": "GGATCC",
"HindIII": "AAGCTT",
"XbaI": "TCTAGA",
"SmaI": "CCCGGG",
"XmaI": "CCCGGG", # Same as SmaI but sticky
"SalI": "GTCGAC",
"PstI": "CTGCAG"
}
def design_restriction_primers(gene_seq, enzyme_5prime, enzyme_3prime):
"""Design primers for restriction-ligation cloning.
Args:
gene_seq: Gene sequence (ATG to stop)
enzyme_5prime: Restriction enzyme for 5' end
enzyme_3prime: Restriction enzyme for 3' end
"""
site_5 = RESTRICTION_SITES[enzyme_5prime]
site_3 = RESTRICTION_SITES[enzyme_3prime]
# Forward: flank + site + gene start (from ATG)
forward = "CCCC" + site_5 + gene_seq[:20]
# Reverse: flank + site + gene end RC (including stop codon)
gene_end_rc = str(Seq(gene_seq[-20:]).reverse_complement())
reverse = "CCCC" + site_3 + gene_end_rc
return {"forward": forward, "reverse": reverse}
# Example: Clone with SmaI and XmaI (isoschizomers)
primers = design_restriction_primers(
"ATGAACAATAACGATCTCTTTCAGGCA...TCATGATTTCACCTGCGACGC",
"SmaI", "SmaI"
)
```
---
## Tips
1. **BLAST is slow** (30-60+ seconds) - use UniProt for faster exact matches
2. **Use first 500 aa** of protein for faster BLAST
3. **Always uppercase sequences** before analysis
4. **Check linear vs circular** - affects fragment count
5. **For ClinVar questions**: Identify protein first (BLAST), then find variants, then query ClinVar
6. **Reference sequence = Benign**: If one option has NO variants, it's benign
7. **For cloning questions**: Fetch the actual gene and plasmid sequences from NCBI to verify primers
8. **For translation efficiency**: Kozak position -3 (purine) is most critical
9. **Gibson homology**: Use 25-35bp homology arms matching vector flanks