Skip to content

Instantly share code, notes, and snippets.

@bfbraum
Created May 29, 2017 15:44
Show Gist options
  • Save bfbraum/2587ea2b602f0bda32942b6abc515eea to your computer and use it in GitHub Desktop.
Save bfbraum/2587ea2b602f0bda32942b6abc515eea to your computer and use it in GitHub Desktop.
A template for running and plotting a very simple agent-based model in R
# Template for running and plotting a very simple agent-based model in R
# Professor Bear Braumoeller, Department of Political Science, Ohio State
# This code creates a 20x20 grid of 0s and 1s, which represent values of some
# variable held by agents in those cells. It then chooses two adjacent cells,
# the first at random and the second at random from among the first cell's
# neighbors, and applies a simple rule -- the first cell takes on the value
# of the second. It iterates this cell selection and rule application 1,000
# times, displays the result, and tracks the fraction of 1s in the matrix
# over time.
# This is not meant to represent a meaningful social process. It's just meant
# to be a template for students and colleagues to use to create more interesting
# agent-based models.
library(spam)
dimension <- 20 # Set the dimensions of the world you want to create
# Create grid and populate it with random 0s and 1s
test.mat <- matrix(sample(c(0,1), dimension*dimension, replace=TRUE), nrow=dimension, ncol=dimension)
# Start tracking the value of interest to you -- here, the fraction of 1s in the matrix
thing.to.track <- sum(test.mat)/(dimension*dimension)
# Open a plot window with two panes to visualize results
getOption("device")(width=10, height=5)
par(mfrow=c(1,2))
pars <- c('plt','usr')
# Set the model in motion. Choose a cell, apply a rule, and plot the result.
for(iteration in 1:1000){
# Select two cells to interact, using two random numbers between 1 and dimension
first.cell.row <- round(runif(1)*(dimension)+0.5)
first.cell.column <- round(runif(1)*(dimension)+0.5)
second.cell.row <- first.cell.row
second.cell.column <- first.cell.column
while((second.cell.row == first.cell.row) & (second.cell.column == first.cell.column)){
second.cell.row <- first.cell.row + sample(c(-1, 0, 1), 1)
second.cell.column <- first.cell.column + sample(c(-1, 0, 1), 1)
}
# Make the world "wrap around" by adjusting location of second cell, if necessary
second.cell.row[second.cell.row==0] <- dimension
second.cell.row[second.cell.row==dimension+1] <- 1
second.cell.column[second.cell.column==0] <- dimension
second.cell.column[second.cell.column==dimension+1] <- 1
# Make the first cell take on the value of the second.
# A stupid rule, but these agents aren't very bright.
test.mat[first.cell.row,first.cell.column] <- test.mat[second.cell.row,second.cell.column]
# Track how applying this rule changes the statistic of interest
thing.to.track <- c(thing.to.track, sum(test.mat)/(dimension*dimension))
# Now plot the result. The tricky part here is getting R to switch back
# and forth between the panes and update the plots correctly. It'd be
# possible just to draw a series of new plots, but they'd flicker a lot.
if(iteration==1){
image(t(test.mat), col=c("white", "black"), axes=FALSE)
par1 <- c(list(mfg=c(1,1,1,2)), par(pars))
plot(-100, -100, xlim=c(1,1000), ylim=c(0,1), ylab="Fraction of black squares", xlab="Iteration", type="n", cex.axis=0.8)
rect(par("usr")[1], par("usr")[3], par("usr")[2], par("usr")[4], col="#E6E6E6")
abline(h=c(0,0.25,0.5,0.75,1), col="white", lwd=0.5)
abline(v=c(0,200,400,600,800,1000), col="white", lwd=0.5)
par2 <- c(list(mfg=c(1,2,1,2)), par(pars))
} else {
par(par1)
image(t(test.mat), col=c("white", "black"), axes=FALSE)
par(par2)
segments(iteration-1, thing.to.track[iteration-1], iteration, thing.to.track[iteration], col="black", lwd=1)
}
}
@bfbraum
Copy link
Author

bfbraum commented May 29, 2017

Hm. Now that I've taken a first cut at this, I realize that it might generalize more readily if one were to create a series of functions and then execute those functions. The actual code once the functions have been written could then just look something like

setup.matrix(20, my.matrix)
setup.thing.to.track(thing.to.track)
for(iterate in 1:1000){
select.cell(my.matrix, first.cell)
select.neighbor(first.cell)
copy.values(first.cell, second.cell)
add.to.tracked.series(thing.to.track, current.state(my.matrix))
display.result(my.matrix)
}

That'd make it a lot easier to see, and change, what's going on at each step.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment