Click any figure for the code that produces it. Every example simulates with ZOMBI2 and plots with Phylustrator, a separate package — so to run one you need both:
pip install zombi2 phylustrator
Pure birth, no extinction — a forward tree of 100 lineages.
pure birth (Yule)The full history behind 50 survivors — their branches solid, extinct lineages dashed.
birth–deathA pulse at t = 3 culls 75% of lineages — the skyline drops sharply at the dashed line, then recovers.
mass extinction · + skylineSpeciation runs slow, then fast, then slow — the burst packs branches between the two dashed regime lines.
time-varying birthSpeciation slows as diversity fills up; the skyline rises and plateaus at the cap of 100.
birth–death · + skylineTwo thousand trees of 100 tips under each of two processes — diversity-dependence bends the lineage curves over (left), and the γ statistic separates the two sets of trees almost perfectly (right).
simulation study · 4000 treesA genome as a ring — genes evenly spaced by rank, coloured by family, arrows by strand. plot(g, layout="circular") + genes().
Two genomes, one per row; ribbons link same-family genes and cross where the order was rearranged. stack([a,b]) + synteny().
Every tip's gene order beside the tree, homologues ribboned between neighbours. Genes are coloured by their position in the ancestral order, so a genome still in that order is a clean gradient and each rearrangement is a break in it. beside(plot(tree), tracks(genomes, reference=…)).
One family's history on the species tree: duplications (squares), losses (crosses) and transfers (arrows, donor→recipient). plot(tree) + branch_events(…).
A family × genome copy-number heatmap, its rows locked to the tips. beside(tree, heatmap(profiles)).
A real bacterium — Mycoplasma genitalium, 546 genes at their true base positions, coloured by strand; the forward/reverse switch marks the replication origin. read_gff(…).
One inversion on a circular genome: the affected segment is reversed and its strands flip (the arrows turn round). The band marks the segment in both rings. highlight(g, start, end) + genes().
Transfers steered to run between two clades (a Clades + Between kernel — topology, not a trait). Tree coloured by clade; the barplot counts transfers by clade pair, so A↔B towers over within-clade.
Every lineage draws its own rate, with no memory of its parent, so neighbouring branches are unrelated — the colour is salt-and-pepper. substitution = ByLineage(spread).
The same independent draw with a gamma instead of a lognormal, where spread is the coefficient of variation. The two agree to first order in spread. ByLineage(spread, dist="gamma").
A daughter starts at its parent's rate and is nudged, so relatives run at similar rates and the colour moves in clades rather than branch to branch. substitution = FromParent(spread).
The same inherited drift in steps: the rate takes one of a few values and a daughter moves to a neighbouring one, which is what a rate-category model assumes. FromParent(spread, bins=6).
A small tree with its internal nodes numbered (0 = initial genome, 1 = crown, …); beside it the reconstructed sequence at each — one free-floating row per node, not aligned to the tips. seqs.ancestral.
A single-copy family across 20 species, residues coloured (with a nucleotide key), each row locked to its tip. beside(tree, alignment(aln)).
Free diffusion — sister lineages drift apart with time.
continuousPulled to an optimum: a high start (yellow) converges to blue.
continuousA two-state trait hops between habitats; each branch is painted by its state history.
discrete · MkTwo traits evolve together (r = 0.9) — two trees, coloured by each trait, and the tip scatter.
continuous · + scatterTwo binary characters where one's flip rate depends on the other's state. Two trees, coloured by each character (X green, Y purple), so you can see Y is present where X is; the 2×2 chain (arrow width = rate) is the model. simulate_discrete(states=("00",…), switch={…}).
A driver (a trait for the habitat) modifies the variance-rate of body size (the target). Body size wanders where the habitat fluctuates and barely moves where it is stable. The tree is coloured by the habitat, and below it body size runs through time, each piece coloured by the habitat it evolved under. rate = 0.25 * mod.DrivenBy(habitat, {…}).
A driver (a trait for the lifestyle) modifies the rate of loss (the target). Endosymbionts also gain genes more slowly, so their genomes shrink. The tree is coloured by the lifestyle and the bars are genome size at each tip.
trait → lossA driver (a trait for the strength of selection) modifies the rate of duplication (the target). Under relaxed selection duplicates accumulate and the genomes grow. The tree is coloured by the selection regime and the bars are genome size at each tip.
trait → duplicationA driver (a trait for competence) modifies who receives a transfer (the target), not a rate. Competent lineages take up DNA more often and their genomes grow. The tree is coloured by competence and the bars are genome size at each tip.
trait → transfer uptakeA driver (a diffusing continuous trait) modifies the rate of origination (the target). A Curve turns each value into a factor, so genome size follows the trait. The tree is coloured by the trait value and the bars are genome size at each tip.
The same run with a different Curve. Gene gain switches on as the trait rises and then levels off at a ceiling, so the factor is bounded — once resources are plentiful, more of them buys nothing.
The same run again, with the response fastest at an intermediate value and falling away on both sides. A table of per-state multipliers can express neither this nor the saturating curve: the tallest bars sit in the middle of the colour ramp, not at its top.
continuous trait → originationDriver and target are both traits on one tree. A temperature trait is grown first; the rate at which body size diffuses then reads it. The same tree is painted twice — by the driver, then by what it drove, on a scale centred where the trait started, so white means has not moved. The cold half of the tree stays white.
trait → traitThe other direction of the same relation. A driver (a named gene family, present or absent) modifies one direction of a trait's switch (the target): carrying a toxin makes a lineage become pathogenic forty times faster, but does not help it revert. The same tree is painted twice — by the gene, then by what the gene drove — so the answer is in the alignment of the two: 80% of the tips carrying the gene end up pathogenic against 37% of those without. Presence changes mid-branch, at the instant the last copy went.
gene → traitGenes rarely act alone. Four families make up aerobic respiration, and a driver (completion, the fraction of the module a lineage keeps) modifies the trait that decides its metabolism. The response is discontinuous — lambda f: 20.0 if f > 0.5 else 1.0 — so more than half the module makes a lineage aerobic and less makes it revert. That is where a threshold belongs in ZOMBI2: in the mapping, alongside every other response shape, rather than as its own kind of driver. 97% of the tree's branch length has the trait on the side of the threshold its completion is.
A two-state trait drives speciation — the fast state's clades take over; the inset is the state Markov chain.
trait → speciationOne state dies far faster; the doomed lineages (dashed) drop out.
trait → extinctionThree graded speciation rates with constant death — the fastest state fills the tree, extinct lineages dashed.
trait → speciation