epiworldpy 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ Copyright (c) 2016 The Pybind Development Team, All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ 3. Neither the name of the copyright holder nor the names of its contributors
14
+ may be used to endorse or promote products derived from this software
15
+ without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ You are under no obligation whatsoever to provide any bug fixes, patches, or
29
+ upgrades to the features, functionality or performance of the source code
30
+ ("Enhancements") to anyone; however, if you choose to make your Enhancements
31
+ available either publicly, or directly to the author of this software, without
32
+ imposing a separate written license agreement for such Enhancements, then you
33
+ hereby grant the following license: a non-exclusive, royalty-free perpetual
34
+ license to install, use, modify, prepare derivative works, incorporate into
35
+ other computer software, distribute, and sublicense such enhancements or
36
+ derivative works thereof, in binary and source code form.
@@ -0,0 +1,346 @@
1
+ Metadata-Version: 2.1
2
+ Name: epiworldpy
3
+ Version: 0.0.1
4
+ Summary: Python bindings for epiworld
5
+ Author-email: "George G. Vega Yon" <g.vegayon@gmail.com>, Milo Banks <milobanks@rowlandhall.org>
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Requires-Python: >=3.7
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy
18
+ Provides-Extra: test
19
+ Requires-Dist: pytest; extra == "test"
20
+ Provides-Extra: doc
21
+ Requires-Dist: nbformat; extra == "doc"
22
+ Requires-Dist: nbclient; extra == "doc"
23
+ Requires-Dist: jupyter; extra == "doc"
24
+ Requires-Dist: epiworldpy[viz]; extra == "doc"
25
+ Provides-Extra: viz
26
+ Requires-Dist: ipympl>=0.8; extra == "viz"
27
+ Requires-Dist: matplotlib>=3.5.0; extra == "viz"
28
+ Requires-Dist: networkx>=3.0; extra == "viz"
29
+ Requires-Dist: scipy>=1.0; extra == "viz"
30
+
31
+ # epiworldpy: Python bindings for epiworld
32
+
33
+
34
+ [![Pip
35
+ Build](https://github.com/UofUEpiBio/epiworldpy/actions/workflows/pip.yaml/badge.svg)](https://github.com/UofUEpiBio/epiworldpy/actions/workflows/pip.yaml)
36
+
37
+ This is a python wrapper of the [`epiworld c++`
38
+ library](https://github.com/UofUEpiBio/epiworld/), an ABM simulation
39
+ engine. This is possible using the
40
+ [`pybind11`](https://pybind11.readthedocs.io/en/stable/) library (which
41
+ rocks!).
42
+
43
+ The `epiworld` module is already
44
+ <a href="https://github.com/UofUEpiBio/epiworldR"
45
+ target="_blank">implemented in R</a>.
46
+
47
+ # Installation
48
+
49
+ - clone this repository
50
+ - `pip install ./epiworldpy`
51
+
52
+ # Examples
53
+
54
+ ## Basic
55
+
56
+ Here we show how to create a `SEIR` object and add terms to it. We will
57
+ use the following data:
58
+
59
+ ``` python
60
+ # Loading the module
61
+ import epiworldpy as epiworld
62
+
63
+ # Create a SEIR model (susceptible, exposed, infectious, recovered), representing COVID-19.
64
+ covid19 = epiworld.ModelSEIR(
65
+ name = 'covid-19',
66
+ n = 10000,
67
+ prevalence = .01,
68
+ contact_rate = 2.0,
69
+ transmission_rate = .1,
70
+ incubation_days = 7.0,
71
+ recovery_rate = 0.14
72
+ )
73
+
74
+ # Taking a look
75
+ covid19.print(False)
76
+ ```
77
+
78
+ ________________________________________________________________________________
79
+ ________________________________________________________________________________
80
+ SIMULATION STUDY
81
+
82
+ Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
83
+ Population size : 10000
84
+ Agents' data : (none)
85
+ Number of entities : 0
86
+ Days (duration) : 0 (of 0)
87
+ Number of viruses : 1
88
+ Last run elapsed t : -
89
+ Rewiring : off
90
+
91
+ Global events:
92
+ - Update infected individuals (runs daily)
93
+
94
+ Virus(es):
95
+ - covid-19 (baseline prevalence: 1.00%)
96
+
97
+ Tool(s):
98
+ (none)
99
+
100
+ Model parameters:
101
+ - Avg. Incubation days : 7.0000
102
+ - Contact rate : 2.0000
103
+ - Prob. Recovery : 0.1400
104
+ - Prob. Transmission : 0.1000
105
+
106
+ <epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
107
+
108
+ Let’s run it and to see what we get:
109
+
110
+ ``` python
111
+ # Run for 100 days with a seed of 223.
112
+ covid19.run(100, 223)
113
+
114
+ # Print an overview.
115
+ covid19.print(False)
116
+ ```
117
+
118
+ _________________________________________________________________________
119
+ Running the model...
120
+ ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done.
121
+ done.
122
+ ________________________________________________________________________________
123
+ ________________________________________________________________________________
124
+ SIMULATION STUDY
125
+
126
+ Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
127
+ Population size : 10000
128
+ Agents' data : (none)
129
+ Number of entities : 0
130
+ Days (duration) : 100 (of 100)
131
+ Number of viruses : 1
132
+ Last run elapsed t : 14.00ms
133
+ Last run speed : 70.84 million agents x day / second
134
+ Rewiring : off
135
+
136
+ Global events:
137
+ - Update infected individuals (runs daily)
138
+
139
+ Virus(es):
140
+ - covid-19 (baseline prevalence: 1.00%)
141
+
142
+ Tool(s):
143
+ (none)
144
+
145
+ Model parameters:
146
+ - Avg. Incubation days : 7.0000
147
+ - Contact rate : 2.0000
148
+ - Prob. Recovery : 0.1400
149
+ - Prob. Transmission : 0.1000
150
+
151
+ Distribution of the population at time 100:
152
+ - (0) Susceptible : 9900 -> 7275
153
+ - (1) Exposed : 100 -> 269
154
+ - (2) Infected : 0 -> 292
155
+ - (3) Recovered : 0 -> 2164
156
+
157
+ Transition Probabilities:
158
+ - Susceptible 1.00 0.00 0.00 0.00
159
+ - Exposed 0.00 0.85 0.15 0.00
160
+ - Infected 0.00 0.00 0.86 0.14
161
+ - Recovered 0.00 0.00 0.00 1.00
162
+
163
+ <epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
164
+
165
+ We can now visualize the model’s compartments:
166
+
167
+ ``` python
168
+ import numpy as np
169
+ import matplotlib.pyplot as plt
170
+
171
+ # Get the data from the database
172
+ history = covid19.get_db().get_hist_total()
173
+
174
+ # Extract unique states and dates
175
+ unique_states = np.unique(history['states'])
176
+ unique_dates = np.unique(history['dates'])
177
+
178
+ # Remove some data that will mess with scaling
179
+ unique_states = np.delete(unique_states, np.where(unique_states == 'Susceptible'))
180
+
181
+ # Initialize a dictionary to store time series data for each state
182
+ time_series_data = {state: [] for state in unique_states}
183
+
184
+ # Populate the time series data for each state
185
+ for state in unique_states:
186
+ for date in unique_dates:
187
+ # Get the count for the current state and date
188
+ mask = (history['states'] == state) & (history['dates'] == date)
189
+ count = history['counts'][mask][0]
190
+ time_series_data[state].append(count)
191
+
192
+ # Start the plotting!
193
+ plt.figure(figsize=(10, 6))
194
+
195
+ for state in unique_states:
196
+ plt.plot(unique_dates, time_series_data[state], label=state)
197
+
198
+ plt.xlabel('Day')
199
+ plt.ylabel('Count')
200
+ plt.title('COVID-19 SEIR Model Data')
201
+ plt.legend()
202
+ plt.grid(True)
203
+ plt.show()
204
+ ```
205
+
206
+ ![COVID-19 SEIR model
207
+ data](README_files/figure-commonmark/series-visualization-output-1.png)
208
+
209
+ We can get the effective reproductive number, over time, too:
210
+
211
+ ``` python
212
+ reproductive_data = covid19.get_db().get_reproductive_number()
213
+
214
+ # Start the plotting!
215
+ plt.figure(figsize=(10, 6))
216
+
217
+ for virus_id, virus_data in enumerate(reproductive_data):
218
+ average_rts = list()
219
+
220
+ for date_data in virus_data:
221
+ if not date_data:
222
+ continue
223
+
224
+ keys_array = np.array(list(date_data.values()), dtype=np.float64)
225
+ average_rts.append(np.mean(keys_array))
226
+
227
+ plt.plot(range(0, len(virus_data)-1), average_rts, label=f"Virus {virus_id}")
228
+
229
+ plt.xlabel('Date')
230
+ plt.ylabel('Effective Reproductive Rate')
231
+ plt.title('COVID-19 SEIR Model Effective Reproductive Rate')
232
+ plt.legend()
233
+ plt.grid(True)
234
+ plt.show()
235
+ ```
236
+
237
+ ![COVID-19 SEIR model effective reproductive
238
+ number](README_files/figure-commonmark/rt-visualization-output-1.png)
239
+
240
+ Let’s do the same for generation time:
241
+
242
+ ``` python
243
+ from collections import defaultdict
244
+
245
+ generation_time = covid19.get_db().get_generation_time()
246
+ agents = generation_time['agents']
247
+ viruses = generation_time['viruses']
248
+ times = generation_time['times']
249
+ gentimes = generation_time['gentimes']
250
+
251
+ # Data formatting
252
+ unique_viruses = np.unique(viruses)
253
+ data = defaultdict(lambda: defaultdict(list))
254
+
255
+ for agent, virus, time, gentime in zip(agents, viruses, times, gentimes):
256
+ data[virus][time].append(gentime)
257
+
258
+ average_data = {virus: {} for virus in unique_viruses}
259
+
260
+ for virus, time_dict in data.items():
261
+ for time, gentime_list in time_dict.items():
262
+ average_data[virus][time] = np.mean(gentime_list)
263
+
264
+ # Plotting
265
+ plt.figure(figsize=(10, 6))
266
+ for virus, time_dict in average_data.items():
267
+ times = sorted(time_dict.keys())
268
+ gentimes = [time_dict[time] for time in times]
269
+ plt.plot(times, gentimes, label=f'Virus {virus}')
270
+
271
+ plt.xlabel('Date')
272
+ plt.ylabel('Generation Time')
273
+ plt.title('COVID-19 SEIR Model Generation Time')
274
+ plt.legend()
275
+ plt.grid(True)
276
+ plt.show()
277
+ ```
278
+
279
+ ![COVID-19 SEIR model generation
280
+ time](README_files/figure-commonmark/gentime-visualization-output-1.png)
281
+
282
+ Epiworld records agent-agent interactions, and we can graph those too.
283
+ In the below example, we only track all cases stemming from a specific
284
+ index case, despite the model having a prevalence of 0.01.
285
+
286
+ ``` python
287
+ import networkx as nx
288
+ from matplotlib.animation import FuncAnimation
289
+
290
+ transmissions = covid19.get_db().get_transmissions()
291
+ start = transmissions['source_exposure_dates']
292
+ end = transmissions['dates']
293
+ source = transmissions['sources']
294
+ target = transmissions['targets']
295
+ days = max(end)
296
+
297
+ graph = nx.Graph()
298
+ fig, ax = plt.subplots(figsize=(6,4))
299
+
300
+ # Animation function
301
+ to_track = { source[0] }
302
+ def update(frame):
303
+ ax.clear()
304
+
305
+ agents_involved_today = set()
306
+ agents_relationships_we_care_about = []
307
+
308
+ # Get only the agents involved in the current frame.
309
+ for i in range(len(start)):
310
+ if start[i] <= frame <= end[i]:
311
+ agents_involved_today.add((source[i], target[i]))
312
+
313
+ # Get only today's agents who have some connection to agents
314
+ # we've seen before.
315
+ for agent in agents_involved_today:
316
+ if agent[0] in to_track or agent[1] in to_track:
317
+ to_track.add(agent[0])
318
+ to_track.add(agent[1])
319
+ graph.add_edge(agent[0], agent[1])
320
+
321
+ # Lay and space them out.
322
+ pos = nx.kamada_kawai_layout(graph)
323
+
324
+ options = {
325
+ "with_labels": True,
326
+ "node_size": 300,
327
+ "font_size": 6,
328
+ "node_color": "white",
329
+ "edgecolors": "white",
330
+ "linewidths": 1,
331
+ "width": 1,
332
+ }
333
+
334
+ # Graph!
335
+ nx.draw_networkx(graph, pos, **options)
336
+ ax.set_title(f"COVID-19 SEIR Model Agent Contact (Day {frame})")
337
+
338
+ ani = FuncAnimation(fig, update, frames=int(days/3), interval=200, repeat=False)
339
+ plt.show()
340
+ ```
341
+
342
+ <!-- I couldn't figure out a way to get Quarto to do animations correctly so we're
343
+ hardcoding a GIF. -->
344
+
345
+ ![Contact information from the COVID-19 SEIR model
346
+ run](README_files/figure-commonmark/contact-visualization-output-1.gif)
@@ -0,0 +1,316 @@
1
+ # epiworldpy: Python bindings for epiworld
2
+
3
+
4
+ [![Pip
5
+ Build](https://github.com/UofUEpiBio/epiworldpy/actions/workflows/pip.yaml/badge.svg)](https://github.com/UofUEpiBio/epiworldpy/actions/workflows/pip.yaml)
6
+
7
+ This is a python wrapper of the [`epiworld c++`
8
+ library](https://github.com/UofUEpiBio/epiworld/), an ABM simulation
9
+ engine. This is possible using the
10
+ [`pybind11`](https://pybind11.readthedocs.io/en/stable/) library (which
11
+ rocks!).
12
+
13
+ The `epiworld` module is already
14
+ <a href="https://github.com/UofUEpiBio/epiworldR"
15
+ target="_blank">implemented in R</a>.
16
+
17
+ # Installation
18
+
19
+ - clone this repository
20
+ - `pip install ./epiworldpy`
21
+
22
+ # Examples
23
+
24
+ ## Basic
25
+
26
+ Here we show how to create a `SEIR` object and add terms to it. We will
27
+ use the following data:
28
+
29
+ ``` python
30
+ # Loading the module
31
+ import epiworldpy as epiworld
32
+
33
+ # Create a SEIR model (susceptible, exposed, infectious, recovered), representing COVID-19.
34
+ covid19 = epiworld.ModelSEIR(
35
+ name = 'covid-19',
36
+ n = 10000,
37
+ prevalence = .01,
38
+ contact_rate = 2.0,
39
+ transmission_rate = .1,
40
+ incubation_days = 7.0,
41
+ recovery_rate = 0.14
42
+ )
43
+
44
+ # Taking a look
45
+ covid19.print(False)
46
+ ```
47
+
48
+ ________________________________________________________________________________
49
+ ________________________________________________________________________________
50
+ SIMULATION STUDY
51
+
52
+ Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
53
+ Population size : 10000
54
+ Agents' data : (none)
55
+ Number of entities : 0
56
+ Days (duration) : 0 (of 0)
57
+ Number of viruses : 1
58
+ Last run elapsed t : -
59
+ Rewiring : off
60
+
61
+ Global events:
62
+ - Update infected individuals (runs daily)
63
+
64
+ Virus(es):
65
+ - covid-19 (baseline prevalence: 1.00%)
66
+
67
+ Tool(s):
68
+ (none)
69
+
70
+ Model parameters:
71
+ - Avg. Incubation days : 7.0000
72
+ - Contact rate : 2.0000
73
+ - Prob. Recovery : 0.1400
74
+ - Prob. Transmission : 0.1000
75
+
76
+ <epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
77
+
78
+ Let’s run it and to see what we get:
79
+
80
+ ``` python
81
+ # Run for 100 days with a seed of 223.
82
+ covid19.run(100, 223)
83
+
84
+ # Print an overview.
85
+ covid19.print(False)
86
+ ```
87
+
88
+ _________________________________________________________________________
89
+ Running the model...
90
+ ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done.
91
+ done.
92
+ ________________________________________________________________________________
93
+ ________________________________________________________________________________
94
+ SIMULATION STUDY
95
+
96
+ Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
97
+ Population size : 10000
98
+ Agents' data : (none)
99
+ Number of entities : 0
100
+ Days (duration) : 100 (of 100)
101
+ Number of viruses : 1
102
+ Last run elapsed t : 14.00ms
103
+ Last run speed : 70.84 million agents x day / second
104
+ Rewiring : off
105
+
106
+ Global events:
107
+ - Update infected individuals (runs daily)
108
+
109
+ Virus(es):
110
+ - covid-19 (baseline prevalence: 1.00%)
111
+
112
+ Tool(s):
113
+ (none)
114
+
115
+ Model parameters:
116
+ - Avg. Incubation days : 7.0000
117
+ - Contact rate : 2.0000
118
+ - Prob. Recovery : 0.1400
119
+ - Prob. Transmission : 0.1000
120
+
121
+ Distribution of the population at time 100:
122
+ - (0) Susceptible : 9900 -> 7275
123
+ - (1) Exposed : 100 -> 269
124
+ - (2) Infected : 0 -> 292
125
+ - (3) Recovered : 0 -> 2164
126
+
127
+ Transition Probabilities:
128
+ - Susceptible 1.00 0.00 0.00 0.00
129
+ - Exposed 0.00 0.85 0.15 0.00
130
+ - Infected 0.00 0.00 0.86 0.14
131
+ - Recovered 0.00 0.00 0.00 1.00
132
+
133
+ <epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
134
+
135
+ We can now visualize the model’s compartments:
136
+
137
+ ``` python
138
+ import numpy as np
139
+ import matplotlib.pyplot as plt
140
+
141
+ # Get the data from the database
142
+ history = covid19.get_db().get_hist_total()
143
+
144
+ # Extract unique states and dates
145
+ unique_states = np.unique(history['states'])
146
+ unique_dates = np.unique(history['dates'])
147
+
148
+ # Remove some data that will mess with scaling
149
+ unique_states = np.delete(unique_states, np.where(unique_states == 'Susceptible'))
150
+
151
+ # Initialize a dictionary to store time series data for each state
152
+ time_series_data = {state: [] for state in unique_states}
153
+
154
+ # Populate the time series data for each state
155
+ for state in unique_states:
156
+ for date in unique_dates:
157
+ # Get the count for the current state and date
158
+ mask = (history['states'] == state) & (history['dates'] == date)
159
+ count = history['counts'][mask][0]
160
+ time_series_data[state].append(count)
161
+
162
+ # Start the plotting!
163
+ plt.figure(figsize=(10, 6))
164
+
165
+ for state in unique_states:
166
+ plt.plot(unique_dates, time_series_data[state], label=state)
167
+
168
+ plt.xlabel('Day')
169
+ plt.ylabel('Count')
170
+ plt.title('COVID-19 SEIR Model Data')
171
+ plt.legend()
172
+ plt.grid(True)
173
+ plt.show()
174
+ ```
175
+
176
+ ![COVID-19 SEIR model
177
+ data](README_files/figure-commonmark/series-visualization-output-1.png)
178
+
179
+ We can get the effective reproductive number, over time, too:
180
+
181
+ ``` python
182
+ reproductive_data = covid19.get_db().get_reproductive_number()
183
+
184
+ # Start the plotting!
185
+ plt.figure(figsize=(10, 6))
186
+
187
+ for virus_id, virus_data in enumerate(reproductive_data):
188
+ average_rts = list()
189
+
190
+ for date_data in virus_data:
191
+ if not date_data:
192
+ continue
193
+
194
+ keys_array = np.array(list(date_data.values()), dtype=np.float64)
195
+ average_rts.append(np.mean(keys_array))
196
+
197
+ plt.plot(range(0, len(virus_data)-1), average_rts, label=f"Virus {virus_id}")
198
+
199
+ plt.xlabel('Date')
200
+ plt.ylabel('Effective Reproductive Rate')
201
+ plt.title('COVID-19 SEIR Model Effective Reproductive Rate')
202
+ plt.legend()
203
+ plt.grid(True)
204
+ plt.show()
205
+ ```
206
+
207
+ ![COVID-19 SEIR model effective reproductive
208
+ number](README_files/figure-commonmark/rt-visualization-output-1.png)
209
+
210
+ Let’s do the same for generation time:
211
+
212
+ ``` python
213
+ from collections import defaultdict
214
+
215
+ generation_time = covid19.get_db().get_generation_time()
216
+ agents = generation_time['agents']
217
+ viruses = generation_time['viruses']
218
+ times = generation_time['times']
219
+ gentimes = generation_time['gentimes']
220
+
221
+ # Data formatting
222
+ unique_viruses = np.unique(viruses)
223
+ data = defaultdict(lambda: defaultdict(list))
224
+
225
+ for agent, virus, time, gentime in zip(agents, viruses, times, gentimes):
226
+ data[virus][time].append(gentime)
227
+
228
+ average_data = {virus: {} for virus in unique_viruses}
229
+
230
+ for virus, time_dict in data.items():
231
+ for time, gentime_list in time_dict.items():
232
+ average_data[virus][time] = np.mean(gentime_list)
233
+
234
+ # Plotting
235
+ plt.figure(figsize=(10, 6))
236
+ for virus, time_dict in average_data.items():
237
+ times = sorted(time_dict.keys())
238
+ gentimes = [time_dict[time] for time in times]
239
+ plt.plot(times, gentimes, label=f'Virus {virus}')
240
+
241
+ plt.xlabel('Date')
242
+ plt.ylabel('Generation Time')
243
+ plt.title('COVID-19 SEIR Model Generation Time')
244
+ plt.legend()
245
+ plt.grid(True)
246
+ plt.show()
247
+ ```
248
+
249
+ ![COVID-19 SEIR model generation
250
+ time](README_files/figure-commonmark/gentime-visualization-output-1.png)
251
+
252
+ Epiworld records agent-agent interactions, and we can graph those too.
253
+ In the below example, we only track all cases stemming from a specific
254
+ index case, despite the model having a prevalence of 0.01.
255
+
256
+ ``` python
257
+ import networkx as nx
258
+ from matplotlib.animation import FuncAnimation
259
+
260
+ transmissions = covid19.get_db().get_transmissions()
261
+ start = transmissions['source_exposure_dates']
262
+ end = transmissions['dates']
263
+ source = transmissions['sources']
264
+ target = transmissions['targets']
265
+ days = max(end)
266
+
267
+ graph = nx.Graph()
268
+ fig, ax = plt.subplots(figsize=(6,4))
269
+
270
+ # Animation function
271
+ to_track = { source[0] }
272
+ def update(frame):
273
+ ax.clear()
274
+
275
+ agents_involved_today = set()
276
+ agents_relationships_we_care_about = []
277
+
278
+ # Get only the agents involved in the current frame.
279
+ for i in range(len(start)):
280
+ if start[i] <= frame <= end[i]:
281
+ agents_involved_today.add((source[i], target[i]))
282
+
283
+ # Get only today's agents who have some connection to agents
284
+ # we've seen before.
285
+ for agent in agents_involved_today:
286
+ if agent[0] in to_track or agent[1] in to_track:
287
+ to_track.add(agent[0])
288
+ to_track.add(agent[1])
289
+ graph.add_edge(agent[0], agent[1])
290
+
291
+ # Lay and space them out.
292
+ pos = nx.kamada_kawai_layout(graph)
293
+
294
+ options = {
295
+ "with_labels": True,
296
+ "node_size": 300,
297
+ "font_size": 6,
298
+ "node_color": "white",
299
+ "edgecolors": "white",
300
+ "linewidths": 1,
301
+ "width": 1,
302
+ }
303
+
304
+ # Graph!
305
+ nx.draw_networkx(graph, pos, **options)
306
+ ax.set_title(f"COVID-19 SEIR Model Agent Contact (Day {frame})")
307
+
308
+ ani = FuncAnimation(fig, update, frames=int(days/3), interval=200, repeat=False)
309
+ plt.show()
310
+ ```
311
+
312
+ <!-- I couldn't figure out a way to get Quarto to do animations correctly so we're
313
+ hardcoding a GIF. -->
314
+
315
+ ![Contact information from the COVID-19 SEIR model
316
+ run](README_files/figure-commonmark/contact-visualization-output-1.gif)