Package: epidemia
EpidemiaEpidemia is a Python library that simulates epidemic outbreaks using a SIR-like model.InstallpipinstallepidemiaIt is recommended to use PyPy instead of CPython (the default Python interpreter) if you have performance problems.ModelsThe following SIR-like models have been implemented.SIR: susceptible, infected, recovered modelSEIR: susceptible, exposed, infected, recovered modelModelReport2: the model defined in report 2 atCOVID-19 en ChileOptimization methodsList of available optimization methods.Nelder-Mead,Powell,CG,BFGS,Newton-CG,COBYLA,trust-constr,dogleg,trust-ncg,trust-exact,trust-krylovusingscipy.optimize.minimizewithout boundslmusingscipy.optimize.least_squaredwithout boundsL-BFGS-B,TNC,SLSQPusingscipy.optimize.minimizetrf,dogboxusingscipy.optimize.least_squaredannealingusingscipy.optimize.dual_annealingbayesianusingskopt.gp_minimizegbrtusingskopt.gbrt_minimizeforestusingskopt.forest_minimizePSOusingpyswarms.single.global_best.GlobalBestPSOFrom experience it is necessary to have a good initial estimate of parameters before fitting. An initial optimization round using eitherannealingor especiallyPSOwork very well. For improved speed it is recommended to use thefast=Trueflag for naive integration. Finally, anL-BFGS-Bwithfast=False(i.e. using RK4) will tweak the parameters to fit the curve as best as possible.ExamplesJupyter notebooks of trained models for specific countries.ChinaChileItalySimulatingWe need to define the start time and initial state the compartments are in. Using a parameter function we can feed parameter values to the model dependent on time. To run the simulation we additionally define the end time.importnumpyasnpfromepidemicimport*# Initial time and statet0=np.datetime64('2020-01-01')y0={'S':1e7,'E':200,'Im':200,'I':20,'H':0,'Hc':0,'R':0,'D':0}# Our alpha at time 't'defα(t):ift>=np.datetime64('2020-06-01')andt<np.datetime64('2020-09-01'):return0.5return1.0# Our parameters at time 't'params=lambdaY,t:{'βE':α(t)*0.062015625,'βIm':α(t)*0.12403125,'βI':α(t)*0.165375,'βH':α(t)*0.0,'βHc':α(t)*0.0,'γE':0.2,'γIm':0.1,'γI':0.1,'γH':0.1666,'γHc':0.1,'μb':3.57e-5,'μd':1.57e-5,'φEI':0.50,'φIR':0.85,'φHR':0.85,'φD':0.50,}# Create and run model till time 'tmax'model=ModelReport2()epidemic=Epidemic(model,t0,tmax=np.datetime64('2021-06-01'))epidemic.run(y0,params)epidemic.run_parameter('R_effective',model.R_effective)epidemic.plot('Epidemic',cols=['I','H','Hc','D'])epidemic.plot_params('Epidemic (R effective)',cols=['R_effective'])Seeexample.py.Training parametersIn order to train our parameters, we define a mapping functionx_params:x=>paramswith boundsx_boundsforx. The first parameter tox_paramsis the initial statey0, and the following parameters are those that are being optimized. Thex_paramsfunction will return a new initial statey0and a newparamsfunction to define how parameters develop over time (see above).First we load our model like above, but we pass adataDataFrame from which we can calculate the error. The DataFrame must have column names that correspond to the model's compartments and derived compartments (more on that later). We define our training variables, bounds and function in order to train the model.# Define our training parameters: initial value, bounds, and mapping function to model parametersx=[0.74,# E010.3,# Im00.38,# CE0.75,# CIm0.165,# βI0.2,# γE0.1,# γIm0.1,# γI0.1667,# γH0.1,# γHc]x_bounds=[(0,20),# E0(0,20),# Im0(0.0,0.4),# CE(0.0,0.9),# CIm(0.0,0.75),# βI(0.17,0.25),# γE(0.07,0.14),# γIm(0.07,0.14),# γI(0.1,0.5),# γH(0.0625,0.14),# γHc]defx_params(E0,Im0,CE,CIm,βI,γE,γIm,γI,γH,γHc):y0={'S':1e7,'E':E0*I0,'Im':Im0*I0,'I':I0,'H':0,'Hc':0,'R':0,'D':D0,}λ1=np.datetime64('2020-04-01')κ1=0.05α2=0.75α=lambdat:1.0ift<λ1elseα2+(1.0-α2)*np.exp(-κ1*(t-λ1)/np.timedelta64(1,'D'))returny0,lambdat:{'βE':α(t)*CE*βI,'βIm':α(t)*CIm*βI,'βI':α(t)*βI,'βH':0.0,'βHc':0.0,'γE':γE,'γIm':γIm,'γI':γI,'γH':γH,'γHc':γHc,'μb':3.57e-5,'μd':1.57e-5,'φEI':0.5,'φIR':0.6,'φHR':0.6,'φD':0.2,}Now we can train the parameters inxusing a variety of methods. Currently implemented are thescipy.optimize.minimizemethods, and thescipy.optimize.dual_annealing,scipy.optimize.least_squares, andskopt.*_minimizemethods. It is recommended to usefast=True(which doesn't use Runge-Kutta 4 and is this ~4 times faster) for all but the last optimization.options={'bayesian':{'n_calls':100,'n_random_starts':10,'fast':True,},'annealing':{'seed':1234567,'fast':True,},'L-BFGS-B':{'disp':True,},}formethodin['annealing','L-BFGS-B']:opt={}ifmethodinoptions:opt=options[method]x=epidemic.optimize(x,x_bounds,x_params,method=method,**opt)epidemic.plot(cols=['I_cases','I'])epidemic.plot(cols=['D'])Seeexample_train.py.Loading dataIn order to optimize our simulation, we need to pass training data to the model. TheEpidemicclass accepts adataargument of that should be a DataFrame, where its columns correspond to the compartments or semi-compartments of the model. For instance,I, orDare valid compartments, but also derived compartments such as the cumulativeI_casesorH_cases.df_infectados=pd.read_csv('data/chile_minsal_infectados.csv',sep=';',index_col=0)df_infectados=df_infectados.transpose()df_infectados.index=pd.to_datetime(df_infectados.index,format='%d-%b')+pd.offsets.DateOffset(years=120)df_fallecidos=pd.read_csv('data/chile_minsal_fallecidos.csv',sep=';',index_col=0)df_fallecidos=df_fallecidos.transpose()df_fallecidos.index=pd.to_datetime(df_fallecidos.index,format='%d-%b')+pd.offsets.DateOffset(years=120)data=pd.DataFrame({'I_cases':df_infectados['Región Metropolitana'],'D':df_fallecidos['Región Metropolitana'],})epidemic=Epidemic(model,t0,tmax,data=data)Change simulation time rangeGiven an Epidemic, we can extend the simulation time range. This will run all simulations for the new time range.epidemic.extend(np.datetime64('2021-06-01'))Add confidence intervalsWhen callingrunon anEpidemic, we can pass thetagargument. If this is anything butNone, empty, ormean, we will assume this is an extra curve that will be saved. If the tag name islowerorupper, it will serve as the lower and upper bounds respectively for the confidence intervals while plotting.epidemic.run(y0_lower,params_lower,tag='lower')epidemic.run(y0_upper,params_upper,tag='upper')# orepidemic.run(*x_params(*x_lower),tag='lower')epidemic.run(*x_params(*x_upper),tag='upper')Visualization and parameter analysisPlotting data columnsepidemic.plot(cols=['I','H','Hc','D'])Plotting derived parametersepidemic.plot_params(cols=['Reff'])Printing parametersDisplay the model parameters and their values.epidemic.print_params()ParameterValueβE0.02293βIm0.06112βI0.1891βH0βHc0γE0.186γIm0.1376γI0.1116γH0.14γHc0.0626μb3.57e-05μd1.57e-05φEI0.5φIR0.6006φHR0.6049φD0.2Printing training parametersDisplay the training parameters and their values and ranges.epidemic.print_x_params(x,x_bounds,x_params)ParameterValueRangeE00.8514[0, 20]Im02.526[0, 20]CE0.1213[0, 0.4]CIm0.3233[0, 0.9]βI0.2521[0, 0.75]γE0.186[0.17, 0.25]γIm0.1376[0.07, 0.14]γI0.1116[0.07, 0.14]γH0.14[0.07, 0.14]γHc0.0626[0.0625, 0.14]Printing statisticsPrint relevant statistics about the simulation.epidemic.print_stats()ParameterValueDateR effective1.762020-03-15R effective1.412020-05-01Fatality0.022020-05-01max(I)30112020-05-01max(H)19742020-05-01max(Hc)11392020-05-01max(D)1732020-05-01Parameter sensitivityProbing parameter sensitivity to the error. Each parameter is moved a small distance epsilon and evaluated to see how much it impacts the error. Higher values means these parameter are very sensitive. When zero it means it has no impact on the error and is thus independent.Each column shows the impact of the error on that data series, while * is the total model error.epidemic.param_sensitivity()*DI_casesE00.00259520.002678470.00253102Im00.0003538520.0003191610.00036079CE0.006349050.001401650.0113138CIm0.005433270.002706230.00813083βI0.02715940.008951370.0453727γE0.01101890.01616750.00588935γIm0.01156010.001235120.0219025γI0.008507040.06151990.0445023γH0.00871520.01745820γHc0.007903360.01579280