1
+ # -*- coding: utf-8 -*-
1
2
import pandas as pd
2
3
import numpy as np
3
4
import covasim as cv # Version used in our study is 3.07
13
14
from causal_testing .testing .estimators import LinearRegressionEstimator
14
15
from causal_testing .testing .base_test_case import BaseTestCase
15
16
17
+ import os
18
+ import logging
16
19
17
- def experimental_causal_test_vaccinate_elderly (runs_per_test_per_config : int = 30 , verbose : bool = False ):
18
- """ Run the causal test case for the effect of changing vaccine to prioritise elderly. This uses the experimental
20
+ logger = logging .getLogger (__name__ )
21
+ logging .basicConfig (level = logging .DEBUG , format = "%(message)s" )
22
+
23
+ ROOT = os .path .realpath (os .path .dirname (__file__ ))
24
+
25
+
26
+ def test_experimental_vaccinate_elderly (runs_per_test_per_config : int = 30 , verbose : bool = False ):
27
+ """Run the causal test case for the effect of changing vaccine to prioritise elderly. This uses the experimental
19
28
data collector.
20
29
21
30
:param runs_per_test_per_config: Number of times to run each input configuration (control and treatment) per test.
@@ -25,78 +34,79 @@ def experimental_causal_test_vaccinate_elderly(runs_per_test_per_config: int = 3
25
34
"""
26
35
27
36
# 1. Read in the Causal DAG
28
- causal_dag = CausalDAG (' dag.dot' )
37
+ causal_dag = CausalDAG (f" { ROOT } / dag.dot" )
29
38
30
39
# 2. Create variables
31
- pop_size = Input (' pop_size' , int )
32
- pop_infected = Input (' pop_infected' , int )
33
- n_days = Input (' n_days' , int )
34
- vaccine = Input (' vaccine' , int )
35
- cum_infections = Output (' cum_infections' , int )
36
- cum_vaccinations = Output (' cum_vaccinations' , int )
37
- cum_vaccinated = Output (' cum_vaccinated' , int )
38
- max_doses = Output (' max_doses' , int )
40
+ pop_size = Input (" pop_size" , int )
41
+ pop_infected = Input (" pop_infected" , int )
42
+ n_days = Input (" n_days" , int )
43
+ vaccine = Input (" vaccine" , int )
44
+ cum_infections = Output (" cum_infections" , int )
45
+ cum_vaccinations = Output (" cum_vaccinations" , int )
46
+ cum_vaccinated = Output (" cum_vaccinated" , int )
47
+ max_doses = Output (" max_doses" , int )
39
48
40
49
# 3. Create scenario by applying constraints over a subset of the input variables
41
- scenario = Scenario (variables = {pop_size , pop_infected , n_days , cum_infections , vaccine ,
42
- cum_vaccinated , cum_vaccinations , max_doses },
43
- constraints = {pop_size .z3 == 50000 , pop_infected .z3 == 1000 , n_days .z3 == 50 })
50
+ scenario = Scenario (
51
+ variables = {
52
+ pop_size ,
53
+ pop_infected ,
54
+ n_days ,
55
+ cum_infections ,
56
+ vaccine ,
57
+ cum_vaccinated ,
58
+ cum_vaccinations ,
59
+ max_doses ,
60
+ },
61
+ constraints = {pop_size .z3 == 50000 , pop_infected .z3 == 1000 , n_days .z3 == 50 },
62
+ )
44
63
45
64
# 4. Construct a causal specification from the scenario and causal DAG
46
65
causal_specification = CausalSpecification (scenario , causal_dag )
47
66
48
67
# 5. Instantiate the experimental data collector for Covasim
49
- covasim_parameters_dict = {'pop_size' : 50000 ,
50
- 'pop_type' : 'hybrid' ,
51
- 'pop_infected' : 1000 ,
52
- 'n_days' : 50 }
53
- control_input_configuration = {'covasim_parameters_dict' : covasim_parameters_dict ,
54
- 'target_elderly' : False }
55
- treatment_input_configuration = {'covasim_parameters_dict' : covasim_parameters_dict ,
56
- 'target_elderly' : True }
57
- data_collector = CovasimVaccineDataCollector (scenario , control_input_configuration ,
58
- treatment_input_configuration ,
59
- runs_per_test_per_config )
68
+ covasim_parameters_dict = {"pop_size" : 50000 , "pop_type" : "hybrid" , "pop_infected" : 1000 , "n_days" : 50 }
69
+ control_input_configuration = {"covasim_parameters_dict" : covasim_parameters_dict , "target_elderly" : False }
70
+ treatment_input_configuration = {"covasim_parameters_dict" : covasim_parameters_dict , "target_elderly" : True }
71
+ data_collector = CovasimVaccineDataCollector (
72
+ scenario , control_input_configuration , treatment_input_configuration , runs_per_test_per_config
73
+ )
60
74
61
75
# 6. Express expected outcomes
62
- expected_outcome_effects = {cum_infections : Positive (),
63
- cum_vaccinations : Negative (),
64
- cum_vaccinated : Negative (),
65
- max_doses : NoEffect ()
66
- }
67
- results_dict = {'cum_infections' : {},
68
- 'cum_vaccinations' : {},
69
- 'cum_vaccinated' : {},
70
- 'max_doses' : {}
71
- }
76
+ expected_outcome_effects = {
77
+ cum_infections : Positive (),
78
+ cum_vaccinations : Negative (),
79
+ cum_vaccinated : Negative (),
80
+ max_doses : NoEffect (),
81
+ }
82
+ results_dict = {"cum_infections" : {}, "cum_vaccinations" : {}, "cum_vaccinated" : {}, "max_doses" : {}}
72
83
73
84
# 7. Create an instance of the causal test engine
74
85
causal_test_engine = CausalTestEngine (causal_specification , data_collector , index_col = 0 )
75
86
76
87
for outcome_variable , expected_effect in expected_outcome_effects .items ():
77
- base_test_case = BaseTestCase (treatment_variable = vaccine ,
78
- outcome_variable = outcome_variable )
79
- causal_test_case = CausalTestCase (base_test_case = base_test_case ,
80
- expected_causal_effect = expected_effect ,
81
- control_value = 0 ,
82
- treatment_value = 1 )
88
+ base_test_case = BaseTestCase (treatment_variable = vaccine , outcome_variable = outcome_variable )
89
+ causal_test_case = CausalTestCase (
90
+ base_test_case = base_test_case , expected_causal_effect = expected_effect , control_value = 0 , treatment_value = 1
91
+ )
83
92
84
93
# 8. Obtain the minimal adjustment set for the causal test case from the causal DAG
85
94
minimal_adjustment_set = causal_dag .identification (base_test_case )
86
95
87
96
# 9. Build statistical model
88
- linear_regression_estimator = LinearRegressionEstimator (vaccine . name , 1 , 0 ,
89
- minimal_adjustment_set ,
90
- outcome_variable . name )
97
+ linear_regression_estimator = LinearRegressionEstimator (
98
+ vaccine . name , 1 , 0 , minimal_adjustment_set , outcome_variable . name
99
+ )
91
100
92
101
# 10. Execute test and save results in dict
93
- causal_test_result = causal_test_engine .execute_test (linear_regression_estimator , causal_test_case , ' ate' )
102
+ causal_test_result = causal_test_engine .execute_test (linear_regression_estimator , causal_test_case , " ate" )
94
103
if verbose :
95
- print (f"Causation:\n { causal_test_result } " )
96
- results_dict [outcome_variable .name ]['ate' ] = causal_test_result .test_value .value
97
- results_dict [outcome_variable .name ]['cis' ] = causal_test_result .confidence_intervals
98
- results_dict [outcome_variable .name ]['test_passes' ] = causal_test_case .expected_causal_effect .apply (
99
- causal_test_result )
104
+ logging .info ("Causation:\n %s" , causal_test_result )
105
+ results_dict [outcome_variable .name ]["ate" ] = causal_test_result .test_value .value
106
+ results_dict [outcome_variable .name ]["cis" ] = causal_test_result .confidence_intervals
107
+ results_dict [outcome_variable .name ]["test_passes" ] = causal_test_case .expected_causal_effect .apply (
108
+ causal_test_result
109
+ )
100
110
return results_dict
101
111
102
112
@@ -108,19 +118,19 @@ class CovasimVaccineDataCollector(ExperimentalDataCollector):
108
118
"""
109
119
110
120
def run_system_with_input_configuration (self , input_configuration : dict ) -> pd .DataFrame :
111
- """ Run the system with a given input configuration.
121
+ """Run the system with a given input configuration.
112
122
113
123
:param input_configuration: A nested dictionary containing Covasim parameters, desired number of repeats, and
114
124
a bool to determine whether elderly should be prioritised for vaccination.
115
125
:return: A dataframe containing results for this input configuration.
116
126
"""
117
- results_df = self .simulate_vaccine (input_configuration [ 'covasim_parameters_dict' ],
118
- self .n_repeats ,
119
- input_configuration [ 'target_elderly' ] )
127
+ results_df = self .simulate_vaccine (
128
+ input_configuration [ "covasim_parameters_dict" ], self .n_repeats , input_configuration [ "target_elderly" ]
129
+ )
120
130
return results_df
121
131
122
132
def simulate_vaccine (self , pars_dict : dict , n_simulations : int = 100 , target_elderly : bool = False ):
123
- """ Simulate observational data that contains a vaccine that is optionally given preferentially to the elderly.
133
+ """Simulate observational data that contains a vaccine that is optionally given preferentially to the elderly.
124
134
125
135
:param pars_dict: A dictionary containing simulation parameters.
126
136
:param n_simulations: Number of simulations to run.
@@ -129,38 +139,49 @@ def simulate_vaccine(self, pars_dict: dict, n_simulations: int = 100, target_eld
129
139
"""
130
140
simulations_results_dfs = []
131
141
for sim_n in range (n_simulations ):
132
- print ( f' Simulation { sim_n + 1 } / { n_simulations } .' )
142
+ logging . info ( " Simulation %s/%s." , sim_n + 1 , n_simulations )
133
143
134
144
# Update simulation parameters with vaccine and optionally sub-target
135
145
if target_elderly :
136
- print ("Prioritising the elderly for vaccination" )
137
- vaccine = cv .vaccinate_prob (vaccine = "Pfizer" , label = "prioritise_elderly" ,
138
- subtarget = self .vaccinate_by_age , days = list (range (7 , pars_dict ['n_days' ])))
146
+ logger .info ("Prioritising the elderly for vaccination" )
147
+ vaccine = cv .vaccinate_prob (
148
+ vaccine = "Pfizer" ,
149
+ label = "prioritise_elderly" ,
150
+ subtarget = self .vaccinate_by_age ,
151
+ days = list (range (7 , pars_dict ["n_days" ])),
152
+ )
139
153
else :
140
- print ("Using standard vaccination protocol" )
141
- vaccine = cv .vaccinate_prob (vaccine = "Pfizer" , label = "regular" , days = list (range (7 , pars_dict ['n_days' ])))
142
-
143
- pars_dict ['interventions' ] = vaccine
144
- pars_dict ['use_waning' ] = True # Must be set to true for vaccination
145
- sim_results_df = self .run_sim_with_pars (pars_dict = pars_dict ,
146
- desired_outputs = ['cum_infections' , 'cum_deaths' , 'cum_recoveries' ,
147
- 'cum_vaccinations' , 'cum_vaccinated' ],
148
- n_runs = 1 )
149
-
150
- sim_results_df ['interventions' ] = vaccine .label # Store label in results instead of vaccine object
151
- sim_results_df ['target_elderly' ] = target_elderly
152
- sim_results_df ['vaccine' ] = int (target_elderly ) # 0 if standard vaccine, 1 if target elderly vaccine
153
- sim_results_df ['max_doses' ] = vaccine .p ['doses' ] # Get max doses for the vaccine
154
+ logger .info ("Using standard vaccination protocol" )
155
+ vaccine = cv .vaccinate_prob (vaccine = "Pfizer" , label = "regular" , days = list (range (7 , pars_dict ["n_days" ])))
156
+
157
+ pars_dict ["interventions" ] = vaccine
158
+ pars_dict ["use_waning" ] = True # Must be set to true for vaccination
159
+ sim_results_df = self .run_sim_with_pars (
160
+ pars_dict = pars_dict ,
161
+ desired_outputs = [
162
+ "cum_infections" ,
163
+ "cum_deaths" ,
164
+ "cum_recoveries" ,
165
+ "cum_vaccinations" ,
166
+ "cum_vaccinated" ,
167
+ ],
168
+ n_runs = 1 ,
169
+ )
170
+
171
+ sim_results_df ["interventions" ] = vaccine .label # Store label in results instead of vaccine object
172
+ sim_results_df ["target_elderly" ] = target_elderly
173
+ sim_results_df ["vaccine" ] = int (target_elderly ) # 0 if standard vaccine, 1 if target elderly vaccine
174
+ sim_results_df ["max_doses" ] = vaccine .p ["doses" ] # Get max doses for the vaccine
154
175
simulations_results_dfs .append (sim_results_df )
155
176
156
177
# Create a single dataframe containing a row for every execution
157
178
obs_df = pd .concat (simulations_results_dfs , ignore_index = True )
158
- obs_df .rename (columns = {' interventions' : ' vaccine_type' }, inplace = True )
179
+ obs_df .rename (columns = {" interventions" : " vaccine_type" }, inplace = True )
159
180
return obs_df
160
181
161
182
@staticmethod
162
183
def run_sim_with_pars (pars_dict : dict , desired_outputs : [str ], n_runs : int = 1 , verbose : int = - 1 ):
163
- """ Runs a Covasim COVID-19 simulation with a given dict of parameters and collects the desired outputs,
184
+ """Runs a Covasim COVID-19 simulation with a given dict of parameters and collects the desired outputs,
164
185
which are given as a list of output names.
165
186
166
187
:param pars_dict: A dictionary containing the parameters and their values for the run.
@@ -170,17 +191,17 @@ def run_sim_with_pars(pars_dict: dict, desired_outputs: [str], n_runs: int = 1,
170
191
171
192
:return results_df: A pandas df containing the results for each run
172
193
"""
173
- results_dict = {k : [] for k in list (pars_dict .keys ()) + desired_outputs + [' rand_seed' ]}
194
+ results_dict = {k : [] for k in list (pars_dict .keys ()) + desired_outputs + [" rand_seed" ]}
174
195
for _ in range (n_runs ):
175
196
# For every run, generate and use a new a random seed.
176
197
# This is to avoid using Covasim's sequential random seeds.
177
198
random .seed ()
178
199
rand_seed = random .randint (0 , 10000 )
179
- pars_dict [' rand_seed' ] = rand_seed
180
- print ( f "Rand Seed: { rand_seed } " )
200
+ pars_dict [" rand_seed" ] = rand_seed
201
+ logger . info ( "Rand Seed: %s" , rand_seed )
181
202
sim = cv .Sim (pars = pars_dict )
182
203
m_sim = cv .MultiSim (sim )
183
- m_sim .run (n_runs = 1 , verbose = verbose , n_cpus = 1 )
204
+ m_sim .run (n_runs = 1 , verbose = False , n_cpus = 1 )
184
205
185
206
for run in m_sim .sims :
186
207
results = run .results
@@ -191,14 +212,15 @@ def run_sim_with_pars(pars_dict: dict, desired_outputs: [str], n_runs: int = 1,
191
212
# Append outputs to results
192
213
for output in desired_outputs :
193
214
if output not in results :
194
- raise IndexError (f' { output } is not in the Covasim outputs.' )
215
+ raise IndexError (f" { output } is not in the Covasim outputs." )
195
216
results_dict [output ].append (
196
- results [output ][- 1 ]) # Append the final recorded value for each variable
217
+ results [output ][- 1 ]
218
+ ) # Append the final recorded value for each variable
197
219
198
220
# Any parameters without results are assigned np.nan for each execution
199
221
for param , results in results_dict .items ():
200
222
if not results :
201
- results_dict [param ] = [np .nan ] * len (results_dict [' rand_seed' ])
223
+ results_dict [param ] = [np .nan ] * len (results_dict [" rand_seed" ])
202
224
return pd .DataFrame (results_dict )
203
225
204
226
@staticmethod
@@ -223,5 +245,5 @@ def vaccinate_by_age(simulation):
223
245
224
246
225
247
if __name__ == "__main__" :
226
- test_results = experimental_causal_test_vaccinate_elderly (runs_per_test_per_config = 30 , verbose = True )
227
- print ( test_results )
248
+ test_results = test_experimental_vaccinate_elderly (runs_per_test_per_config = 30 , verbose = True )
249
+ logging . info ( "%s" , test_results )
0 commit comments