# Bio Transcription Translation

> Transcribe DNA to RNA and translate to protein using Biopython, with NCBI codon-table selection, CDS validation, and six-frame ORF finding. Use when converting a CDS or ORF to its amino-acid sequence, selecting a non-standard (mitochondrial, bacterial, ciliate) genetic code, validating a coding sequence, or scanning all reading frames.

- Skill: `pku-yuangroup/bio-transcription-translation` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add pku-yuangroup/bio-transcription-translation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pku-yuangroup/bio-transcription-translation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: pku-yuangroup (https://skillmd.com/u/pku-yuangroup)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pku-yuangroup/bio-transcription-translation

---


## Version Compatibility

Reference examples tested with: BioPython 1.83+

Before using code patterns, verify installed versions match. If versions differ:
- Python: `pip show <package>` then `help(module.function)` to check signatures

If code throws ImportError, AttributeError, or TypeError, introspect the installed
package and adapt the example to match the actual API rather than retrying.

# Transcription and Translation

**"Translate my DNA sequence to protein"** -> Transcribe DNA to RNA and translate to protein, choosing the right genetic code and validating the reading frame.
- Python: `Seq.translate()`, `Seq.transcribe()`, `Bio.Data.CodonTable` (BioPython)

## The Governing Principle

The single most dangerous bug in translation is **silent**: a valid-but-wrong `table=` argument produces a plausible wrong protein with no error. Translating human mitochondrial DNA with the default Standard code (table 1) inserts `*` where UGA actually codes Trp and truncates at AGA/AGG (which are stops in vertebrate mito). The protein looks real and nothing complains. By contrast, an *unknown* table id or name raises `KeyError` (loud). Only valid-but-wrong tables corrupt silently.

The defense: when the input is a complete coding sequence, pass `cds=True`. It converts silent traps into loud `TranslationError` exceptions by validating start, length, and stop. Use it for ORF validation rather than trusting a clean-looking output.

Since the Biopython 1.78 alphabet removal, `transcribe()`, `back_transcribe()`, and `translate()` perform NO type checking. Transcribing a protein or translating the wrong strand returns silent garbage. Confirm the molecule and strand before converting.

## Required Import

```python
from Bio.Seq import Seq
from Bio.Data import CodonTable
```

## Transcription Is a String Operation, Not Biology

`transcribe()` is a pure T->U replacement on the coding (sense) strand; `back_transcribe()` is U->T. Neither performs splicing, intron removal, 5' capping, or poly-A addition. The Biopython tutorial states plainly that all transcribe does is replace T with U.

```python
coding_dna = Seq('ATGCGATCGATCG')
rna = coding_dna.transcribe()        # Seq('AUGCGAUCGAUCG'), T->U only
back = rna.back_transcribe()         # Seq('ATGCGATCGATCG'), U->T only
```

True biological transcription starts from the template strand, so reverse-complement first:

```python
template = Seq('CGATCGATCGCAT')
mrna = template.reverse_complement().transcribe()
```

Translation accepts DNA or RNA directly, so explicit transcription is rarely needed before `translate()`.

## Translation Basics

```python
coding_dna = Seq('ATGTTTGGT')
coding_dna.translate()               # Seq('MFG'), from DNA
Seq('AUGUUUGGU').translate()         # Seq('MFG'), from RNA
```

### Stop-Codon Behavior

`translate()` substitutes `stop_symbol` (default `'*'`) for EVERY in-frame stop, so internal stops appear as `*` mid-protein. `to_stop=True` instead halts at the first in-frame stop and does NOT append the symbol.

```python
seq = Seq('ATGTTTGGTTAAGGG')
seq.translate()                      # Seq('MFG*G'), stop shown, translation continues
seq.translate(to_stop=True)          # Seq('MFG'), halts at first stop
```

## NCBI Codon Tables: Which Code, and the Consequence of the Wrong One

Biopython exposes every NCBI genetic code by integer id or registered name. Selecting the wrong one is the #1 silent bug (see governing principle).

| ID | Name | Key reassignments vs Standard | When it matters |
|----|------|-------------------------------|-----------------|
| 1 | Standard | none (baseline) | Most nuclear genes |
| 2 | Vertebrate Mitochondrial | AGA/AGG -> STOP; AUA -> Met; UGA -> Trp | Human/vertebrate mtDNA (4 stops: UAA, UAG, AGA, AGG) |
| 3 | Yeast Mitochondrial | CUN (all four CU*) -> Thr; AUA -> Met; UGA -> Trp | **CTG -> Thr lives HERE, not table 12** |
| 4 | Mold/Protozoan Mito + Mycoplasma/Spiroplasma | UGA -> Trp (only change) | Fungal/protozoan mito; Mycoplasma |
| 5 | Invertebrate Mitochondrial | AGA/AGG -> Ser; AUA -> Met; UGA -> Trp | Insect/worm mito (AGA/AGG=Ser, not STOP as in table 2) |
| 6 | Ciliate Nuclear | UAA/UAG -> Gln; only UGA stays stop | Tetrahymena, Paramecium (single stop) |
| 11 | Bacterial/Archaeal/Plastid | same coding as Standard; expanded starts | Prokaryotes, plastids (differs from 1 mainly in initiation) |
| 12 | Alternative Yeast Nuclear | CUG -> Ser (from Leu) | *Candida* CUG-Ser clade |

**Explicit correction:** CTG -> Thr is table **3** (Yeast Mitochondrial). Table **12** is CUG -> **Ser**. Do not conflate them.

```python
seq = Seq('ATGGCCTGA')
seq.translate(table=2)                            # by NCBI integer id
seq.translate(table='Vertebrate Mitochondrial')   # by registered name

CodonTable.unambiguous_dna_by_id[2]
CodonTable.unambiguous_dna_by_name['Vertebrate Mitochondrial']
```

## Validating a Coding Sequence with cds=True

**Goal:** Translate a complete ORF and have any structural defect raise a loud error instead of producing a silent wrong protein.

**Approach:** Pass `cds=True`. It enforces four conditions, each raising `Bio.Data.CodonTable.TranslationError` on failure: (1) first codon is a start codon for the chosen table; (2) length is a multiple of 3; (3) sequence ends in a stop; (4) no internal in-frame stop. A valid alternative start (GTG/TTG/ATT) is translated as M, biologically correct for fMet initiation. The terminal stop is stripped from the output.

**Reference (BioPython 1.83+):**

```python
cds = Seq('ATGTTTGGTTAA')
cds.translate(cds=True)              # Seq('MFG'), validated, terminal stop removed

alt_start = Seq('GTGTTTGGTTAA')
alt_start.translate(table=11, cds=True)   # Seq('MFG'), GTG start -> M under bacterial code
```

Start-codon lists differ by table: table 1 = TTG/CTG/ATG; table 2 = ATT/ATC/ATA/ATG/GTG; table 11 = TTG/CTG/ATT/ATC/ATA/ATG/GTG. A start valid under one table fails under another, which is exactly the loud signal `cds=True` provides.

## translate() Parameters and Edge Cases

Signature (the `Seq.translate` METHOD): `translate(table='Standard', stop_symbol='*', to_stop=False, cds=False, gap='-')`. The `Seq` method defaults to `gap='-'`, while both the module-level `Bio.Seq.translate(sequence, ...)` function and `SeqRecord.translate()` default to `gap=None`.

- **Partial codon** (length not a multiple of 3): emits a `BiopythonWarning` and SILENTLY drops the trailing 1-2 bases. Easy to miss in a pipeline. Under `cds=True` the same condition becomes a loud `TranslationError`.
- **Gaps:** because the `Seq` method defaults to `gap='-'`, a full gap codon `'---'` already translates to `'-'` (e.g. `Seq('GTG---GCCATT').translate()` -> `'V-AI'`, no error). A codon mixing gaps and bases (`'TT-'`) raises `TranslationError`. `SeqRecord.translate()` instead defaults to `gap=None`, so even a full `'---'` codon raises unless `gap='-'` is passed; mixed gap/base codons still raise. For alignment-derived CDS, preserve codon and alignment semantics with `alignment/multiple-alignment` or the project's established translation wrapper rather than assuming one gap-normalization policy.
- **Dual-coding stop tables (27 Karyorelict, 28 Condylostoma, 31 Blastocrithidia Nuclear):** these reassign a stop codon so it codes both an amino acid and stop, so `to_stop=True` raises a `ValueError` (no single truncation point).

```python
Seq('ATGTTTGG').translate()          # BiopythonWarning, trailing 'GG' dropped -> Seq('MF')
Seq('ATGTTTGG').translate(cds=True)  # TranslationError: length not a multiple of three
```

## Selenocysteine and Pyrrolysine Are Silently Lost

Selenocysteine (Sec, one-letter U) is encoded by UGA and pyrrolysine (Pyl, one-letter O) by UAG, both normally stop codons. Recoding requires a SECIS (Sec) or PYLIS (Pyl) element that Biopython does NOT detect. No NCBI table maps UGA->U or UAG->O. Naive translation therefore yields `*` mid-protein, and `to_stop=True` SILENTLY truncates the protein at that position. Real selenoproteins (GPX, TXNRD, SELENOP) come out truncated or peppered with `*`. There is no clean Biopython workaround; flag these genes and handle the recoding event manually.

## Six-Frame Translation

**Goal:** Translate a DNA sequence in all six frames (three forward, three reverse) to expose every possible protein product.

**Approach:** For each strand, offset by 0, 1, 2 bases, trim to a multiple of 3, and translate.

**Reference (BioPython 1.83+):**

```python
def six_frame_translation(seq):
    frames = []
    for strand, s in [('+', seq), ('-', seq.reverse_complement())]:
        for frame in range(3):
            length = 3 * ((len(s) - frame) // 3)
            fragment = s[frame:frame + length]
            frames.append((strand, frame, fragment.translate()))
    return frames

seq = Seq('ATGCGATCGATCGATCGATCG')
for strand, frame, protein in six_frame_translation(seq):
    print(f'{strand}{frame}: {protein}')
```

## Find All ORFs (Start to Stop)

**Goal:** Identify all open reading frames (Met to stop) across both strands and all three frames, keeping only those above a minimum length.

**Approach:** Translate each of the six frames, then scan each translation for Met-to-stop segments meeting the threshold.

**Reference (BioPython 1.83+):**

```python
def find_orfs(seq, min_protein_length=30):
    orfs = []
    for strand, s in [('+', seq), ('-', seq.reverse_complement())]:
        for frame in range(3):
            end = frame + 3 * ((len(s) - frame) // 3)
            trans = str(s[frame:end].translate())
            aa_start = 0
            while True:
                start = trans.find('M', aa_start)
                if start == -1:
                    break
                stop = trans.find('*', start)
                if stop == -1:
                    stop = len(trans)
                orf = trans[start:stop]
                if len(orf) >= min_protein_length:
                    orfs.append((strand, frame, start * 3 + frame, orf))
                aa_start = start + 1
    return orfs

seq = Seq('ATGCGATCGATCGATCGATCGTAA')
for strand, frame, pos, orf in find_orfs(seq, min_protein_length=3):
    print(f'{strand} frame {frame} pos {pos}: {orf}')
```

## Inspect a Codon Table

```python
table = CodonTable.unambiguous_dna_by_id[2]
table.start_codons                   # ['ATT', 'ATC', 'ATA', 'ATG', 'GTG']
table.stop_codons                    # ['TAA', 'TAG', 'AGA', 'AGG']
table.forward_table['TGA']           # 'W' under vertebrate mito code
```

## Common Errors

| Symptom | Cause | Fix |
|---------|-------|-----|
| Plausible protein, wrong residues, no error | Valid-but-wrong `table=` (e.g. mito DNA on table 1) | Select the organism's NCBI table; use `cds=True` to validate |
| `*` mid-protein or premature truncation | Selenoprotein/pyrrolysine UGA/UAG, or wrong table where UGA=Trp | Use correct mito table for UGA=Trp; Sec/Pyl recoding is not automatic |
| `TranslationError: First codon ... is not a start codon` | `cds=True` on a sequence not starting at a valid start for that table | Trim to the true start, or pick the table whose starts include it |
| `TranslationError: ... is not a multiple of three` | `cds=True` on a partial CDS | Trim to a full ORF; without `cds=True` this only warns and drops trailing bases |
| `TranslationError: Extra in frame stop codon found` | Internal stop under `cds=True` | Wrong frame, wrong table, or genuine internal stop; re-check frame/table |
| Garbage protein from a protein input | `transcribe()`/`translate()` on a non-nucleotide Seq (no type checks since 1.78) | Verify molecule type before converting |
| `KeyError` | Unknown table id or name | Use a valid NCBI id (1-6, 9-16, 21-31) or registered name |

## Decision Tree

```
Need to convert a sequence?
├── DNA <-> RNA (string-level T<->U)?
│   ├── coding strand to RNA -> seq.transcribe()
│   ├── RNA back to DNA      -> seq.back_transcribe()
│   └── template strand to mRNA -> seq.reverse_complement().transcribe()
├── DNA/RNA to protein?
│   ├── alignment-derived CDS    -> alignment/multiple-alignment (codon-aware), or project wrapper
│   ├── complete CDS to validate -> translate(cds=True) [loud on defects]
│   ├── stop at first stop only  -> translate(to_stop=True)
│   ├── non-standard organism    -> translate(table=N)  [pick from the table above]
│   └── show internal stops      -> translate()  [* per stop]
└── Unknown coding regions? -> six-frame translation, then scan M...* for ORFs
```

## Related Skills

- seq-objects - Create and inspect Seq objects before translation
- reverse-complement - Strand handling for six-frame translation and template-strand transcription
- codon-usage - Analyze codon bias and adaptation in coding sequences
- sequence-io/read-sequences - Parse GenBank/FASTA records and CDS features for translation

## References

The genetic-code tables and their organism assignments follow the NCBI Taxonomy "The Genetic Codes" page, compiled by Andrzej (Anjay) Elzanowski and Jim Ostell at NCBI (https://www.ncbi.nlm.nih.gov/Taxonomy/Utils/wprintgc.cgi). This is a maintained web resource; cite it as the NCBI page rather than as a journal article.

