Skip to content

Commit

Permalink
Remove exclude_none_values
Browse files Browse the repository at this point in the history
This feature is functional only if the agent records are in the form of
```
{'satisfication': (('A0', 1), ('B0', None), ('A1', 1), ('B1', None), ('A2', 1), ('B2', None)), 'unique_id': (('A0', 'A0'), ('B0', 'B0'), ('A1', 'A1'), ('B1', 'B1'), ('A2', 'A2'), ('B2', 'B2'))}
```

instead of
```
((1, 'A0', 1, 'A0'), (1, 'B0', None, 'B0'), (1, 'A1', 1, 'A1'), (1, 'B1', None, 'B1'), (1, 'A2', 1, 'A2'), (1, 'B2', None, 'B2'))
```

A more explicit solution instead of implicitly ignoring a group of
agents just because of their attribute returns `None`, would be to add a
filter to the data collector itself. i.e., instead of
```python
agent_records = map(get_reports, model.schedule.agents)
```

we should do
```python
agent_records = map(get_reports, filter(agent_filter, model.schedule.agents))
```
Where `agent_filter` can be `lambda a: isinstance(a, Trader)`
. And this, too, is only a few lines of code of change.
  • Loading branch information
rht committed Sep 20, 2023
1 parent 3dbabfe commit ca11c4d
Show file tree
Hide file tree
Showing 3 changed files with 2 additions and 68 deletions.
18 changes: 0 additions & 18 deletions mesa/datacollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def __init__(
model_reporters=None,
agent_reporters=None,
tables=None,
exclude_none_values=False,
):
"""Instantiate a DataCollector with lists of model and agent reporters.
Both model_reporters and agent_reporters accept a dictionary mapping a
Expand All @@ -79,8 +78,6 @@ def __init__(
model_reporters: Dictionary of reporter names and attributes/funcs
agent_reporters: Dictionary of reporter names and attributes/funcs.
tables: Dictionary of table names to lists of column names.
exclude_none_values: Boolean of whether to drop records which values
are None, in the final result.
Notes:
If you want to pickle your model you must not use lambda functions.
Expand All @@ -104,7 +101,6 @@ class attributes of a model
self.model_vars = {}
self._agent_records = {}
self.tables = {}
self.exclude_none_values = exclude_none_values

if model_reporters is not None:
for name, reporter in model_reporters.items():
Expand Down Expand Up @@ -159,20 +155,6 @@ def _new_table(self, table_name, table_columns):
def _record_agents(self, model):
"""Record agents data in a mapping of functions and agents."""
rep_funcs = self.agent_reporters.values()
if self.exclude_none_values:
# Drop records which values are None.

def get_reports(agent):
_prefix = (agent.model.schedule.steps, agent.unique_id)
reports = (rep(agent) for rep in rep_funcs)
reports_without_none = tuple(r for r in reports if r is not None)
if len(reports_without_none) == 0:
return None
return _prefix + reports_without_none

agent_records = (get_reports(agent) for agent in model.schedule.agents)
agent_records_without_none = (r for r in agent_records if r is not None)
return agent_records_without_none

def get_reports(agent):
_prefix = (agent.model.schedule.steps, agent.unique_id)
Expand Down
2 changes: 0 additions & 2 deletions mesa/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ def initialize_data_collector(
model_reporters=None,
agent_reporters=None,
tables=None,
exclude_none_values=False,
) -> None:
if not hasattr(self, "schedule") or self.schedule is None:
raise RuntimeError(
Expand All @@ -84,7 +83,6 @@ def initialize_data_collector(
model_reporters=model_reporters,
agent_reporters=agent_reporters,
tables=tables,
exclude_none_values=exclude_none_values,
)
# Collect data for the first time during initialization.
self.datacollector.collect(self)
50 changes: 2 additions & 48 deletions tests/test_datacollector.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,14 @@ class MockModel(Model):

schedule = BaseScheduler(None)

def __init__(self, test_exclude_none_values=False):
def __init__(self):
self.schedule = BaseScheduler(self)
self.model_val = 100

self.n = 10
for i in range(self.n):
self.schedule.add(MockAgent(i, self, val=i))
if test_exclude_none_values:
self.schedule.add(DifferentMockAgent(self.n + i, self, val=i))
if test_exclude_none_values:
# Only DifferentMockAgent has val3.
agent_reporters = {"value": lambda a: a.val, "value3": "val3"}
else:
agent_reporters = {"value": lambda a: a.val, "value2": "val2"}
agent_reporters = {"value": lambda a: a.val, "value2": "val2"}
self.initialize_data_collector(
{
"total_agents": lambda m: m.schedule.get_agent_count(),
Expand All @@ -71,7 +65,6 @@ def __init__(self, test_exclude_none_values=False):
},
agent_reporters,
{"Final_Values": ["agent_id", "final_value"]},
exclude_none_values=test_exclude_none_values,
)

def test_model_calc_comp(self, input1, input2):
Expand Down Expand Up @@ -211,44 +204,5 @@ def test_initialize_before_agents_added_to_scheduler(self):
)


class TestDataCollectorExcludeNone(unittest.TestCase):
def setUp(self):
"""
Create the model and run it a set number of steps.
"""
self.model = MockModel(test_exclude_none_values=True)
for i in range(7):
if i == 4:
self.model.schedule.remove(self.model.schedule._agents[3])
self.model.step()

def test_agent_records(self):
"""
Test agent-level variable collection.
"""
data_collector = self.model.datacollector
agent_table = data_collector.get_agent_vars_dataframe()

assert len(data_collector._agent_records) == 8
for step, records in data_collector._agent_records.items():
if step < 5:
assert len(records) == 20
else:
assert len(records) == 19

for values in records:
agent_id = values[1]
if agent_id < self.model.n:
assert len(values) == 3
else:
# Agents with agent_id >= self.model.n are
# DifferentMockAgent, which additionally contains val3.
assert len(values) == 4

assert "value" in list(agent_table.columns)
assert "value2" not in list(agent_table.columns)
assert "value3" in list(agent_table.columns)


if __name__ == "__main__":
unittest.main()

0 comments on commit ca11c4d

Please sign in to comment.