Nature, in Code

Effective Population Size: Population Size Bottlenecks

The effective population size, Ne, is the size of a population that behaves like an ideal (Wright-Fisher) population of size Ne. Let's demonstrate what that means.

The first graph shows the evolutionary dynamics of a population of size N=1000. The effective population size is calculated as the harmonic mean, and shown in the legend. Because the population size is constant at N=1000, the effective population size is Ne=1000 as well.

RELOAD SIMULATION

Let's go ahead and introduce a population size bottleneck of N=10 at every 10th generation. What is the effective population size of such a population?

Our population is of size N=1000 for 9 generations, then N=10 for one generation (the bottleneck), then N=1000 for 9 generations again, then N=10 for one generation, etc. The average population size over time is 0.9*1000 + 0.1*10, or 901. We could be tempted to think that the population thus behaves like an ideal population at a constant size N=901, which would look as follows:

RELOAD SIMULATION

However, the harmonic mean of the population size is only 92. What would an ideal population at constant size N=92 look like? Let's take a look:

RELOAD SIMULATION

At N=92, the effect of genetic drift has strongly increased compared to N=1000. Let's now take a look at the evolutionary dynamics of the population with N=1000, but with bottlenecks of N=10 every 10th generation:

RELOAD SIMULATION

Thus, despite having an average size of N=901, this population behaves like an ideal population of N=92. In other words, while its census population size is N=901, its effective population size is Ne=92.

Code


var p;
var N = 1000;
var generations = 100;
var data = [];
var simulations = 10;

var population_sizes = [];


function next_generation(simulation_data, current_N) {
	var draws = 2 * current_N;
	var a1 = 0;
	var a2 = 0;
	for (var i = 0; i < draws; i = i + 1) {
		if (Math.random() <= p) {
			a1 = a1 + 1;
		}
		else {
			a2 = a2 + 1;
		}
	}
	p = a1/draws;
	simulation_data.push(p);
}

function simulation(simulation_counter) {
	p = 0.5;
	var population_size;
	for (var i = 0; i < generations; i = i + 1) {
		if (i%10 == 9) {
			population_size = 10;
		}
		else {
			population_size = N;
		}
		population_sizes.push(population_size);
		next_generation(data[simulation_counter],population_size);
	}
}

function effective_population_size(all_Ns) {
	var denominator = 0;
	for (var i = 0; i < all_Ns.length; i = i + 1) {
		denominator = denominator + (1 / all_Ns[i]);
	}
	return Math.round(all_Ns.length / denominator);
}

for (var i = 0; i < simulations; i = i + 1) {
	data.push([]);
	simulation(i);
}

Ne = effective_population_size(population_sizes);
draw_line_chart(data,"Generation","p",["Eff. Population Size:",Ne,"Generations:",generations]);
			
Note: the draw_line_chart function is built with D3.js and can be found here.