Nature, in Code

Schelling's Segregation Model

While Nature, In Code is mostly about processes in the biological domain, I'm equally fascinated by social processes. Schelling's model of segregation is one of the best known simple models in all of social science.

It is particularly interesting because it explains an important social problem - that of segregation - with a simple model of individual decision making. Most strikingly, one outcome of the model is that there will be strong segregation on the population level even if individuals have no strong desire for segregation.

In the simplest case, the model is a grid that is occupied by two types, which I call "A" (in yellow) and "B" (in blue). A certain fraction of the cells - 5% in the examples below - is empty. You can think of "A" and "B" as two objectively distinct traits - e.g. socioeconomic status, gender, race, etc.

The rules of the model are very simple. An individual looks around its neighborhood (i.e. the cells it is surrounded by) and calculates the fraction of cells that are of the same type as itself. Then, if the fraction is lower than a certain threshold p, the individual will move to a random empty cell.

Depending on the "minimum similarity" threshold p, we get very different outcomes. Intriguingly, even when individuals are happy to live in a neighborhood where they are in the minority (e.g. p = 0.3), the model will still converge to segregation at the population level:

RELOAD SIMULATION

But perhaps even more strikingly, even when individuals have just a slight preference to be surrounded by individuals similar to them (e.g. p = 0.51), the population will become very strongly segregated:

RELOAD SIMULATION

Code


var grid_length = 75;
var grid = [];
var temp_grid = [];
var empty = 0.05;
var min_pref_similar = 0.3
var moves_per_update = 0;

function get_random_int(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function init_grid() {
	for (var i = 0; i < grid_length; i = i + 1) {
		grid[i] = [];
		for (var ii = 0; ii < grid_length; ii = ii + 1) {
			if (Math.random() < empty) {
				grid[i][ii] = "E";
			}
			else {
				if (Math.random() < 0.5) {
					grid[i][ii] = "A";	
				}
				else {
					grid[i][ii] = "B";
				}
			}
		}
	}
}

init_grid();

draw_grid(grid,["E","#ffffff","A","#F4D23E","B","#1167BD"]);

function simulate_and_visualize() {
	var updates = grid_length * grid_length;
	moves_per_update = 0;
	for (var i = 0; i < updates; i++) {
		run_time_step();
	}
	update_grid(grid,["E","#ffffff","A","#F4D23E","B","#1167BD"]);
	if (moves_per_update == 0) {
		clearInterval(interval_id)		
	}
}

var interval_id = setInterval(simulate_and_visualize, 100);

function run_time_step() {
	var random_i;
	var random_ii;
	do {
		random_i = get_random_int(0,grid_length-1);
		random_ii = get_random_int(0,grid_length-1)
	} while (grid[random_i][random_ii] === "E");
	var current_type = grid[random_i][random_ii];
	var move = make_decision(random_i, random_ii);
	if (move) {
		// set current cell to empty
		grid[random_i][random_ii] = "E";
		// get random empty cell
		do {
			random_i = get_random_int(0,grid_length-1);
			random_ii = get_random_int(0,grid_length-1)
		} while (grid[random_i][random_ii] !== "E");
		// and populate with current type
		grid[random_i][random_ii] = current_type;
		moves_per_update++;
	}
}

function make_decision(i, ii) {
	var current_type = grid[i][ii];
	var type_a = 0;
	var type_b = 0;
	for (var n_i = i-1; n_i <= i+1; n_i = n_i + 1) {
		for (var n_ii = ii-1; n_ii <= ii+1; n_ii = n_ii + 1) {
			if (n_i == i && n_ii == ii) {
				continue;
			}
			if (grid[get_bounded_index(n_i)][get_bounded_index(n_ii)] === "A") {
				type_a++;
			}
			if (grid[get_bounded_index(n_i)][get_bounded_index(n_ii)] === "B") {
				type_b++;
			}
		}
	}
	var fraction_a = type_a / (type_a + type_b);
	var fraction_b = type_b / (type_a + type_b);
	if ((current_type === "A" && fraction_a < min_pref_similar) || (current_type === "B" && fraction_b < min_pref_similar)) {
		return true;
	}
	else {
		return false;
	}
}

function get_bounded_index(index) {
	var bounded_index = index;
	if (index < 0) {
		bounded_index = index + grid_length;
	}
	if (index >= grid_length) {
		bounded_index = index - grid_length;
	}
	return bounded_index;
}			
Note: the draw_grid and update_grid functions are built with D3.js and can be found here.