# 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