Branching Processes
A branching process models a population in which each individual independently produces a random number of offspring drawn from a fixed offspring distribution. The classic version is the Galton–Watson process, and it answers a sharp question: starting from a single ancestor, does the lineage die out or grow without bound?
The Galton–Watson process
Label the generations , starting with one individual in generation . Each individual, independently, leaves a random number of offspring with probability mass function . Let be the population size in generation , so and each individual in generation founds the next generation.
The offspring distribution has a mean, the expected number of children per individual, This single number controls the qualitative fate of the process.
Expected growth
Because individuals reproduce independently, expectations multiply across generations. Conditioning on the previous generation gives , and taking expectations again yields The expected population size grows or shrinks geometrically at rate .
In the epidemic reading, one individual is one infection and its “offspring” are the people it infects, so is exactly the basic reproduction number . The mean chain then grows like , the same quantity that the next-generation matrix computes for structured populations and that drives the early exponential phase of an SIR outbreak.
The probability generating function
The natural bookkeeping tool for offspring counts is the probability generating function (PGF) It packages the whole offspring distribution into one function, much as the moment generating function does for continuous variables. Its derivative at recovers the mean, , and composing with itself tracks successive generations.
Extinction
Let be the probability of ultimate extinction — that the lineage eventually reaches size . Extinction of the whole process happens exactly when each of the first generation’s sub-lineages goes extinct, and those are independent copies of the original. This self-similarity gives the fixed-point equation The extinction probability is the smallest solution of in the interval .
Classifying by the mean :
- Subcritical (): the only fixed point up to forces ; extinction is certain.
- Critical (): still ; the process dies out with probability one (though it can drift large first).
- Supercritical (): there is a fixed point , so the process survives with positive probability .
The gap between the critical case and genuine growth is why small populations can vanish by chance even when conditions favor increase — the same stochastic fragility seen in genetic drift.
Outbreaks: will an introduction take off?
Branching processes describe the early, stochastic phase of an outbreak, when the susceptible pool is still effectively unlimited. A single imported case starts one lineage of infections; either it fizzles out or it seeds a major outbreak.
A common and tractable choice is a Poisson offspring distribution with mean (see the Poisson distribution), whose PGF is . The extinction probability solves and the probability of a major outbreak is . When every introduction eventually dies out; only gives a genuine chance of takeoff.
Worked example
Take a Poisson offspring distribution with , so we solve One root is , but we want the smallest root in . Iterating from gives , then , , , converging to So the probability of extinction from a single introduction is about , and the probability of a major outbreak is Even with , roughly one in five introductions fizzles out purely by chance.
In code
R
# Extinction probability by fixed-point iteration: s = G(s)
G <- function(s, R0 = 2) exp(R0 * (s - 1)) # Poisson offspring PGF
q <- 0
for (i in 1:100) q <- G(q)
q # ~ 0.2032
1 - q # ~ 0.7968 (major-outbreak probability)
# Simulate branching trees and estimate extinction empirically
set.seed(1)
sim_extinct <- function(R0 = 2, gens = 40) {
z <- 1
for (g in 1:gens) {
z <- sum(rpois(z, R0)) # each individual has Poisson(R0) offspring
if (z == 0) return(TRUE) # extinct
}
FALSE # still alive -> treat as survival
}
mean(replicate(10000, sim_extinct())) # ~ 0.20, matches q
Python
import numpy as np
def G(s, R0=2.0): # Poisson offspring PGF
return np.exp(R0 * (s - 1))
q = 0.0
for _ in range(100):
q = G(q)
print(q, 1 - q) # ~ 0.2032 0.7968
rng = np.random.default_rng(1)
def sim_extinct(R0=2.0, gens=40):
z = 1
for _ in range(gens):
z = rng.poisson(R0, size=z).sum() # offspring of current generation
if z == 0:
return True
return False
print(np.mean([sim_extinct() for _ in range(10000)])) # ~ 0.20
Julia
using Distributions, Statistics, Random
G(s; R0=2.0) = exp(R0 * (s - 1)) # Poisson offspring PGF
q = 0.0
for _ in 1:100
q = G(q)
end
println((q, 1 - q)) # ~ (0.2032, 0.7968)
Random.seed!(1)
function sim_extinct(; R0=2.0, gens=40)
z = 1
for _ in 1:gens
z = sum(rand(Poisson(R0), z))
z == 0 && return true
end
false
end
println(mean(sim_extinct() for _ in 1:10000)) # ~ 0.20
Evolutionary emergence: the Antia–Bergstrom model
A single number hides a subtler question for a newly introduced pathogen: what if the strain that spills over is poorly adapted to the new host () but can evolve as it spreads? Antia, Regoes, Koella & Bergstrom (2003) answered this with a multi-type branching process, and it is a beautiful application of everything above.
Model each case’s secondary infections as a binomial offspring distribution: an infected host contacts others and infects each independently with probability , so offspring with mean and PGF . The introduced wildtype is subcritical, , so on its own it always dies out. But at each transmission the pathogen mutates with small probability to an adapted mutant with . Emergence is the event that an adapted lineage establishes before the wildtype chain burns out.
Two branching-process facts combine to give the answer. First, a single mutant establishes with probability , where is the extinction probability of the mutant process — the smallest root of . Second, a subcritical wildtype outbreak seeded by one case produces, in expectation, secondary infections in total (the geometric sum of ). Each of those transmissions throws off a mutant with probability , so the expected number of established mutant lineages is , and — treating rare mutant lineages as independent — the probability of emergence is
The headline result is in the factor : as it blows up, so a pathogen that almost spreads lingers long enough to give evolution many attempts at rescue. A spillover strain with just below is therefore far more dangerous than its subcritical label suggests — the central public-health message of the paper.
Worked example
Take contacts, wildtype (so ), mutant (so ), and mutation probability . Solving gives , so a mutant establishes with probability . The wildtype throws off secondary infections on average, so . Push the wildtype closer to threshold, : now it throws off infections and — a tenfold jump in emergence risk from the same mutation rate.
n <- 10; pw <- 0.09; pm <- 0.15; mu <- 1e-3
Rw <- n * pw; Rm <- n * pm # 0.9 (subcritical), 1.5 (supercritical)
# Mutant establishment probability: smallest root of q = (1 - pm + pm q)^n
q <- 0; for (i in 1:1000) q <- (1 - pm + pm * q)^n
pi_est <- 1 - q # ~0.63
# Rare-mutation emergence probability
P_emerge <- 1 - exp(-mu * Rw / (1 - Rw) * pi_est) # ~0.0057
c(pi_est = pi_est, P_emerge = P_emerge)
# Monte Carlo check: two-type binomial branching process
set.seed(1)
establishes <- function(p, gens = 60) { # does one mutant lineage survive?
z <- 1
for (g in 1:gens) { z <- sum(rbinom(z, n, p)); if (z == 0) return(FALSE) }
TRUE
}
emerge_once <- function() {
z <- 1
for (g in 1:300) {
if (z == 0) return(FALSE) # wildtype burned out, no emergence
kids <- sum(rbinom(z, n, pw)) # wildtype secondary infections
n_mut <- rbinom(1, kids, mu) # how many mutated
if (n_mut > 0 && any(replicate(n_mut, establishes(pm)))) return(TRUE)
z <- kids - n_mut
}
FALSE
}
mean(replicate(20000, emerge_once())) # ~0.006, matches the formula
import numpy as np
n, pw, pm, mu = 10, 0.09, 0.15, 1e-3
Rw = n * pw
q = 0.0
for _ in range(1000):
q = (1 - pm + pm * q) ** n # mutant extinction prob
pi_est = 1 - q # ~0.63
P_emerge = 1 - np.exp(-mu * Rw / (1 - Rw) * pi_est) # ~0.0057
print(pi_est, P_emerge) # the R Monte-Carlo check translates directly
0.6284894704844379 0.005640437894373074
n, pw, pm, mu = 10, 0.09, 0.15, 1e-3
Rw = n * pw
q = 0.0
for _ in 1:1000
q = (1 - pm + pm * q)^n # mutant extinction prob
end
pi_est = 1 - q # ~0.63
P_emerge = 1 - exp(-mu * Rw / (1 - Rw) * pi_est) # ~0.0057
println((pi_est, P_emerge))
Why it matters
Branching processes turn a vague worry — “could this spread?” — into a precise probability, separating the deterministic message of from the luck of small numbers. They explain why an outbreak with can still fail to establish, why small populations wink out despite favorable growth, and how genealogies and family names go extinct. The same machinery underlies nuclear chain reactions, PCR amplification, surname survival, and the founding dynamics of new mutations.