An allele copied at random each generation, in a population small enough that luck alone moves it. Watch where it ends up.
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))
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 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)
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))