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.
[1]:
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):
[2]:
df_soep = soep_faker.soep(size=1000)
df_soep.head()
[2]:
age | gender | work_hours_day | nace2_division | hid | pid | |
---|---|---|---|---|---|---|
0 | 65.0 | male | 7.224629 | 75 | 7270 | 4993 |
1 | 44.0 | female | 0.000000 | -2 | 7270 | 3968 |
2 | 78.0 | female | 3.583111 | 84 | 2836 | 174 |
3 | 3.0 | female | 0.000000 | -2 | 2836 | 9598 |
4 | 8.0 | male | 0.000000 | -2 | 2836 | 1382 |
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.
[3]:
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.
[4]:
class Work(p2n.LocationDesigner):
n_agents = 10
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. We build 2 of them. Using stick_together()
, we make sure that agents of the same household live in the same city.
[5]:
class City(p2n.LocationDesigner):
n_locations = 2
def stick_together(self, agent):
"""Keep agents of the same household together when assigning the agents to cities."""
return agent.hid
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.
[6]:
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:
[7]:
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.
[8]:
creator.create(
df=df_soep,
n_agents=100,
sample_level="hid",
location_designers=[
Home,
City,
Work,
School,
],
)
inspector.plot_networks(location_color="label")
[9]:
inspector.eval_affiliations()
______________________________________
Number of locations
______________________________________
location_label
Home 31
Work 25
School 10
City 2
Name: count, dtype: int64
______________________________________
Number of agents per location
______________________________________
mean std min 25% 50% 75% max
location_label
City 51.500000 4.949747 48.0 49.75 51.5 53.25 55.0
Home 3.322581 1.868816 1.0 2.00 3.0 4.00 10.0
School 1.300000 0.483046 1.0 1.00 1.0 1.75 2.0
Work 1.680000 1.676305 1.0 1.00 1.0 2.00 9.0
______________________________________
Number of affiliated locations per agent
______________________________________
mean 2.533981
std 0.520474
min 2.000000
25% 2.000000
50% 3.000000
75% 3.000000
max 4.000000
Name: n_affiliated_locations, dtype: float64
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 5000 agents:
[10]:
model = p2n.Model()
creator = p2n.Creator(model=model)
inspector = p2n.NetworkInspector(model=model)
creator.create(
df=df_soep,
n_agents=5000,
sample_level="hid",
location_designers=[
Home,
City,
Work,
School,
],
)
[10]:
(AgentList (5002 objects), LocationList (1827 objects))
The table below shows that now the population is large enough to fill all locations as we wanted.
[11]:
inspector.eval_affiliations()
______________________________________
Number of locations
______________________________________
location_label
Home 1576
Work 220
School 29
City 2
Name: count, dtype: int64
______________________________________
Number of agents per location
______________________________________
mean std min 25% 50% 75% max
location_label
City 2501.000000 0.000000 2501.0 2501.0 2501.0 2501.0 2501.0
Home 3.173858 1.477957 1.0 2.0 3.0 4.0 10.0
School 13.275862 5.364543 4.0 10.0 13.0 16.0 22.0
Work 9.363636 1.947846 2.0 10.0 10.0 10.0 14.0
______________________________________
Number of affiliated locations per agent
______________________________________
mean 2.488804
std 0.507073
min 2.000000
25% 2.000000
50% 2.000000
75% 3.000000
max 4.000000
Name: n_affiliated_locations, dtype: float64