forked from projectmesa/mesa
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Bar and Pie Chart visualization and include charts example (fix I…
…ssue projectmesa#490) (projectmesa#594) * Added first version of pie chart and put it into the forest_fire example * Added Bar charts and created a new "charts" example Added bar charts that can either visualize model-level fields or agent-level fields. The syntax for adding these to models are identical to exisiting syntax to add line charts to a model. Added a new "charts" example based off the existing bank_reserves model to better provide examples of each available chart in mesa. (model-level bar, agent-level bar, pie, and line.) * Improved handling of negative values in bar chart; cleanup Negative values now appear as bars below the X axis. Cleaned up intially very messy D3.js code. (Now it's just the normal amount of wild for D3) * Bundled the legend and chart itself together in a div. * Fix testing errors * fixed charts example For some reason, there was a mismatch in variable names between the server.py file and the model.py file. This commmit fixes that mismatch.
- Loading branch information
Showing
13 changed files
with
900 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Mesa Charts Example | ||
|
||
## Summary | ||
|
||
A modified version of the "bank_reserves" example made to provide examples of mesa's charting tools. | ||
|
||
The chart types included in this example are: | ||
- Line Charts for time-series data of multiple model parameters | ||
- Pie Charts for model parameters | ||
- Bar charts for both model and agent-level parameters | ||
|
||
## Installation | ||
|
||
To install the dependencies use pip and the requirements.txt in this directory. e.g. | ||
|
||
``` | ||
$ pip install -r requirements.txt | ||
``` | ||
|
||
## Interactive Model Run | ||
|
||
To run the model interactively, use `mesa runserver` in this directory: | ||
|
||
``` | ||
$ mesa runserver | ||
``` | ||
|
||
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/), select the model parameters, press Reset, then Start. | ||
|
||
## Files | ||
|
||
* ``bank_reserves/random_walker.py``: This defines a class that inherits from the Mesa Agent class. The main purpose is to provide a method for agents to move randomly one cell at a time. | ||
* ``bank_reserves/agents.py``: Defines the People and Bank classes. | ||
* ``bank_reserves/model.py``: Defines the Bank Reserves model and the DataCollector functions. | ||
* ``bank_reserves/server.py``: Sets up the interactive visualization server. | ||
* ``run.py``: Launches a model visualization server. | ||
|
||
## Further Reading | ||
|
||
See the "bank_reserves" model for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
""" | ||
The following code was adapted from the Bank Reserves model included in Netlogo | ||
Model information can be found at: http://ccl.northwestern.edu/netlogo/models/BankReserves | ||
Accessed on: November 2, 2017 | ||
Author of NetLogo code: | ||
Wilensky, U. (1998). NetLogo Bank Reserves model. | ||
http://ccl.northwestern.edu/netlogo/models/BankReserves. | ||
Center for Connected Learning and Computer-Based Modeling, | ||
Northwestern University, Evanston, IL. | ||
""" | ||
|
||
from mesa import Agent | ||
from charts.random_walk import RandomWalker | ||
|
||
|
||
class Bank(Agent): | ||
def __init__(self, unique_id, model, reserve_percent=50): | ||
# initialize the parent class with required parameters | ||
super().__init__(unique_id, model) | ||
# for tracking total value of loans outstanding | ||
self.bank_loans = 0 | ||
"""percent of deposits the bank must keep in reserves - this is a | ||
UserSettableParameter in server.py""" | ||
self.reserve_percent = reserve_percent | ||
# for tracking total value of deposits | ||
self.deposits = 0 | ||
# total amount of deposits in reserve | ||
self.reserves = ((self.reserve_percent / 100) * self.deposits) | ||
# amount the bank is currently able to loan | ||
self.bank_to_loan = 0 | ||
|
||
"""update the bank's reserves and amount it can loan; | ||
this is called every time a person balances their books | ||
see below for Person.balance_books()""" | ||
def bank_balance(self): | ||
self.reserves = ((self.reserve_percent / 100) * self.deposits) | ||
self.bank_to_loan = (self.deposits - (self.reserves + self.bank_loans)) | ||
|
||
|
||
# subclass of RandomWalker, which is subclass to Mesa Agent | ||
class Person(RandomWalker): | ||
def __init__(self, unique_id, pos, model, moore, bank, rich_threshold): | ||
# init parent class with required parameters | ||
super().__init__(unique_id, pos, model, moore=moore) | ||
# the amount each person has in savings | ||
self.savings = 0 | ||
# total loan amount person has outstanding | ||
self.loans = 0 | ||
"""start everyone off with a random amount in their wallet from 1 to a | ||
user settable rich threshold amount""" | ||
self.wallet = self.random.randint(1, rich_threshold + 1) | ||
# savings minus loans, see balance_books() below | ||
self.wealth = 0 | ||
# person to trade with, see do_business() below | ||
self.customer = 0 | ||
# person's bank, set at __init__, all people have the same bank in this model | ||
self.bank = bank | ||
|
||
def do_business(self): | ||
"""check if person has any savings, any money in wallet, or if the | ||
bank can loan them any money""" | ||
if self.savings > 0 or self.wallet > 0 or self.bank.bank_to_loan > 0: | ||
# create list of people at my location (includes self) | ||
my_cell = self.model.grid.get_cell_list_contents([self.pos]) | ||
# check if other people are at my location | ||
if len(my_cell) > 1: | ||
# set customer to self for while loop condition | ||
customer = self | ||
while customer == self: | ||
"""select a random person from the people at my location | ||
to trade with""" | ||
customer = self.random.choice(my_cell) | ||
# 50% chance of trading with customer | ||
if self.random.randint(0, 1) == 0: | ||
# 50% chance of trading $5 | ||
if self.random.randint(0, 1) == 0: | ||
# give customer $5 from my wallet (may result in negative wallet) | ||
customer.wallet += 5 | ||
self.wallet -= 5 | ||
# 50% chance of trading $2 | ||
else: | ||
# give customer $2 from my wallet (may result in negative wallet) | ||
customer.wallet += 2 | ||
self.wallet -= 2 | ||
|
||
def balance_books(self): | ||
# check if wallet is negative from trading with customer | ||
if self.wallet < 0: | ||
# if negative money in wallet, check if my savings can cover the balance | ||
if self.savings >= (self.wallet * -1): | ||
"""if my savings can cover the balance, withdraw enough | ||
money from my savings so that my wallet has a 0 balance""" | ||
self.withdraw_from_savings(self.wallet * -1) | ||
# if my savings cannot cover the negative balance of my wallet | ||
else: | ||
# check if i have any savings | ||
if self.savings > 0: | ||
"""if i have savings, withdraw all of it to reduce my | ||
negative balance in my wallet""" | ||
self.withdraw_from_savings(self.savings) | ||
# record how much money the bank can loan out right now | ||
temp_loan = self.bank.bank_to_loan | ||
"""check if the bank can loan enough money to cover the | ||
remaining negative balance in my wallet""" | ||
if temp_loan >= (self.wallet * -1): | ||
"""if the bank can loan me enough money to cover | ||
the remaining negative balance in my wallet, take out a | ||
loan for the remaining negative balance""" | ||
self.take_out_loan(self.wallet * -1) | ||
else: | ||
"""if the bank cannot loan enough money to cover the negative | ||
balance of my wallet, then take out a loan for the | ||
total amount the bank can loan right now""" | ||
self.take_out_loan(temp_loan) | ||
else: | ||
"""if i have money in my wallet from trading with customer, deposit | ||
it to my savings in the bank""" | ||
self.deposit_to_savings(self.wallet) | ||
# check if i have any outstanding loans, and if i have savings | ||
if self.loans > 0 and self.savings > 0: | ||
# check if my savings can cover my outstanding loans | ||
if self.savings >= self.loans: | ||
# payoff my loans with my savings | ||
self.withdraw_from_savings(self.loans) | ||
self.repay_a_loan(self.loans) | ||
# if my savings won't cover my loans | ||
else: | ||
# pay off part of my loans with my savings | ||
self.withdraw_from_savings(self.savings) | ||
self.repay_a_loan(self.wallet) | ||
# calculate my wealth | ||
self.wealth = (self.savings - self.loans) | ||
|
||
# part of balance_books() | ||
def deposit_to_savings(self, amount): | ||
# take money from my wallet and put it in savings | ||
self.wallet -= amount | ||
self.savings += amount | ||
# increase bank deposits | ||
self.bank.deposits += amount | ||
|
||
# part of balance_books() | ||
def withdraw_from_savings(self, amount): | ||
# put money in my wallet from savings | ||
self.wallet += amount | ||
self.savings -= amount | ||
# decrease bank deposits | ||
self.bank.deposits -= amount | ||
|
||
# part of balance_books() | ||
def repay_a_loan(self, amount): | ||
# take money from my wallet to pay off all or part of a loan | ||
self.loans -= amount | ||
self.wallet -= amount | ||
# increase the amount the bank can loan right now | ||
self.bank.bank_to_loan += amount | ||
# decrease the bank's outstanding loans | ||
self.bank.bank_loans -= amount | ||
|
||
# part of balance_books() | ||
def take_out_loan(self, amount): | ||
"""borrow from the bank to put money in my wallet, and increase my | ||
outstanding loans""" | ||
self.loans += amount | ||
self.wallet += amount | ||
# decresae the amount the bank can loan right now | ||
self.bank.bank_to_loan -= amount | ||
# increase the bank's outstanding loans | ||
self.bank.bank_loans += amount | ||
|
||
# step is called for each agent in model.BankReservesModel.schedule.step() | ||
def step(self): | ||
# move to a cell in my Moore neighborhood | ||
self.random_move() | ||
# trade | ||
self.do_business() | ||
# deposit money or take out a loan | ||
self.balance_books() | ||
# updat the bank's reserves and the amount it can loan right now | ||
self.bank.bank_balance() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
""" | ||
The following code was adapted from the Bank Reserves model included in Netlogo | ||
Model information can be found at: http://ccl.northwestern.edu/netlogo/models/BankReserves | ||
Accessed on: November 2, 2017 | ||
Author of NetLogo code: | ||
Wilensky, U. (1998). NetLogo Bank Reserves model. | ||
http://ccl.northwestern.edu/netlogo/models/BankReserves. | ||
Center for Connected Learning and Computer-Based Modeling, | ||
Northwestern University, Evanston, IL. | ||
""" | ||
|
||
from charts.agents import Bank, Person | ||
from mesa import Model | ||
from mesa.space import MultiGrid | ||
from mesa.datacollection import DataCollector | ||
from mesa.time import RandomActivation | ||
import numpy as np | ||
|
||
|
||
""" | ||
If you want to perform a parameter sweep, call batch_run.py instead of run.py. | ||
For details see batch_run.py in the same directory as run.py. | ||
""" | ||
|
||
# Start of datacollector functions | ||
|
||
|
||
def get_num_rich_agents(model): | ||
""" return number of rich agents""" | ||
|
||
rich_agents = [a for a in model.schedule.agents if a.savings > model.rich_threshold] | ||
return len(rich_agents) | ||
|
||
|
||
def get_num_poor_agents(model): | ||
"""return number of poor agents""" | ||
|
||
poor_agents = [a for a in model.schedule.agents if a.loans > 10] | ||
return len(poor_agents) | ||
|
||
|
||
def get_num_mid_agents(model): | ||
"""return number of middle class agents""" | ||
|
||
mid_agents = [a for a in model.schedule.agents if | ||
a.loans < 10 and a.savings < model.rich_threshold] | ||
return len(mid_agents) | ||
|
||
|
||
def get_total_savings(model): | ||
"""sum of all agents' savings""" | ||
|
||
agent_savings = [a.savings for a in model.schedule.agents] | ||
# return the sum of agents' savings | ||
return np.sum(agent_savings) | ||
|
||
|
||
def get_total_wallets(model): | ||
"""sum of amounts of all agents' wallets""" | ||
|
||
agent_wallets = [a.wallet for a in model.schedule.agents] | ||
# return the sum of all agents' wallets | ||
return np.sum(agent_wallets) | ||
|
||
|
||
def get_total_money(model): | ||
# sum of all agents' wallets | ||
wallet_money = get_total_wallets(model) | ||
# sum of all agents' savings | ||
savings_money = get_total_savings(model) | ||
# return sum of agents' wallets and savings for total money | ||
return wallet_money + savings_money | ||
|
||
|
||
def get_total_loans(model): | ||
# list of amounts of all agents' loans | ||
agent_loans = [a.loans for a in model.schedule.agents] | ||
# return sum of all agents' loans | ||
return np.sum(agent_loans) | ||
|
||
|
||
class Charts(Model): | ||
|
||
# grid height | ||
grid_h = 20 | ||
# grid width | ||
grid_w = 20 | ||
|
||
"""init parameters "init_people", "rich_threshold", and "reserve_percent" | ||
are all UserSettableParameters""" | ||
def __init__(self, height=grid_h, width=grid_w, init_people=2, rich_threshold=10, | ||
reserve_percent=50,): | ||
self.height = height | ||
self.width = width | ||
self.init_people = init_people | ||
self.schedule = RandomActivation(self) | ||
self.grid = MultiGrid(self.width, self.height, torus=True) | ||
# rich_threshold is the amount of savings a person needs to be considered "rich" | ||
self.rich_threshold = rich_threshold | ||
self.reserve_percent = reserve_percent | ||
# see datacollector functions above | ||
self.datacollector = DataCollector(model_reporters={ | ||
"Rich": get_num_rich_agents, | ||
"Poor": get_num_poor_agents, | ||
"Middle Class": get_num_mid_agents, | ||
"Savings": get_total_savings, | ||
"Wallets": get_total_wallets, | ||
"Money": get_total_money, | ||
"Loans": get_total_loans}, | ||
agent_reporters={ | ||
"Wealth": lambda x: x.wealth}) | ||
|
||
# create a single bank for the model | ||
self.bank = Bank(1, self, self.reserve_percent) | ||
|
||
# create people for the model according to number of people set by user | ||
for i in range(self.init_people): | ||
# set x, y coords randomly within the grid | ||
x = self.random.randrange(self.width) | ||
y = self.random.randrange(self.height) | ||
p = Person(i, (x, y), self, True, self.bank, self.rich_threshold) | ||
# place the Person object on the grid at coordinates (x, y) | ||
self.grid.place_agent(p, (x, y)) | ||
# add the Person object to the model schedule | ||
self.schedule.add(p) | ||
|
||
self.running = True | ||
self.datacollector.collect(self) | ||
|
||
def step(self): | ||
# tell all the agents in the model to run their step function | ||
self.schedule.step() | ||
# collect data | ||
self.datacollector.collect(self) | ||
|
||
def run_model(self): | ||
for i in range(self.run_time): | ||
self.step() |
Oops, something went wrong.