villager is a framework for creating and running agent based models in R. Its purpose is to provide an extensible framework where modeling can be done in native R.
- Extensible data output system (csv, excel sheets, sqlite)
- Built in support for agents and resources
- Easy to use time management
villager can be installed from CRAN by running the following,
install.packages("villager")
When reading though the Readme and vignettes, it's important to take note of a few concepts
- Villages are the highest aggregate; they contain villages which in turn contain agents (agents)
- Agents and resources can be subclassed to support additional properties
- The data_writer class can be subclassed when writing to data sources other than csv
- Models are functions that are added to villages; each village can exhibit different behavior
villager
is about modeling populations with (optional) associated resources. It supports a community level aggregation of agents, referred to as villages or an individual village. Agents, which are referred to as gender-neutral agents, are members of community level aggregations.
villager compliant models must conform to the function template below. The agent_mgr
and resource_mgr
are responsible for interacting with the individual agents and resources.
test_model <- function(current_state, previous_state, model_data, agent_mgr, resource_mgr) {
...
...
}
Agents are created by instantiating the agent
class. There are a number of agent properties that can be passed to the constructor.
test_model <- function(current_state, previous_state, model_data, agent_mgr, resource_mgr, village_mgr) {
mother <- agent$new(first_name="Kirsten", last_name="Taylor", age=9125)
father <- agent$new(first_name="Joshua", last_name="Thompson", age=7300)
daughter <- agent$new(first_name="Mariylyyn", last_name="Thompson", age=10220)
}
To add agents to the simulation, use the provided agent_mgr
object to call add_agent
. Because the classes are R6, the object can be modified after being added to the manager and the changes will be persisted without needing to re-add the villager. For example, setting a daughter's mother and her father below. Note that the standard way is to modify the properties beforehand, although not strictly necessary.
test_model <- function(current_state, previous_state, model_data, agent_mgr, resource_mgr, village_mgr) {
agent_mgr <- agent_manager$new()
agent_mgr$add_agent(mother, father, daughter)
daughter$mother_id <- mother$identifier
daughter$father_id <- father$identifier
}
The agent manager can also be used to pair agents, representative of a relationship or social bond.
agent_mgr$agent_mgr$connect_agents(mother, father)
Resources are similar to agents in that they're both R6 classes, are instantiated similarly, and are also managed by an object passed into the model. An example of creating resources and adding them to the simulation is given below.
test_model <- function(current_state, previous_state, model_data, agent_mgr, resource_mgr, village_mgr) {
corn_resource <- resource$new(name="corn", quantity = 10)
fish_resource <- resource$new(name="fish", quantity = 15)
corn_resource$quantity=5
resource_mgr <- resource_manager$new()
resource_mgr$add_resource(corn_resource, fish_resource)
fish_resource$quantity=5
}
Objects of type village
, agent
, and resource
have particular states at a particular time. As the simulation progresses, the state of these change based on model logic. At the end of each time step, the state of each object is saved, giving a complete record of the system's evolution. The essence of any agent based model is changing the state at each time step. villager provides a mechanism for defining the initial state and for changing the state throughout the simulation.
Creating the initial state is done by creating a function that resembles model functions from above. The manager classes are used to populate the village with an initial population of agents and resources.
initial_condition <- function(current_state, model_data, agent_mgr, resource_mgr) {
# Create the initial villagers
mother <- agent$new(first_name="Kirsten", last_name="Taylor", age=9125)
father <- agent$new(first_name="Joshua", last_name="Thompson", age=7300)
daughter <- agent$new(first_name="Mariylyyn", last_name="Thompson", age=10220)
daughter$mother_id <- mother$identifier
daughter$father_id <- father$identifier
# Add the agents to the manager
agent_mgr$connect_agents(mother, father)
agent_mgr$add_agent(mother, father, daughter)
# Create the resources
corn_resource <- resource$new(name="corn", quantity = 10)
fish_resource <- resource$new(name="fish", quantity = 15)
# Add the resources to the manager
resource_mgr$add_resource(corn_resource, fish_resource)
}
Models are tied to particular village instances. This binding is done when villages are created, shown below. Models can have names and must always be paired with an initial condition function and a model function.
small_village <- village$new("Test Model 1", initial_condition, test_model)
The simulator
class is responsible for running simulations. It encapsulates all of the villages and controls the duration of the simulation. The simulator below runs for 100 time steps: roughly 13 years. The simulator can be paired with any number of villages, in the case of the simulator below, there's only a single village.
simulator <- simulation$new(100, list(small_village))
simulator$run_model()
We can combine the examples above into a full simulation that...
- Starts with an initial population of three villagers
- Increases the age of each villager at the start of each day
- Runs for 4745 days
- Sets the villager profession after age 12
library(villager)
initial_condition <- function(current_state, model_data, agent_mgr, resource_mgr) {
# Create the initial villagers
mother <- agent$new(first_name="Kirsten", last_name="Taylor", age=9125)
father <- agent$new(first_name="Joshua", last_name="Thompson", age=7300)
daughter <- agent$new(first_name="Mariylyyn", last_name="Thompson", age=10220)
daughter$mother_id <- mother$identifier
daughter$father_id <- father$identifier
# Add the agents to the manager
agent_mgr$connect_agents(mother, father)
agent_mgr$add_agent(mother, father, daughter)
# Create the resources
corn_resource <- resource$new(name="corn", quantity = 10)
fish_resource <- resource$new(name="fish", quantity = 15)
# Add the resources to the manager
resource_mgr$add_resource(corn_resource, fish_resource)
}
test_model <- function(current_state, previous_state, model_data, agent_mgr, resource_mgr, village_mgr) {
print(paste("Step:", current_state$step))
for (agent in agent_mgr$get_living_agents()) {
agent$age <- agent$age 1
if (agent$age >= 4383) {
agent$profession <- "Farmer"
}
}
}
small_village <- village$new("Test Model", initial_condition, test_model)
simulator <- simulation$new(4745, list(small_village))
simulator$run_model()
To demonstrate programatically creating villagers, consider the model below that has the following logic.
- Starts with 10 villagers
- Every even day, two new villagers are created
- Every odd day, one villager dies
initial_condition <- function(current_state, model_data, agent_mgr, resource_mgr) {
for (i in 1:10) {
name <- runif(1, 0.0, 100)
new_agent <- agent$new(first_name <- name, last_name <- "Smith")
agent_mgr$add_agent(new_agent)
}
}
model <- function(current_state, previous_state, model_data, agent_mgr, resource_mgr, village_mgr) {
current_day <- current_state$step
if((current_day%%2) == 0) {
# Then it's an even day
# Create two new agents whose first names are random numbers
for (i in 1:2) {
name <- runif(1, 0.0, 100)
new_agent <- agent$new(first_name <- name, last_name <- "Smith")
agent_mgr$add_agent(new_agent)
}
} else {
# It's an odd day
living_agents <- agent_mgr$get_living_agents()
# Kill the first one
living_agents[[1]]$alive <- FALSE
}
}
coastal_village <- village$new("Test village", initial_condition, model)
simulator <- simulation$new(4, villages = list(coastal_village))
simulator$run_model()
In the examples above, the default properties of agents and resources were used. It's possible that these won't cover all the needs for more diverse models. There are vignettes on extending the agent and resource classes to handle these situations.
Code contributions are welcome as pull requests to the develop
branch. Bugs, comments, and questions can be submitted as Github Issues.