BIO 202, Spring 2026, draft v1. Wright-Fisher drift. Allele frequencies stagger around at random until they hit zero or one and the variation is gone.
Four stages. Watch trajectories. Watch them hit absorbing boundaries. Watch the fixation-probability statistic equal the starting frequency for neutral alleles. End at the Buri flies — Wright-Fisher in a lab.
A single allele drifts in a Wright-Fisher population. Slide N, slide p₀, slide seed. Watch one realization. Toggle the fan to see 50.
Each generation, sample 2N alleles with replacement from the previous generation. That is the Wright-Fisher rule. The expected next-generation frequency is the current frequency; the variance is p(1−p)/(2N).
Click two points on the canvas to mark the upper and lower bound of where you expect most replicate trajectories to land at generation 200. Then toggle the fan to check.
set.seed(42)N <- 100p <- 0.5gens <- 200traj <- numeric(gens + 1); traj[1] <- pfor (g in 1:gens) { p <- rbinom(1, 2*N, p) / (2*N) traj[g + 1] <- p}plot(traj, type = "l", ylim = c(0,1))
Let the simulation run until every replicate is at 0 or 1. Plot the distribution of times-to-fixation. Mean is around 4N for neutral alleles starting at p = 0.5.
500 replicates, run until each is fixed. Histogram the fixation times. The expected mean for a neutral allele at p = 0.5 is about 2.8N generations (Kimura's classical result); conditional on fixation, ~4N.
set.seed(42)N <- 50; p0 <- 0.5; reps <- 500tfix <- replicate(reps, { p <- p0; g <- 0 while (p > 0 && p < 1) { p <- rbinom(1,2*N,p)/(2*N); g <- g + 1 } g})mean(tfix); hist(tfix)
For a neutral allele, the probability of fixation equals the starting frequency. Departures from this line are the signature of selection.
For each of 8 starting frequencies (0.1, 0.2, …, 0.9), run 300 replicates. Plot fraction that fixed the allele against p₀. The diagonal is the neutral prediction; deviation is selection (or sampling noise).
set.seed(42)N <- 50; s <- 0; reps <- 300p0s <- seq(0.1, 0.9, 0.1)pfix <- sapply(p0s, function(p0) { mean(replicate(reps, { p <- p0 while (p > 0 && p < 1) { p_sel <- p*(1+s)/(p*(1+s)+(1-p)) p <- rbinom(1,2*N,p_sel)/(2*N) } p == 1 }))})plot(p0s, pfix); abline(0, 1)
Peter Buri tracked the bw75 allele in 107 Drosophila lines for 19 generations, with 16 flies per line per generation. The variance in allele frequency across lines should grow at the rate Wright-Fisher predicts.
Buri's empirical trajectories overlaid with a Wright-Fisher simulation at N=16 (the actual sample size each generation). The simulation matches the observed spread — drift accounts for the variance.
buri <- read.csv("data/clean/buri_fly.csv")set.seed(42); N <- 16; gens <- 19; lines <- 107; p0 <- 0.5sim <- replicate(lines, { p <- p0; traj <- p for (g in 1:gens) { p <- rbinom(1,2*N,p)/(2*N); traj <- c(traj, p) } traj})matplot(t(sim), type = "l", col = rgb(0,0,0,0.1))