Skip to content

Tom Janvier

Data Scientist

Valaire, France

Stop Codon Frequency Analyser

Genomics · Statistics

Source on GitHub

Python pipeline over 202,388 bacterial genomes and 536,170,378 coding sequences from AllTheBacteria, measuring how often each of the three stop codons is used. GC content is the dominant driver of stop codon bias (r = -0.855 for TAA, r = 0.749 for TGA), and Bradyrhizobium is a genuine outlier, holding TAG near 0.28 where TGA should dominate. The headline frequency reverses depending on whether genomes or species are weighted equally, which says as much about the sample as about the biology.

Built with

Python, Biopython, Pandas, Matplotlib, Requests, Bakta

Genomes
202,388
Coding sequences
536,170,378
Species labels
208
TAA / TGA / TAG
56.45% / 26.73% / 16.81%
TAA vs GC
r = -0.855
TGA vs GC
r = 0.749
Genome size vs TAA, GC held
r = 0.048
Invalid triplets
0

The question

All three stop codons end translation, so you might expect them to be interchangeable. They are not. Species, and even genes within a species, prefer particular ones, and TAA is the fastest to translate with the lowest read-through error rate. Small studies had reported that TAA dominates AT-rich genomes while TGA takes over in GC-rich ones. The question this project asks is whether that pattern survives being asked of hundreds of thousands of genomes at once.

The source is AllTheBacteria, which held 2,440,377 assemblies at release 2024-08. Storage and processing time capped the run at a 202,388-genome subset, and the consequences of that choice turned out to matter more than anything else in the project.

How the pipeline works

The system splits in two. An analysis module handles one genome at a time and can be pointed at a single pair of files; a batch pipeline handles fetching, matching and cleanup. Keeping them apart meant the extraction logic could be run and checked in isolation.

Each genome arrives as a FASTA assembly plus a Bakta JSON annotation giving CDS coordinates and strand. For every annotated coding sequence the module takes the terminal triplet: the last three bases on the positive strand, or the reverse complement of the first three on the negative, since Bakta records negative-strand features by their position on the forward sequence. Bakta's coordinates are one-based and inclusive, so each one is converted to Python's zero-based indexing.

The full archive set would have needed roughly 150 GB of disk, which was not available, so the pipeline pulls one archive pair at a time, processes it, and deletes it before moving on. Rows are appended to the CSV as they are produced rather than held in memory, so an interrupted run resumes without losing completed genomes. Bakta archive URLs are discovered at run time through the AllTheBacteria OSF API instead of being hardcoded.

Flow diagram: AllTheBacteria and OSF archives feed manifests, which feed batch_pipeline.py, which pairs FASTA and Bakta JSON into analysis.py, through strand-aware extraction and triplet validation, into a results CSV and then the figures.
Pipeline architecture. Downloaded archives are deleted before the next batch, which is what keeps the run inside the available disk.

GC content drives the bias

Across the 202,388 genomes TAA was the most common stop codon at 56.45% (302,692,618 counts), then TGA at 26.73% (143,325,034) and TAG at 16.81% (90,152,726). GC content ranged from 26.89% to 73.61% with a mean of 40.47%.

The correlation with base composition is strong and in the direction theory predicts: r = -0.855 between GC content and TAA usage, r = 0.749 for TGA. These are the two largest coefficients anywhere in the matrix. At species level the effect is starker still. Brachybacterium muris averages a TGA proportion of 0.910 at 70.0% GC, several Burkholderia species run 0.71 to 0.78 at 67-68% GC, and at the other end Brachyspira hyodysenteriae averages 0.805 TAA at just 27.1% GC.

TAG behaves differently. It stays roughly flat across the whole GC range, correlating only weakly with composition at r = 0.259, which suggests it is under less compositional pressure than the other two.

Two panels. Left: binned trend lines for TAA, TAG and TGA against genomic GC content across all genomes. Right: species means for the same three codons, sized by number of genomes.
Stop codon proportion against genomic GC content. TAA falls and TGA rises as GC content increases; TAG stays flat. The crossover sits inside the 49-57% band, which is almost unsampled here.

Genome size is GC content in disguise

Genome size looks like it matters: r = -0.417 for TAA and r = 0.455 for TGA, with TAA holding near 0.58-0.62 up to 5 Mbp, dropping sharply between 6 and 8 Mbp as TGA climbs to 0.64-0.66, then partially recovering above 9 Mbp.

It does not survive a control. GC content and genome size are themselves correlated at r = 0.708 in this dataset, because the largest genomes here belong to GC-rich species such as Burkholderia. Holding GC constant with a species-level partial correlation collapses the genome size to TAA association to r = 0.048. Once base composition is accounted for, genome size carries almost no signal of its own.

The extremes are also not believable genomes. 228 assemblies fall below 1 Mbp, the smallest a 67.5 kb fragment carrying 100 CDS, and the largest is a 14.93 Mbp assembly split across 9,115 contigs, far more likely contaminated than genuinely large.

Three scatter panels plotting TAA, TAG and TGA proportion against genome size in megabases, each with a binned trend line.
Genome size against stop codon proportion. The apparent trend in the 5-9 Mbp range is carried by a handful of GC-rich species rather than by size itself.

The result that reverses

The most interesting finding is not about biology. The headline frequencies depend on how the dataset is weighted: TAA is the most common stop codon when every genome counts equally, but TGA is the most common when every species counts equally.

The sample is why. Streptococcus pneumoniae alone accounts for 70,851 of the 202,388 genomes; the three most represented species account for 58.8% and the ten most for 88.7%. The genome counts are dominated by AT-rich pathogens, while the species roster leans GC-rich because 86 of the 207 named labels are represented by a single genome. Neither weighting is unbiased, and the true domain-wide values must sit somewhere between them.

There is a corroborating detail. Korkmaz et al. report TAG at roughly 20% across the GC range. The species-weighted figure here is 0.198, which matches closely; the genome-weighted figure of 0.168 does not. A large dataset is not automatically a representative one.

Correlation heatmap across TAA, TAG, TGA, GC content, genome size, CDS count, average CDS length and contig count.
Correlation matrix. The TAA to TGA figure of -0.93 is largely compositional arithmetic: the three proportions sum to one and TAG barely moves, which forces the other two to move inversely.

Bradyrhizobium

One genus refuses to follow the pattern. Bradyrhizobium japonicum averages a TAG proportion of 0.280 and B. diazoefficiens 0.278, with related species also well above the dataset average, all at GC contents of 63-64% where TGA would normally dominate. Base composition does not explain it, which points instead at the release factor complement in the lineage. It is the clearest candidate in the dataset for a follow-up.

What the zero invalid count does and does not prove

No invalid triplet was recorded anywhere in 536 million coding sequences. That figure needs reading carefully rather than celebrating. Bakta annotates coding sequences as open reading frames that end in a stop codon, so a valid triplet is what you would expect by construction, and a zero count is not on its own evidence that the extraction is biologically correct.

What it does rule out is a systematic fault. An off-by-one error converting Bakta's one-based inclusive coordinates, a mishandled negative strand, or a failed contig lookup would each have produced a large invalid rate, and at this scale none of them is present.

Where it breaks down

Treating each genome as an independent data point is the main statistical weakness. Genomes from one species share an evolutionary history, so analysing 202,388 of them without accounting for phylogenetic structure inflates the effective sample size and likely exaggerates the correlations. Species-level averages, or a phylogenetically adjusted method such as PGLS, would be more defensible.

Coverage has real holes. Only 1,124 genomes, 0.56% of the total, and four of the 207 named species fall between 49% and 57% GC, which is exactly where the TAA/TGA crossover sits. Escherichia, Salmonella, Pseudomonas aeruginosa, Staphylococcus aureus and Mycobacterium are absent from the dataset entirely, and those are the mid-GC organisms that would fill the gap.

Assembly quality was filtered only indirectly, since Bakta's PASS status certifies the annotation rather than the assembly. Applying a modest screen afterwards, at least 1 Mbp, no more than 500 contigs, named species only, removes 2.8% of the data and barely moves the results: TAA goes from 56.45% to 56.91% and the GC/TAA correlation from -0.855 to -0.849. The conclusions hold, but the screen should have been applied from the start.

One weakness was in the planning rather than the code. The codon counting was first implemented against GFF3 on the assumption that this was the format AllTheBacteria distributed. The annotations are Bakta JSON, and the parsing had to be rewritten. Opening a single annotation file before committing to a format would have avoided the whole cost.

What comes next

Extending coverage into the 49-57% GC range is the most useful next step, since it closes the gap and brings in reference organisms like E. coli. Beyond that: species-level summaries as the unit of analysis, formal outlier detection using per-genome Z-scores against the genus mean, and an automated pytest suite built on the existing synthetic test file. The pipeline is already capable of all of it; what it needs is a broader and better chosen sample.