A simple 4 layer contact network based on survey data
Assume we want to build a simple contact network model consisting of 4 location types: households, school classes, work places and cities. To increase realism, we use survey data to create the population of agents and to define some of the location properties.
[10]:
import random
import pop2net as p2n
from pop2net.data_fakers.soep import soep_faker
In this example, we only use fake survey data (but of course you should real survey data here):
[11]:
df_soep = soep_faker.soep(size=1000)
df_soep.head()
[11]:
age | gender | work_hours_day | nace2_division | hid | pid | |
---|---|---|---|---|---|---|
0 | 41.0 | female | 8.053323 | 99 | 9715 | 4134 |
1 | 91.0 | male | 7.930584 | 65 | 9715 | 405 |
2 | 0.0 | female | 0.000000 | -2 | 9715 | 2336 |
3 | 22.0 | female | 0.000000 | -2 | 6067 | 5682 |
4 | 48.0 | male | 0.000000 | -2 | 6067 | 9459 |
The first contact layer Home
is a location class where agents of one household meet each other for 12 hours. We use the agent attribute hid
(household id), which is provided by the survey data, to group the agents in their empirical households.
[12]:
class Home(p2n.LocationDesigner):
def split(self, agent):
"""Group the agents by their household id."""
return agent.hid
def weight(self, agent):
"""Weight the connection between the agent and the Home by 12."""
return 12
The second layer models work places. The agents are grouped by their NACE2 division which is provided in the survey data. The connection is weighted by the agents’ empirical work hours given by the survey data.
[13]:
class Work(p2n.LocationDesigner):
n_agents = 3
def filter(self, agent):
"""Ignore agents that have 0 work hours or an invalid NACE2 value."""
return True if agent.work_hours_day > 0 and agent.nace2_division > 0 else False
def split(self, agent):
"""Group agents by NACE2 division."""
return agent.nace2_division
def weight(self, agent):
"""Weight the connection between the agent and the Work instance
by the agent's daily work hours."""
return agent.work_hours_day
The third type of location are cities. Using stick_together()
, we make sure that agents of the same household live in the same city.
[14]:
class City(p2n.LocationDesigner):
def stick_together(self, agent):
"""Keep agents of the same household together when assigning the agents to cities."""
return agent.hid
def weight(self, agent):
"""Apply a random weight between 0.1 and 1."""
return random.uniform(0.1, 1)
The fourth contact layer models a school consisting of multiple classrooms including agents of the same age. Using nest()
we ensure that children from the same city visit the same school.
[15]:
class School(p2n.LocationDesigner):
n_agents = 15 # Set the number of agents to 15.
def filter(self, agent):
"""Ignore agents younger than 6 or older than 18."""
return True if 6 <= agent.age <= 18 else False
def split(self, agent):
"""Group the agents by age."""
return agent.age
def weight(self, agent):
"""Weight the connection between the agent and the School by 6."""
return 6
def nest(self):
"""Nest this location type within the location type City."""
return City
Create the necessary pop2net objects:
[16]:
model = p2n.Model()
creator = p2n.Creator(model=model)
inspector = p2n.NetworkInspector(model=model)
In the following we build the network. 100 rows are sampled from the df_soep
and are translated into agents. The argument sample_level
ensures that we always sample complete households. Using the argument location classes
we can define which contact layers we want to use to build our network.
[17]:
creator.create(
df=df_soep,
n_agents=50,
sample_level="hid",
location_designers=[
Home,
City,
Work,
School,
],
)
inspector.plot_networks(location_color="label")
Maybe you have noticed that some of the school classes or work places are undercrowded because the overall population is too small. Let’s create a new network model and increase the population size to 3000 agents:
[18]:
model = p2n.Model()
creator = p2n.Creator(model=model)
inspector = p2n.NetworkInspector(model=model)
creator.create(
df=df_soep,
n_agents=3000,
sample_level="hid",
location_designers=[
Home,
Work,
City,
School,
],
)
[18]:
(AgentList (3003 objects), LocationList (1384 objects))
[ ]:
inspector.plot_networks(location_color="label")
Because the network plot above is very useless, let’s have a look at the table below. It shows us that now the population is large enough to fill all locations as we wanted.
[ ]:
inspector.eval_affiliations()
______________________________________
Number of agents per location
______________________________________
mean std min 25% 50% 75% max
location_class
City 3001.000000 NaN 3001.0 3001.0 3001.0 3001.0 3001.0
Home 3.077949 1.530013 1.0 2.0 3.0 4.0 9.0
School 14.000000 4.636809 4.0 12.0 15.0 15.0 22.0
Work 2.973430 0.313952 1.0 3.0 3.0 3.0 4.0
______________________________________
Number of affiliated locations per agent
______________________________________
mean 2.489503
std 0.506596
min 2.000000
25% 2.000000
50% 2.000000
75% 3.000000
max 4.000000
Name: n_affiliated_locations, dtype: float64