Lesson 11 — Reading drift in the wild when a population gets small

BIO 202, Spring 2026, draft v1. Florida Scrub Jay. A real population whose effective size dropped from habitat fragmentation, leaving a measurable drift signature.

What you'll do

Heterozygosity decays at rate 1 − 1/(2Nₑ) per generation. Fit Nₑ from the decay. Detect a bottleneck. Apply to FSJ data.

There are 9 billion people on the planet. You can't mate with most of them... A normal human meets about 10,000–20,000 people over a lifetime they could have biological kids with. So for human purposes, our effective population size is 10,000–20,000.— 202_lec10_06

A — Heterozygosity decay at rate 1 − 1/(2Nₑ)

Watch heterozygosity fall over time. Slide Nₑ; watch the decay rate change. Larger Nₑ → slower decay.

Locked — confirm your name above to begin.

Scenario

One locus, observed heterozygosity Hₜ. Each generation: H decays by factor (1 − 1/(2Nₑ)). Plot the trajectory. Bigger Nₑ → slower decay. The decay rate per generation is the inverse of "effective genetic time."

Heterozygosity over time

Nₑ: 100  |  decay rate: /gen  |  half-life (≈ 1.4·Nₑ gen):

Prediction

  1. Q1. The half-life of heterozygosity (time for H to drop to H₀/2) scales as:
Try at least 4 Nₑ values. 0/4 values

Controls

100
500

R code — heterozygosity decay

Ne <- 100gens <- 500H0 <- 0.5H <- H0 * (1 - 1/(2*Ne))^(0:gens)plot(0:gens, H, type = "l", ylim = c(0, H0))

B — A bottleneck — Nₑ drops, then recovers

Two-epoch demography. Slide the bottleneck depth and duration. Watch the heterozygosity trajectory bend.

Complete Stage A.

Scenario

Pre-bottleneck Nₑ = 1000. At generation 200, Nₑ drops to a slider value (the bottleneck) for some duration, then recovers. Heterozygosity decays slowly, then crashes, then resumes slow decay (now from a lower starting point).

Heterozygosity through a bottleneck

bottleneck Nₑ: 50  |  duration: 50 gen  |  % H lost in bottleneck:

Prediction

  1. Q1. What matters more for total heterozygosity loss in a bottleneck — depth (how small Nₑ gets) or duration (how long it stays small)?
Try at least 5 (depth, duration) combos. 0/5 combos

Controls

50
50

R code — two-epoch demography

Ne_pre <- 1000; Ne_bot <- 50; dur <- 50gens <- 800; t_bot <- 200; H <- numeric(gens+1); H[1] <- 0.5for (g in 1:gens) {  Ne <- if (g >= t_bot && g < t_bot + dur) Ne_bot else Ne_pre  H[g+1] <- H[g] * (1 - 1/(2*Ne))}

C — Fit Nₑ from an observed heterozygosity trajectory

Given noisy H̄ measurements over generations, find the Nₑ that best explains the decay. Profile-likelihood-style — scan Nₑ values, find the minimum sum-of-squared residuals.

Complete Stage B.

Scenario

Simulate H̄ trajectories with a known true Nₑ plus measurement noise. Fit Nₑ by scanning candidate values. Read off the minimum and the 95% CI from the SSR curve.

Sum-of-squared-residuals vs Nₑ

true Nₑ: 200  |  best-fit Nₑ:  |  95% CI:

Prediction

  1. Q1. If you fit Nₑ from a 100-generation H̄ trajectory with 10% measurement noise, the 95% CI on Nₑ will:
Try at least 3 noise levels. 0/3 noise levels

Controls

200
0.010
42

R code — fit Nₑ by SSR

Hobs <- read.csv("data/clean/fsj_allele_freq.csv")Ne_grid <- seq(10, 5000, 10)ssr <- sapply(Ne_grid, function(N) {  Hpred <- Hobs$H[1] * (1 - 1/(2*N))^Hobs$gen  sum((Hobs$H - Hpred)^2)})Ne_grid[which.min(ssr)]

D — Florida Scrub Jay across the decades

Allele frequency time courses from a wild population. Find Nₑ. Decide whether you need a one-epoch or two-epoch fit.

Complete Stage C.

Scenario

Loci from data/clean/fsj_allele_freq.csv across decades. Mean heterozygosity per year is plotted. Fit a one-epoch model (constant Nₑ) vs a two-epoch (bottleneck) model. The faint blue trajectories around the 1-epoch fit are five simulated decay paths under the same fitted Nₑ — they show how much noise constant-Nₑ drift alone produces. Compare the observed dots to that envelope, not just to the mean line.

Observed FSJ heterozygosity over time

one-epoch fit Nₑ:  |  two-epoch fit:  |  ΔSSR:

Prediction

  1. Q1. After the synthetic noise envelope is drawn under the 1-epoch fit, do the FSJ observed points sit inside that envelope or outside it?
Run the fit at least 2 times. 0/2 runs

Controls

42

R code — FSJ Nₑ

fsj <- read.csv("data/clean/fsj_allele_freq.csv")H <- tapply(2*fsj$p*(1-fsj$p), fsj$year, mean)yrs <- as.numeric(names(H))Ne_grid <- seq(10, 5000, 10)ssr <- sapply(Ne_grid, function(N) {  Hp <- H[1] * (1 - 1/(2*N))^(yrs - yrs[1])  sum((H - Hp)^2)})