# Setting Up Python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)Genetic Drift and Fixation: Understanding Evolution Through Randomness
A Simulation-Based Tutorial Using the Wright-Fisher Model
When people think about evolution, they often think about natural selection.
Natural selection explains how advantageous genetic variants become more common because they improve survival or reproduction.
However, natural selection is not the only force driving evolution.
Even in the complete absence of selection, allele frequencies can change over time simply because populations are finite.
This phenomenon is called genetic drift.
Genetic drift is one of the most fundamental concepts in population genetics because it explains:
- Random changes in allele frequencies
- Loss of genetic diversity
- Founder effects
- Population bottlenecks
- Neutral evolution
- Fixation and extinction of alleles
Understanding drift is essential for modern genomics, GWAS, evolutionary biology, conservation genetics, and statistical genetics.
In this tutorial we will study:
- What genetic drift is
- Why fixation occurs
- The Wright-Fisher model
- Important theoretical results
- Simulation of allele-frequency trajectories
- Verification of classical population genetics theorems
0.1 Learning Objectives
By the end of this tutorial you should be able to:
- Explain genetic drift intuitively
- Derive the Wright-Fisher model
- Understand fixation and extinction
- Simulate neutral evolution
- Interpret allele frequency trajectories
- Understand why small populations drift faster than large populations
- Connect drift to modern genomic studies
0.2 Prerequisites
This tutorial assumes familiarity with:
- Basic probability
- Binomial distributions
- Python programming
- Elementary genetics
No prior knowledge of population genetics is required.
0.3 What is an Allele?
A gene can exist in different versions called alleles.
For example:
| Gene | Alleles |
|---|---|
| Eye color gene | Blue, Brown |
| SNP rs12345 | A, G |
Suppose a locus contains two alleles:
\[ A \]
and
\[ a \]
Let \(p\) denote the frequency of allele A.
Then
\[ 1-p \]
is the frequency of allele a.
For example:
| Allele | Frequency |
|---|---|
| A | 0.7 |
| a | 0.3 |
The frequencies always sum to one.
\[ p + (1-p)=1 \]
0.4 Infinite vs Finite Populations
Imagine an infinitely large population.
If:
- No mutation
- No migration
- No selection
- Random mating
then allele frequencies remain constant forever.
This idealized situation is the basis of the Hardy-Weinberg equilibrium.
Real populations are never infinite.
Because populations are finite, each generation represents a random sample from the previous generation.
This sampling introduces randomness.
That randomness is genetic drift.
0.5 An Intuitive Example
Imagine a population containing:
- 50 copies of allele A
- 50 copies of allele a
The allele frequency is:
\[ p=0.5 \]
Now imagine forming the next generation by randomly selecting individuals.
By chance you may obtain:
- 55 copies of A
- 45 copies of a
Now:
\[ p=0.55 \]
Nothing biological happened.
No selection occurred.
The change is purely due to chance.
This is genetic drift.
0.5.1 Formal Definition
Genetic drift is the random fluctuation of allele frequencies due to finite population sampling.
Unlike natural selection:
| Genetic Drift | Natural Selection |
|---|---|
| Random | Non-random |
| Stronger in small populations | Can operate in any population |
| Does not require fitness differences | Requires fitness differences |
| Can fix harmful alleles | Favors beneficial alleles |
0.6 What is Fixation?
An allele is fixed when its frequency becomes:
\[ p=1 \]
This means every chromosome in the population carries that allele.
Example:
| Allele | Frequency |
|---|---|
| A | 1.0 |
| a | 0.0 |
At this point:
- Genetic variation disappears
- No alternative allele remains
0.7 What is Allele Loss?
The opposite situation occurs when:
\[ p=0 \]
The allele disappears entirely from the population.
Example:
| Allele | Frequency |
|---|---|
| A | 0 |
| a | 1 |
In a drift-only model:
- fixation is absorbing
- extinction is absorbing
Once reached, these states remain forever.
0.8 The Wright-Fisher Model
The Wright-Fisher model is the classical mathematical model of genetic drift.
Assumptions:
- Constant population size
- Diploid individuals
- Random mating
- No mutation
- No migration
- No selection
- Non-overlapping generations
Suppose:
\[ N \]
is the number of diploid individuals.
There are:
\[ 2N \]
gene copies.
0.9 Sampling the Next Generation
Suppose allele A has frequency:
\[ p_t \]
in generation:
\[ t \]
The number of copies of allele A in the next generation follows:
\[ X_{t+1} \sim Binomial(2N,p_t) \]
The allele frequency becomes:
\[ p_{t+1} = \frac{X_{t+1}}{2N} \]
This simple equation forms the foundation of modern population genetics.
0.10 Important Theorem 1: Drift Has No Direction
For a neutral allele:
\[ E[p_{t+1}\mid p_t] = p_t \]
Interpretation:
On average, allele frequencies do not systematically increase or decrease.
Drift is unbiased.
However, individual populations may behave very differently.
0.11 Important Theorem 2: Variance of Drift
The variance is:
\[ Var(p_{t+1}\mid p_t) = \frac{p_t(1-p_t)}{2N} \]
This equation reveals an important biological fact:
Drift becomes stronger as population size decreases.
0.12 Why Small Populations Drift Faster
Suppose:
\[ p_t=0.5 \]
For
\[ N=20 \]
the variance becomes:
\[ \frac{0.5\times0.5}{40} = 0.00625 \]
For
\[ N=1000 \]
the variance becomes:
\[ \frac{0.5\times0.5}{2000} = 0.000125 \]
The smaller population experiences much larger fluctuations.
0.13 Important Theorem 3: Fixation Probability
One of the most beautiful results in population genetics states:
For a neutral allele:
\[ P(Fixation)=p_0 \]
where
\[ p_0 \]
is the starting frequency.
Examples:
| Initial Frequency | Fixation Probability |
|---|---|
| 0.1 | 10% |
| 0.2 | 20% |
| 0.5 | 50% |
| 0.8 | 80% |
0.14 New Mutations
A new mutation begins as a single copy.
In a diploid population:
\[ p_0 = \frac{1}{2N} \]
Therefore:
\[ P(Fixation) = \frac{1}{2N} \]
Example:
For
\[ N=1000 \]
\[ P(Fixation) = 0.0005 \]
Only 0.05%.
Most neutral mutations disappear.
0.15 Important Theorem 4: Heterozygosity Decay
Drift reduces genetic diversity.
Expected heterozygosity after t generations:
\[ H_t = H_0 \left( 1-\frac{1}{2N} \right)^t \]
This explains why isolated populations lose diversity over time.
0.16 Simulating the Wright-Fisher Model
We now implement the model directly.
def simulate_drift(
N=100,
p0=0.5,
generations=100
):
p = p0
trajectory = [p]
for _ in range(generations):
count_A = np.random.binomial(
2*N,
p
)
p = count_A/(2*N)
trajectory.append(p)
if p == 0 or p == 1:
trajectory.extend(
[p]*(generations-len(trajectory)+1)
)
break
return np.array(trajectory)
# Simulating One Population
trajectory = simulate_drift(
N=100,
p0=0.5,
generations=200
)
# Visualizing Drift
plt.figure(figsize=(12,6))
plt.plot(
trajectory,
linewidth=2
)
plt.axhline(
0,
linestyle="--"
)
plt.axhline(
1,
linestyle="--"
)
plt.xlabel("Generation")
plt.ylabel("Allele Frequency")
plt.title(
"Single Wright-Fisher Trajectory"
)
plt.ylim(
-0.05,
1.05
)
plt.show()
0.17 Interpreting the Plot
Notice:
- Frequency fluctuates randomly
- No directional trend exists
- Changes occur despite no selection
- Eventually fixation or extinction may occur
This randomness is the hallmark of genetic drift.
0.18 Simulating Multiple Populations
The true power of drift appears when we simulate many populations with identical starting conditions.
def simulate_many_populations(
N=100,
p0=0.5,
generations=200,
n_replicates=50
):
trajectories=[]
for _ in range(n_replicates):
traj = simulate_drift(
N,
p0,
generations
)
trajectories.append(traj)
return np.array(trajectories)
trajectories = simulate_many_populations(
N=100,
p0=0.5,
generations=200,
n_replicates=50
)
plt.figure(figsize=(12,7))
for traj in trajectories:
plt.plot(
traj,
alpha=0.3
)
plt.xlabel("Generation")
plt.ylabel("Allele Frequency")
plt.title(
"50 Independent Populations Starting at p=0.5"
)
plt.ylim(
-0.05,
1.05
)
plt.show()
0.19 Interpretation
All populations began with: \(p_0=0.5\).
Yet they diverged dramatically.
Some populations moved upward.
Some moved downward.
Some approached fixation.
Some approached extinction.
This illustrates a central lesson:
Evolutionary outcomes can differ substantially even when populations start under identical conditions.
0.20 Summary
In this first part we learned:
- What genetic drift is
- Why finite populations matter
- What fixation means
- The Wright-Fisher model
- Classical theorems of drift
- Why small populations drift faster
- How to simulate allele-frequency trajectories
In Part 2 we will move beyond a single SNP and systematically investigate:
- Population size effects
- Initial allele frequency effects
- Fixation probability verification
- Multiple SNP simulations
- Sample size effects
- Allele frequency spectra
0.21 Experimental Exploration of Genetic Drift
In Part 1, we simulated a single SNP evolving under neutral genetic drift.
Now we move toward a more realistic population genetics framework by asking:
- What happens when population size changes?
- What happens when initial allele frequency changes?
- Can we experimentally verify the fixation theorem?
- What happens when thousands of SNPs evolve simultaneously?
- How does sample size affect allele frequency estimates?
- How does drift reshape the allele frequency spectrum?
These experiments mirror questions encountered in modern statistical genetics and genomics.
0.22 Experiment 1: Effect of Population Size
Recall the variance theorem:
\[ Var(p_{t+1}|p_t) = \frac{p_t(1-p_t)}{2N} \]
The denominator contains population size:
\[ N \]
Therefore:
- Small populations should drift rapidly.
- Large populations should drift slowly.
0.23 Simulating Different Population Sizes
population_sizes = [
20,
50,
100,
500,
1000
]
plt.figure(figsize=(12,7))
for N in population_sizes:
traj = simulate_drift(
N=N,
p0=0.5,
generations=200
)
plt.plot(
traj,
linewidth=2,
label=f"N={N}"
)
plt.xlabel("Generation")
plt.ylabel("Allele Frequency")
plt.title(
"Effect of Population Size on Genetic Drift"
)
plt.legend()
plt.ylim(
-0.05,
1.05
)
plt.show()

0.24 Interpretation
Notice the dramatic difference.
For:
\[ N=20 \]
the allele frequency may rapidly reach fixation or extinction.
For:
\[ N=1000 \]
the frequency remains close to its initial value.
This directly verifies the variance theorem.
0.25 Quantifying Drift Variability
Instead of looking at one trajectory, we can measure variance across many replicate populations.
population_sizes = [
20,
50,
100,
500,
1000
]
variances=[]
for N in population_sizes:
final_freqs=[]
for _ in range(1000):
traj = simulate_drift(
N=N,
p0=0.5,
generations=100
)
final_freqs.append(
traj[-1]
)
variances.append(
np.var(final_freqs)
)plt.figure(figsize=(10,6))
plt.plot(
population_sizes,
variances,
marker="o",
linewidth=3
)
plt.xlabel("Population Size")
plt.ylabel("Variance of Final Allele Frequency")
plt.title(
"Drift Variance Decreases With Population Size"
)
plt.show()
0.26 Experiment 2: Initial Allele Frequency
The fixation theorem states:
\[ P(Fixation)=p_0 \]
where, \(p_0\) is the initial allele frequency.
We first visualize trajectories starting from different frequencies.
starting_frequencies = [
0.1,
0.3,
0.5,
0.7,
0.9
]
plt.figure(figsize=(12,7))
for p0 in starting_frequencies:
traj = simulate_drift(
N=100,
p0=p0,
generations=200
)
plt.plot(
traj,
label=f"p0={p0}"
)
plt.xlabel("Generation")
plt.ylabel("Allele Frequency")
plt.title(
"Effect of Initial Allele Frequency"
)
plt.legend()
plt.show()
0.27 Interpretation
Alleles beginning near \(p=1\) have a much greater chance of fixation.
Alleles beginning near \(p=0\)
have a much greater chance of loss.
0.28 Experiment 3: Verifying the Fixation Theorem
One of the most elegant results in population genetics states \(P(Fixation)=p_0\) .
We now verify this experimentally.
def estimate_fixation_probability(
p0,
N=100,
generations=1000,
n_replicates=1000
):
fixation_count=0
for _ in range(n_replicates):
traj = simulate_drift(
N=N,
p0=p0,
generations=generations
)
if traj[-1] == 1:
fixation_count += 1
return fixation_count/n_replicates
p0_values = np.arange(
0.05,
1.0,
0.05
)
estimated_probs=[]
for p0 in p0_values:
estimated_probs.append(
estimate_fixation_probability(
p0=p0,
N=100,
generations=1000,
n_replicates=500
)
)
plt.figure(figsize=(8,8))
plt.plot(
p0_values,
p0_values,
linewidth=3,
label="Theory"
)
plt.scatter(
p0_values,
estimated_probs,
s=70,
label="Simulation"
)
plt.xlabel("Initial Frequency")
plt.ylabel("Fixation Probability")
plt.title(
"Experimental Verification of the Fixation Theorem"
)
plt.legend()
plt.show()
0.29 Interpretation
The simulation closely follows the theoretical prediction:
\[ P(Fixation)=p_0 \]
This is one of the most important theorems in neutral evolution.
0.30 Experiment 4: Multiple SNP Simulation
Real genomes contain millions of SNPs.
Instead of tracking a single locus, we simulate many SNPs simultaneously.
# Creating 1,000 Independent SNPs
n_snps = 1000
initial_freqs = np.random.uniform(
0.05,
0.95,
n_snps
)
final_freqs=[]
for p0 in initial_freqs:
traj = simulate_drift(
N=100,
p0=p0,
generations=200
)
final_freqs.append(
traj[-1]
)
# Visualizing Frequency Changes
plt.figure(figsize=(12,6))
plt.hist(
initial_freqs,
bins=30,
alpha=0.6,
label="Initial Frequencies"
)
plt.hist(
final_freqs,
bins=30,
alpha=0.6,
label="Final Frequencies"
)
plt.xlabel("Allele Frequency")
plt.ylabel("Number of SNPs")
plt.title(
"Genetic Drift Reshapes the Frequency Spectrum"
)
plt.legend()
plt.show()

0.31 Interpretation
Initially, frequencies were nearly uniform.
After drift:
- More SNPs accumulate near 0
- More SNPs accumulate near 1
- Intermediate frequencies become less common
This is a hallmark of neutral evolution.
0.32 Experiment 5: SNP Loss and Fixation
How many SNPs become fixed or lost?
lost=0
fixed=0
for p in final_freqs:
if p == 0:
lost += 1
elif p == 1:
fixed += 1
print("Lost SNPs:", lost)
print("Fixed SNPs:", fixed)
# Visualization
categories = [
"Lost",
"Fixed",
"Segregating"
]
counts = [
lost,
fixed,
len(final_freqs)-lost-fixed
]
plt.figure(figsize=(8,6))
plt.bar(
categories,
counts
)
plt.ylabel("Number of SNPs")
plt.title(
"Fate of SNPs After Drift"
)
plt.show()Lost SNPs: 313
Fixed SNPs: 285

0.33 Experiment 6: Number of SNPs Matters
Now repeat the experiment using increasingly larger numbers of SNPs.
snp_counts = [
100,
500,
1000,
5000
]
fixed_counts=[]
lost_counts=[]
for n_snps in snp_counts:
fixed=0
lost=0
freqs=np.random.uniform(
0.05,
0.95,
n_snps
)
for p0 in freqs:
traj=simulate_drift(
N=50,
p0=p0,
generations=300
)
if traj[-1]==1:
fixed += 1
elif traj[-1]==0:
lost += 1
fixed_counts.append(fixed)
lost_counts.append(lost)
plt.figure(figsize=(10,6))
plt.plot(
snp_counts,
fixed_counts,
marker="o",
linewidth=3,
label="Fixed"
)
plt.plot(
snp_counts,
lost_counts,
marker="o",
linewidth=3,
label="Lost"
)
plt.xlabel("Number of SNPs")
plt.ylabel("Count")
plt.title(
"Genome-Wide Consequences of Drift"
)
plt.legend()
plt.show()

0.34 Interpretation
As the number of SNPs increases:
- More variants become fixed
- More variants become lost
- Overall diversity declines
0.35 Experiment 7: Sampling Error Versus Genetic Drift
In GWAS we rarely observe the entire population.
Instead we estimate allele frequencies from samples.
Suppose the true frequency is \(p=0.30\).
0.36 Simulating Sampling Error
true_frequency = 0.30
sample_sizes = [
20,
50,
100,
500,
1000,
5000
]
sampling_std=[]
for n in sample_sizes:
estimates=[]
for _ in range(5000):
count = np.random.binomial(
n,
true_frequency
)
estimates.append(
count/n
)
sampling_std.append(
np.std(estimates)
)
plt.figure(figsize=(10,6))
plt.plot(
sample_sizes,
sampling_std,
marker="o",
linewidth=3
)
plt.xlabel("Sample Size")
plt.ylabel("Standard Deviation")
plt.title(
"Sampling Error Decreases With Sample Size"
)
plt.show()

0.37 Interpretation
Larger studies produce more precise allele frequency estimates.
This randomness is not genetic drift.
Instead it is statistical sampling error.
0.38 Genetic Drift vs Sampling Error
| Genetic Drift | Sampling Error |
|---|---|
| Biological process | Measurement process |
| Occurs across generations | Occurs during data collection |
| Changes true frequency | Changes estimated frequency |
| Evolutionary phenomenon | Statistical phenomenon |
Understanding this distinction is essential in GWAS and population genetics.
0.39 Experiment 8: Distribution of Frequency Estimates
sample_size = 50
estimates=[]
for _ in range(10000):
count=np.random.binomial(
sample_size,
true_frequency
)
estimates.append(
count/sample_size
)
plt.figure(figsize=(10,6))
plt.hist(
estimates,
bins=40
)
plt.axvline(
true_frequency,
linestyle="--"
)
plt.xlabel("Estimated Frequency")
plt.ylabel("Count")
plt.title(
"Sampling Distribution of Allele Frequency Estimates"
)
plt.show()

0.40 Interpretation
Even though the true frequency remains fixed at:
\[ p=0.30 \]
individual samples produce different estimates.
This is exactly why larger cohorts improve statistical power.
0.41 Summary
In this section we experimentally demonstrated:
- Small populations drift faster.
- Large populations are more stable.
- Fixation probability equals starting frequency.
- Drift pushes SNPs toward fixation and extinction.
- Diversity decreases across the genome.
- Sample size affects precision of allele-frequency estimation.
- Sampling error and genetic drift are fundamentally different processes.
In Part 3 we will move to:
- Genome-wide simulations
- Heterozygosity decay
- Effective population size
- Bottlenecks
- Founder effects
- Population structure
- GWAS implications
- Modern statistical genetics applications
0.42 Genome-Wide Consequences of Genetic Drift
So far we have studied drift at the level of individual SNPs.
Real genomes contain millions of variants.
Over evolutionary time, drift acts simultaneously on all of them.
The cumulative effect produces:
- Loss of genetic diversity
- Population differentiation
- Founder effects
- Population bottlenecks
- Changes in allele frequency spectra
Many patterns observed in modern genomics can be understood through drift alone.
0.43 Heterozygosity
One of the most important measures of genetic diversity is heterozygosity.
For a biallelic SNP:
\[ H = 2p(1-p) \]
where:
- (p) = frequency of allele A
- (1-p) = frequency of allele a
0.44 Maximum Heterozygosity
The maximum occurs when:
\[ p=0.5 \]
because:
\[ H=2(0.5)(0.5)=0.5 \]
The minimum occurs when:
\[ p=0 \]
or
\[ p=1 \]
because:
\[ H=0 \]
At fixation, all diversity disappears.
0.45 Theoretical Decay of Heterozygosity
One of the classical results of population genetics is:
\[ H_t = H_0 \left( 1-\frac{1}{2N} \right)^t \]
where:
- (H_0) = initial heterozygosity
- (N) = population size
- (t) = generations
0.46 Simulating Heterozygosity Decay
def heterozygosity_decay(
H0,
N,
generations
):
t = np.arange(
generations+1
)
Ht = H0 * (
1 - 1/(2*N)
)**t
return t, Ht
population_sizes = [
20,
100,
500,
1000
]
plt.figure(figsize=(12,6))
for N in population_sizes:
t,Ht = heterozygosity_decay(
H0=0.5,
N=N,
generations=500
)
plt.plot(
t,
Ht,
linewidth=3,
label=f"N={N}"
)
plt.xlabel("Generation")
plt.ylabel("Expected Heterozygosity")
plt.title(
"Loss of Genetic Diversity Through Drift"
)
plt.legend()
plt.show()
0.47 Interpretation
Small populations lose diversity rapidly.
Large populations retain diversity for much longer periods.
This is one reason conservation biologists worry about endangered populations.
0.48 Effective Population Size
A critical concept in population genetics is:
\[ N_e \]
the effective population size.
0.48.1 Census Size vs Effective Size
Census size:
\[ N \]
represents the number of individuals.
Effective population size:
\[ N_e \]
represents the size of an ideal population that experiences the same amount of drift.
Usually:
\[ N_e < N \]
0.49 Why is Effective Population Size Smaller?
Real populations violate Wright-Fisher assumptions:
- Unequal family sizes
- Population structure
- Sex imbalance
- Bottlenecks
- Selection
All of these increase drift.
0.50 Why Statistical Geneticists Care About Ne
Many quantities depend on:
\[ N_e \]
including:
- LD decay
- Drift strength
- Coalescent times
- Polygenic adaptation
- Demographic inference
0.51 Population Bottlenecks
A bottleneck occurs when population size suddenly decreases.
Examples include:
- Natural disasters
- Famine
- Disease outbreaks
- Founder migrations
0.52 Bottleneck Simulation
def simulate_bottleneck(
p0=0.5,
generations_before=50,
generations_bottleneck=20,
generations_after=100,
N_before=1000,
N_bottleneck=20,
N_after=1000
):
p = p0
trajectory=[p]
schedule = (
[N_before]*generations_before +
[N_bottleneck]*generations_bottleneck +
[N_after]*generations_after
)
for N in schedule:
count=np.random.binomial(
2*N,
p
)
p=count/(2*N)
trajectory.append(p)
return np.array(trajectory)
traj = simulate_bottleneck()
plt.figure(figsize=(12,6))
plt.plot(
traj,
linewidth=3
)
plt.axvspan(
50,
70,
alpha=0.3,
label="Bottleneck"
)
plt.xlabel("Generation")
plt.ylabel("Allele Frequency")
plt.title(
"Population Bottleneck"
)
plt.legend()
plt.show()
0.53 Interpretation
Most frequency changes occur during the bottleneck period.
This happens because:
\[ N \]
temporarily becomes very small.
0.53.1 Founder Effects
A founder effect is a special case of drift.
A small group leaves a large population and establishes a new population.
The new population contains only a subset of the original genetic variation.
0.53.2 Founder Effect Simulation
source_population = np.random.uniform(
0.05,
0.95,
5000
)
founder_population = np.random.choice(
source_population,
size=200,
replace=False
)
plt.figure(figsize=(12,6))
plt.hist(
source_population,
bins=30,
alpha=0.5,
label="Source Population"
)
plt.hist(
founder_population,
bins=30,
alpha=0.5,
label="Founder Population"
)
plt.xlabel("Allele Frequency")
plt.ylabel("Count")
plt.title(
"Founder Effect"
)
plt.legend()
plt.show()

0.54 Interpretation
Even before evolution occurs, the founder population already differs genetically.
This is purely due to sampling.
0.55 Drift Across an Entire Genome
Now simulate:
- 10,000 SNPs
- 500 generations
n_snps = 10000
initial_freqs = np.random.uniform(
0.01,
0.99,
n_snps
)
final_freqs=[]
for p0 in initial_freqs:
traj = simulate_drift(
N=100,
p0=p0,
generations=500
)
final_freqs.append(
traj[-1]
)
# Fate of Variants
fixed = np.sum(
np.array(final_freqs)==1
)
lost = np.sum(
np.array(final_freqs)==0
)
segregating = (
n_snps-fixed-lost
)
print("Fixed:", fixed)
print("Lost:", lost)
print("Segregating:", segregating)
plt.figure(figsize=(8,6))
plt.bar(
["Lost","Fixed","Segregating"],
[lost,fixed,segregating]
)
plt.ylabel("Number of SNPs")
plt.title(
"Genome-Wide Fate of Variants"
)
plt.show()
Fixed: 4523
Lost: 4665
Segregating: 812

0.56 Interpretation
After sufficient evolutionary time:
- Many variants disappear
- Many become fixed
- Only a subset remain polymorphic
0.57 Genetic Drift and Population Structure
Different populations experience different drift histories.
Suppose two populations split.
Each evolves independently.
Their allele frequencies diverge over time.
0.58 Simulating Divergence
shared_freqs = np.random.uniform(
0.05,
0.95,
1000
)
population_A=[]
population_B=[]
for p0 in shared_freqs:
trajA = simulate_drift(
N=100,
p0=p0,
generations=100
)
trajB = simulate_drift(
N=100,
p0=p0,
generations=100
)
population_A.append(
trajA[-1]
)
population_B.append(
trajB[-1]
)
plt.figure(figsize=(8,8))
plt.scatter(
population_A,
population_B,
alpha=0.5
)
plt.xlabel("Population A")
plt.ylabel("Population B")
plt.title(
"Population Divergence Through Drift"
)
plt.show()

0.59 Interpretation
The populations began identically.
Drift alone created differences.
This is the foundation of population structure.
0.60 Connection to FST
Population differentiation is often measured using:
\[ F_{ST} \]
which quantifies how much allele frequencies differ between populations.
Drift increases:
\[ F_{ST} \]
over time.
0.61 Why GWAS Researchers Care About Drift
Modern GWAS datasets contain individuals from populations with different demographic histories.
Drift can create allele-frequency differences unrelated to disease.
0.61.1 Example
Suppose:
Population A:
\[ p=0.7 \]
Population B:
\[ p=0.3 \]
for a SNP.
If disease prevalence also differs between populations, a naive GWAS may falsely associate the SNP with disease.
0.61.2 Confounding
This phenomenon is called:
Population stratification.
Drift is one of its major causes.
0.62 How GWAS Controls Drift Effects
Modern studies use:
- Principal Components Analysis (PCA)
- Linear Mixed Models
- Genetic Relationship Matrices
- Ancestry Adjustment
to account for population structure.
0.63 Connection to FinnGen
Finnish populations experienced:
- Founder events
- Bottlenecks
- Relative isolation
Consequently:
- Some rare variants became common
- Certain disease alleles increased in frequency
This is known as the Finnish Disease Heritage.
0.64 Connection to UK Biobank
UK Biobank contains:
- Multiple ancestry groups
- Geographic structure
- Historical demographic differences
These produce subtle allele-frequency differences caused partly by drift.
0.65 Connection to Polygenic Scores
Polygenic scores depend on allele frequencies.
Since drift changes frequencies:
- Prediction accuracy changes across populations
- Transferability decreases
- Ancestry matching becomes important
Genetic drift is random evolution caused by finite populations.
Drift eventually causes fixation or extinction.
Small populations drift faster than large populations.
Heterozygosity decreases through time.
Effective population size determines drift strength.
Bottlenecks accelerate drift.
Founder effects reshape genetic variation.
Drift creates population structure.
Drift contributes to FST.
Drift influences GWAS, polygenic scores, and demographic inference.
0.66 Summary
One of the most surprising lessons in population genetics is that evolution does not always require selection.
Randomness alone can dramatically reshape genomes.
Given enough time, drift can:
- Eliminate variants
- Fix variants
- Reduce diversity
- Differentiate populations
Many genomic patterns observed today arise not because some variants were better than others, but simply because finite populations are subject to chance.
Understanding genetic drift is therefore essential for evolutionary biology, statistical genetics, genomics, and modern precision medicine.