├── README.md └── compartmental_models ├── SEIR Simulator in Python.ipynb ├── SEIR Simulator with Parameter Estimation in Python.ipynb ├── SEIRD Simulator in Python.ipynb ├── SEIRD Simulator with Parameter Estimation in Python.ipynb └── images ├── notations.png ├── seir_de_eqns.png ├── seir_model.png ├── seir_simulation.png ├── seird_de_eqns.png ├── seird_model.png ├── seird_simulation.png └── seird_simulator.png /README.md: -------------------------------------------------------------------------------- 1 | # Simulators for different purposes 2 | 3 | The repo currently has the following models implemented in jupyter notebooks: 4 | * [Compartmental models](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology "Compartmental models in epidemiology") namely [SEIR](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIR%20Simulator%20in%20Python.ipynb "SEIR Simulator in Python") and [SEIRD](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIRD%20Simulator%20in%20Python.ipynb "SEIRD Simulator in Python") which are elaborations of the basic SIR model. These models are used in epidemiology to predict the spread of a disease. E.g. variations of these basic models are being used in prediction of spread of COVID-19. 5 | * Estimation of [SEIR](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIR%20Simulator%20with%20Parameter%20Estimation%20in%20Python.ipynb "SEIR Simulator with Parameter Estimation in Python") and [SEIRD](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIRD%20Simulator%20with%20Parameter%20Estimation%20in%20Python.ipynb "SEIRD Simulator with Parameter Estimation in Python") model parameters using nonlinear least squares. 6 | 7 | The simulator is built using IPython Widgets. Here is how SEIRD simulator looks like,
8 | 9 |
10 | The simulator doesn't render in Jupyter notebooks hosted on GitHub but on your local machine, you will be able to run the notebook and use the simulator for various parameter values of choice. 11 |
12 | ## Compartmental Models 13 | 14 | ### SEIR Model 15 | 16 | 17 | ### SEIRD Model 18 | 19 | 20 | More details on the models are provided in the corresponding notebooks. 21 | 22 | ## Organization 23 | 24 | **compartmental_models** 25 | - [SEIR](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIR%20Simulator%20in%20Python.ipynb "SEIR Simulator in Python") 26 | - [SEIR with Parameter Estimation](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIR%20Simulator%20with%20Parameter%20Estimation%20in%20Python.ipynb "SEIR Simulator with Parameter Estimation in Python") 27 | - [SEIRD](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIRD%20Simulator%20in%20Python.ipynb "SEIRD Simulator in Python") 28 | - [SEIRD with Parameter Estimation](https://github.com/silpara/simulators/blob/master/compartmental_models/SEIRD%20Simulator%20with%20Parameter%20Estimation%20in%20Python.ipynb "SEIRD Simulator with Parameter Estimation in Python") 29 | 30 | 31 | -------------------------------------------------------------------------------- /compartmental_models/SEIR Simulator in Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Simulation of SEIR Model\n", 8 | "\n", 9 | "SEIR is a type of compartmental models which are used in modelling of infectious disease using differential equations. These types of models divide the population into groups or compartments and the dynamics of these groups are expressed with the help of a system of differential equations.\n", 10 | "\n", 11 | "These system of equations are parametrized which capture the mechanistic nature of the disease. For simulation, you select values of these parameters and the resulting curves simulate the behaviour by solving the set of equations. Finally the results are plotted in a graph to visually understand the effect of the parameters.\n", 12 | "\n", 13 | "## SEIR Model\n", 14 | "\n", 15 | "For completeness the SEIR model is produced below:\n", 16 | "\n", 17 | "\n", 18 | "\n", 19 | "$\\displaystyle \\frac{dS}{dt} = -\\frac{\\beta S I}{N}$

\n", 20 | "$\\displaystyle \\frac{dE}{dt} = \\frac{\\beta S I}{N} - \\sigma E$

\n", 21 | "$\\displaystyle \\frac{dI}{dt} = \\sigma E - \\gamma I$

\n", 22 | "$\\displaystyle \\frac{dR}{dt} = \\gamma I$

\n", 23 | "$N = S + E + I + R$

\n", 24 | "Where,

\n", 25 | "$\\beta$ is infection rate or the rate of spread

\n", 26 | "$\\sigma$ is the incubation rate or the rate of latent individuals becoming infectious (average duration of incubation is $1/\\sigma$)

\n", 27 | "$\\gamma$ is the recovery rate or mortality rate. If the duration of indection is D then $\\gamma$ = 1/D\n", 28 | "\n", 29 | "A supplementary post to this notebook can be read at [Simulating Compartmental Models in Epidemiology using Python & Jupyter Widgets](https://dilbertai.com/2020/04/12/simulating-compartmental-models-in-epidemiology-using-python-jupyter-widgets/)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 22, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "import os\n", 39 | "import sys\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "import numpy as np\n", 42 | "import pandas as pd\n", 43 | "from scipy.integrate import odeint\n", 44 | "import plotly.graph_objects as go\n", 45 | "import plotly.io as pio\n", 46 | "pio.renderers.default = \"notebook\"\n", 47 | "%matplotlib inline\n", 48 | "plt.style.use('ggplot')" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 23, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "# Jupyter Specifics\n", 58 | "from IPython.display import HTML\n", 59 | "from ipywidgets.widgets import interact, IntSlider, FloatSlider, Layout\n", 60 | "\n", 61 | "style = {'description_width': '100px'}\n", 62 | "slider_layout = Layout(width='99%')" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 24, 68 | "metadata": {}, 69 | "outputs": [], 70 | "source": [ 71 | "def ode_model(z, t, beta, sigma, gamma):\n", 72 | " \"\"\"\n", 73 | " Reference https://www.idmod.org/docs/hiv/model-seir.html\n", 74 | " \"\"\"\n", 75 | " S, E, I, R = z\n", 76 | " N = S + E + I + R\n", 77 | " dSdt = -beta*S*I/N\n", 78 | " dEdt = beta*S*I/N - sigma*E\n", 79 | " dIdt = sigma*E - gamma*I\n", 80 | " dRdt = gamma*I\n", 81 | " return [dSdt, dEdt, dIdt, dRdt]" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 25, 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "def ode_solver(t, initial_conditions, params):\n", 91 | " initE, initI, initR, initN = initial_conditions\n", 92 | " beta, sigma, gamma = params\n", 93 | " initS = initN - (initE + initI + initR)\n", 94 | " res = odeint(ode_model, [initS, initE, initI, initR], t, args=(beta, sigma, gamma))\n", 95 | " return res" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 26, 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "# ref: https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf\n", 105 | "initN = 1380000000\n", 106 | "# S0 = 966000000\n", 107 | "initE = 1\n", 108 | "initI = 1\n", 109 | "initR = 0\n", 110 | "sigma = 1/5.2\n", 111 | "gamma = 1/2.9\n", 112 | "R0 = 4\n", 113 | "beta = R0 * gamma\n", 114 | "days = 150" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 27, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "def main(initE, initI, initR, initN, beta, sigma, gamma, days):\n", 124 | " initial_conditions = [initE, initI, initR, initN]\n", 125 | " params = [beta, sigma, gamma]\n", 126 | " tspan = np.arange(0, days, 1)\n", 127 | " sol = ode_solver(tspan, initial_conditions, params)\n", 128 | " S, E, I, R = sol[:, 0], sol[:, 1], sol[:, 2], sol[:, 3]\n", 129 | " \n", 130 | " # Create traces\n", 131 | " fig = go.Figure()\n", 132 | " fig.add_trace(go.Scatter(x=tspan, y=S, mode='lines+markers', name='Susceptible'))\n", 133 | " fig.add_trace(go.Scatter(x=tspan, y=E, mode='lines+markers', name='Exposed'))\n", 134 | " fig.add_trace(go.Scatter(x=tspan, y=I, mode='lines+markers', name='Infected'))\n", 135 | " fig.add_trace(go.Scatter(x=tspan, y=R, mode='lines+markers',name='Recovered'))\n", 136 | " \n", 137 | " if days <= 30:\n", 138 | " step = 1\n", 139 | " elif days <= 90:\n", 140 | " step = 7\n", 141 | " else:\n", 142 | " step = 30\n", 143 | " \n", 144 | " # Edit the layout\n", 145 | " fig.update_layout(title='Simulation of SEIR Model',\n", 146 | " xaxis_title='Day',\n", 147 | " yaxis_title='Counts',\n", 148 | " title_x=0.5,\n", 149 | " width=900, height=600\n", 150 | " )\n", 151 | " fig.update_xaxes(tickangle=-90, tickformat = None, tickmode='array', tickvals=np.arange(0, days + 1, step))\n", 152 | " if not os.path.exists(\"images\"):\n", 153 | " os.mkdir(\"images\")\n", 154 | " fig.write_image(\"images/seir_simulation.png\")\n", 155 | " fig.show()" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 28, 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "data": { 165 | "application/vnd.jupyter.widget-view+json": { 166 | "model_id": "9035b90a6cea4fcbbd42244fb0a09f80", 167 | "version_major": 2, 168 | "version_minor": 0 169 | }, 170 | "text/plain": [ 171 | "interactive(children=(IntSlider(value=1, description='initE', layout=Layout(width='99%'), max=100000, style=Sl…" 172 | ] 173 | }, 174 | "metadata": {}, 175 | "output_type": "display_data" 176 | } 177 | ], 178 | "source": [ 179 | "interact(main, initE=IntSlider(min=0, max=100000, step=1, value=initE, description='initE', style=style, layout=slider_layout),\n", 180 | " initI=IntSlider(min=0, max=100000, step=10, value=initI, description='initI', style=style, layout=slider_layout),\n", 181 | " initR=IntSlider(min=0, max=100000, step=10, value=initR, description='initR', style=style, layout=slider_layout),\n", 182 | " initN=IntSlider(min=0, max=1380000000, step=1000, value=initN, description='initN', style=style, layout=slider_layout),\n", 183 | " beta=FloatSlider(min=0, max=4, step=0.01, value=beta, description='Infection rate', style=style, layout=slider_layout),\n", 184 | " sigma=FloatSlider(min=0, max=4, step=0.01, value=sigma, description='Incubation rate', style=style, layout=slider_layout),\n", 185 | " gamma=FloatSlider(min=0, max=4, step=0.01, value=gamma, description='Recovery rate', style=style, layout=slider_layout),\n", 186 | " days=IntSlider(min=1, max=600, step=7, value=days, description='Days', style=style, layout=slider_layout)\n", 187 | " );" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "**References:**
\n", 195 | "1. SEIR and SEIRS Model https://www.idmod.org/docs/hiv/model-seir.html
\n", 196 | "2. Compartmental models in epidemiology https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SEIR_model
\n", 197 | "3. Solve Differential Equations in Python https://www.youtube.com/watch?v=VV3BnroVjZo
\n", 198 | "4. Computational Statistics in Python https://people.duke.edu/~ccc14/sta-663/CalibratingODEs.html
\n", 199 | "5. Ordinary Differential Equations (ODE) with Python and Jupyter https://elc.github.io/posts/ordinary-differential-equations-with-python/
\n", 200 | "6. SEIRS+ Model https://github.com/ryansmcgee/seirsplus
\n", 201 | "7. Stack Overflow https://stackoverflow.com/questions/40753159/why-is-scipy-minimize-ignoring-my-constraints
\n", 202 | "8. Lotka–Volterra equations https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations
\n", 203 | "9. SEIR and Regression Model based COVID-19 outbreak predictions in India https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf
\n", 204 | "\n", 205 | "A simulator built with RShiny which provides many more parameters https://alhill.shinyapps.io/COVID19seir/" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": null, 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": null, 218 | "metadata": {}, 219 | "outputs": [], 220 | "source": [] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "metadata": {}, 226 | "outputs": [], 227 | "source": [] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": {}, 233 | "outputs": [], 234 | "source": [] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.7.6" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 4 258 | } 259 | -------------------------------------------------------------------------------- /compartmental_models/SEIR Simulator with Parameter Estimation in Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Simulation & Parameter Estimation of SEIR Model\n", 8 | "\n", 9 | "SEIR is a type of compartmental models which are used in modelling of infectious disease using differential equations. These types of models divide the population into groups or compartments and the dynamics of these groups are expressed with the help of a system of differential equations.\n", 10 | "\n", 11 | "These system of equations are parametrized which capture the mechanistic nature of the disease. For simulation, you select values of these parameters and the resulting curves simulate the behaviour by solving the set of equations. Finally the results are plotted in a graph to visually understand the effect of the parameters.\n", 12 | "\n", 13 | "## SEIR Model\n", 14 | "\n", 15 | "For completeness the SEIR model is produced below:\n", 16 | "\n", 17 | "\n", 18 | "\n", 19 | "$\\displaystyle \\frac{dS}{dt} = -\\frac{\\beta S I}{N}$

\n", 20 | "$\\displaystyle \\frac{dE}{dt} = \\frac{\\beta S I}{N} - \\sigma E$

\n", 21 | "$\\displaystyle \\frac{dI}{dt} = \\sigma E - \\gamma I$

\n", 22 | "$\\displaystyle \\frac{dR}{dt} = \\gamma I$

\n", 23 | "$N = S + E + I + R$

\n", 24 | "Where,

\n", 25 | "$\\beta$ is infection rate or the rate of spread

\n", 26 | "$\\sigma$ is the incubation rate or the rate of latent individuals becoming infectious (average duration of incubation is $1/\\sigma$)

\n", 27 | "$\\gamma$ is the recovery rate or mortality rate. If the duration of indection is D then $\\gamma$ = 1/D\n", 28 | "\n", 29 | "#### A supplementary blogpost to this notebook can be found at [Estimating Parameters of Compartmental Models from Observed Data](https://dilbertai.com/2020/04/27/estimating-parameters-of-compartmental-models-from-observed-data/)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 1, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "import os\n", 39 | "import sys\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "import numpy as np\n", 42 | "import pandas as pd\n", 43 | "from scipy.integrate import odeint\n", 44 | "import plotly.graph_objects as go\n", 45 | "import plotly.io as pio\n", 46 | "import requests\n", 47 | "from lmfit import minimize, Parameters, Parameter, report_fit\n", 48 | "pio.renderers.default = \"notebook\"\n", 49 | "%matplotlib inline\n", 50 | "plt.style.use('ggplot')" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 2, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "# Jupyter Specifics\n", 60 | "from IPython.display import HTML\n", 61 | "from ipywidgets.widgets import interact, IntSlider, FloatSlider, Layout, ToggleButton\n", 62 | "\n", 63 | "style = {'description_width': '100px'}\n", 64 | "slider_layout = Layout(width='99%')" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 3, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "def ode_model(z, t, beta, sigma, gamma):\n", 74 | " \"\"\"\n", 75 | " Reference https://www.idmod.org/docs/hiv/model-seir.html\n", 76 | " \"\"\"\n", 77 | " S, E, I, R = z\n", 78 | " N = S + E + I + R\n", 79 | " dSdt = -beta*S*I/N\n", 80 | " dEdt = beta*S*I/N - sigma*E\n", 81 | " dIdt = sigma*E - gamma*I\n", 82 | " dRdt = gamma*I\n", 83 | " return [dSdt, dEdt, dIdt, dRdt]" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 4, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "def ode_solver(t, initial_conditions, params):\n", 93 | " initE, initI, initR, initN = initial_conditions\n", 94 | " beta, sigma, gamma = params['beta'].value, params['sigma'].value, params['gamma'].value\n", 95 | " initS = initN - (initE + initI + initR)\n", 96 | " res = odeint(ode_model, [initS, initE, initI, initR], t, args=(beta, sigma, gamma))\n", 97 | " return res" 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": 5, 103 | "metadata": {}, 104 | "outputs": [ 105 | { 106 | "name": "stdout", 107 | "output_type": "stream", 108 | "text": [ 109 | "Request Success? True\n" 110 | ] 111 | } 112 | ], 113 | "source": [ 114 | "response = requests.get('https://api.rootnet.in/covid19-in/stats/history')\n", 115 | "print('Request Success? {}'.format(response.status_code == 200))\n", 116 | "covid_history = response.json()['data']" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 6, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "keys = ['day', 'total', 'confirmedCasesIndian', 'confirmedCasesForeign', 'confirmedButLocationUnidentified',\n", 126 | " 'discharged', 'deaths']\n", 127 | "df_covid_history = pd.DataFrame([[d.get('day'), \n", 128 | " d['summary'].get('total'), \n", 129 | " d['summary'].get('confirmedCasesIndian'), \n", 130 | " d['summary'].get('confirmedCasesForeign'),\n", 131 | " d['summary'].get('confirmedButLocationUnidentified'),\n", 132 | " d['summary'].get('discharged'), \n", 133 | " d['summary'].get('deaths')] \n", 134 | " for d in covid_history],\n", 135 | " columns=keys)\n", 136 | "df_covid_history = df_covid_history.sort_values(by='day')\n", 137 | "df_covid_history['infected'] = df_covid_history['total'] - df_covid_history['discharged'] - df_covid_history['deaths']\n", 138 | "df_covid_history['total_recovered_or_dead'] = df_covid_history['discharged'] + df_covid_history['deaths']" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": 7, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [ 147 | "# ref: https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf\n", 148 | "initN = 1380000000\n", 149 | "# S0 = 966000000\n", 150 | "initE = 1000\n", 151 | "initI = 47\n", 152 | "initR = 0\n", 153 | "sigma = 1/5.2\n", 154 | "gamma = 1/2.9\n", 155 | "R0 = 4\n", 156 | "beta = R0 * gamma\n", 157 | "days = 112\n", 158 | "\n", 159 | "params = Parameters()\n", 160 | "params.add('beta', value=beta, min=0, max=10)\n", 161 | "params.add('sigma', value=sigma, min=0, max=10)\n", 162 | "params.add('gamma', value=gamma, min=0, max=10)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## Simulation" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 23, 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "def main(initE, initI, initR, initN, beta, sigma, gamma, days, param_fitting):\n", 179 | " initial_conditions = [initE, initI, initR, initN]\n", 180 | " params['beta'].value, params['sigma'].value,params['gamma'].value = [beta, sigma, gamma]\n", 181 | " tspan = np.arange(0, days, 1)\n", 182 | " sol = ode_solver(tspan, initial_conditions, params)\n", 183 | " S, E, I, R = sol[:, 0], sol[:, 1], sol[:, 2], sol[:, 3]\n", 184 | " \n", 185 | " # Create traces\n", 186 | " fig = go.Figure()\n", 187 | " if not param_fitting:\n", 188 | " fig.add_trace(go.Scatter(x=tspan, y=S, mode='lines+markers', name='Susceptible'))\n", 189 | " fig.add_trace(go.Scatter(x=tspan, y=E, mode='lines+markers', name='Exposed'))\n", 190 | " fig.add_trace(go.Scatter(x=tspan, y=I, mode='lines+markers', name='Infected'))\n", 191 | " fig.add_trace(go.Scatter(x=tspan, y=R, mode='lines+markers',name='Recovered'))\n", 192 | " if param_fitting:\n", 193 | " fig.add_trace(go.Scatter(x=tspan, y=df_covid_history.infected, mode='lines+markers',\\\n", 194 | " name='Infections Observed', line = dict(dash='dash')))\n", 195 | " fig.add_trace(go.Scatter(x=tspan, y=df_covid_history.total_recovered_or_dead, mode='lines+markers',\\\n", 196 | " name='Recovered/Deceased Observed', line = dict(dash='dash')))\n", 197 | " \n", 198 | " if days <= 30:\n", 199 | " step = 1\n", 200 | " elif days <= 90:\n", 201 | " step = 7\n", 202 | " else:\n", 203 | " step = 30\n", 204 | " \n", 205 | " # Edit the layout\n", 206 | " fig.update_layout(title='Simulation of SEIR Model',\n", 207 | " xaxis_title='Day',\n", 208 | " yaxis_title='Counts',\n", 209 | " title_x=0.5,\n", 210 | " width=900, height=600\n", 211 | " )\n", 212 | " fig.update_xaxes(tickangle=-90, tickformat = None, tickmode='array', tickvals=np.arange(0, days + 1, step))\n", 213 | " if not os.path.exists(\"images\"):\n", 214 | " os.mkdir(\"images\")\n", 215 | " fig.write_image(\"images/seir_simulation.png\")\n", 216 | " fig.show()" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": 24, 222 | "metadata": {}, 223 | "outputs": [ 224 | { 225 | "data": { 226 | "application/vnd.jupyter.widget-view+json": { 227 | "model_id": "a787662501c544789daa0c0b3a4b3c80", 228 | "version_major": 2, 229 | "version_minor": 0 230 | }, 231 | "text/plain": [ 232 | "interactive(children=(IntSlider(value=1000, description='initE', layout=Layout(width='99%'), max=100000, style…" 233 | ] 234 | }, 235 | "metadata": {}, 236 | "output_type": "display_data" 237 | } 238 | ], 239 | "source": [ 240 | "interact(main, initE=IntSlider(min=0, max=100000, step=1, value=initE, description='initE', style=style, \\\n", 241 | " layout=slider_layout),\n", 242 | " initI=IntSlider(min=0, max=100000, step=10, value=initI, description='initI', style=style, \\\n", 243 | " layout=slider_layout),\n", 244 | " initR=IntSlider(min=0, max=100000, step=10, value=initR, description='initR', style=style, \\\n", 245 | " layout=slider_layout),\n", 246 | " initN=IntSlider(min=0, max=1380000000, step=1000, value=initN, description='initN', style=style, \\\n", 247 | " layout=slider_layout),\n", 248 | " beta=FloatSlider(min=0, max=4, step=0.01, value=beta, description='Infection rate', style=style, \\\n", 249 | " layout=slider_layout),\n", 250 | " sigma=FloatSlider(min=0, max=4, step=0.01, value=sigma, description='Incubation rate', style=style, \\\n", 251 | " layout=slider_layout),\n", 252 | " gamma=FloatSlider(min=0, max=4, step=0.01, value=gamma, description='Recovery rate', style=style, \\\n", 253 | " layout=slider_layout),\n", 254 | " days=IntSlider(min=0, max=600, step=7, value=days, description='Days', style=style, \\\n", 255 | " layout=slider_layout),\n", 256 | " param_fitting=ToggleButton(value=False, description='Fitting Mode', disabled=False, button_style='', \\\n", 257 | " tooltip='Click to show fewer plots', icon='check-circle')\n", 258 | " );" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "## Parameter Estimation" 266 | ] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": 25, 271 | "metadata": {}, 272 | "outputs": [], 273 | "source": [ 274 | "def error(params, initial_conditions, tspan, data):\n", 275 | " sol = ode_solver(tspan, initial_conditions, params)\n", 276 | " return (sol[:, 2:4] - data).ravel()" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 26, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "initial_conditions = [initE, initI, initR, initN]\n", 286 | "beta = 1.08\n", 287 | "sigma = 0.02\n", 288 | "gamma = 0.02\n", 289 | "params['beta'].value = beta\n", 290 | "params['sigma'].value = sigma\n", 291 | "params['gamma'].value = gamma\n", 292 | "days = 45\n", 293 | "tspan = np.arange(0, days, 1)\n", 294 | "data = df_covid_history.loc[0:(days-1), ['infected', 'total_recovered_or_dead']].values" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": 27, 300 | "metadata": {}, 301 | "outputs": [ 302 | { 303 | "data": { 304 | "text/html": [ 305 | "
name value initial value min max vary
beta 1.08000000 1.3793103448275863 0.00000000 10.0000000 True
sigma 0.02000000 0.1923076923076923 0.00000000 10.0000000 True
gamma 0.02000000 0.3448275862068966 0.00000000 10.0000000 True
" 306 | ], 307 | "text/plain": [ 308 | "Parameters([('beta', ),\n", 309 | " ('sigma', ),\n", 310 | " ('gamma', )])" 311 | ] 312 | }, 313 | "execution_count": 27, 314 | "metadata": {}, 315 | "output_type": "execute_result" 316 | } 317 | ], 318 | "source": [ 319 | "params" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": 28, 325 | "metadata": {}, 326 | "outputs": [], 327 | "source": [ 328 | "# fit model and find predicted values\n", 329 | "result = minimize(error, params, args=(initial_conditions, tspan, data), method='leastsq')" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": 29, 335 | "metadata": {}, 336 | "outputs": [ 337 | { 338 | "data": { 339 | "text/html": [ 340 | "
name value standard error relative error initial value min max vary
beta 0.25240521 0.02426587 (9.61%) 1.08 0.00000000 10.0000000 True
sigma 0.07915451 0.00865739 (10.94%) 0.02 0.00000000 10.0000000 True
gamma 0.02201161 0.00129203 (5.87%) 0.02 0.00000000 10.0000000 True
" 341 | ], 342 | "text/plain": [ 343 | "Parameters([('beta',\n", 344 | " ),\n", 345 | " ('sigma',\n", 346 | " ),\n", 347 | " ('gamma',\n", 348 | " )])" 349 | ] 350 | }, 351 | "execution_count": 29, 352 | "metadata": {}, 353 | "output_type": "execute_result" 354 | } 355 | ], 356 | "source": [ 357 | "result.params" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 30, 363 | "metadata": {}, 364 | "outputs": [ 365 | { 366 | "name": "stdout", 367 | "output_type": "stream", 368 | "text": [ 369 | "[[Fit Statistics]]\n", 370 | " # fitting method = leastsq\n", 371 | " # function evals = 64\n", 372 | " # data points = 90\n", 373 | " # variables = 3\n", 374 | " chi-square = 25843295.5\n", 375 | " reduced chi-square = 297049.373\n", 376 | " Akaike info crit = 1137.09769\n", 377 | " Bayesian info crit = 1144.59712\n", 378 | "[[Variables]]\n", 379 | " beta: 0.25240521 +/- 0.02426587 (9.61%) (init = 1.08)\n", 380 | " sigma: 0.07915451 +/- 0.00865739 (10.94%) (init = 0.02)\n", 381 | " gamma: 0.02201161 +/- 0.00129203 (5.87%) (init = 0.02)\n", 382 | "[[Correlations]] (unreported correlations are < 0.100)\n", 383 | " C(beta, sigma) = -0.991\n", 384 | " C(beta, gamma) = 0.437\n", 385 | " C(sigma, gamma) = -0.327\n" 386 | ] 387 | } 388 | ], 389 | "source": [ 390 | "# display fitted statistics\n", 391 | "report_fit(result)" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": 31, 397 | "metadata": {}, 398 | "outputs": [ 399 | { 400 | "data": { 401 | "text/html": [ 402 | "
\n", 403 | " \n", 404 | " \n", 405 | "
\n", 406 | " \n", 444 | "
" 445 | ] 446 | }, 447 | "metadata": {}, 448 | "output_type": "display_data" 449 | } 450 | ], 451 | "source": [ 452 | "final = data + result.residual.reshape(data.shape)\n", 453 | "fig = go.Figure()\n", 454 | "fig.add_trace(go.Scatter(x=tspan, y=data[:, 0], mode='markers', name='Observed Infections', line = dict(dash='dot')))\n", 455 | "fig.add_trace(go.Scatter(x=tspan, y=data[:, 1], mode='markers', name='Observed Recovered/Deceased', line = dict(dash='dot')))\n", 456 | "fig.add_trace(go.Scatter(x=tspan, y=final[:, 0], mode='lines+markers', name='Fitted Infections'))\n", 457 | "fig.add_trace(go.Scatter(x=tspan, y=final[:, 1], mode='lines+markers', name='Fitted Recovered/Deceased'))\n", 458 | "fig.update_layout(title='Observed vs Fitted',\n", 459 | " xaxis_title='Day',\n", 460 | " yaxis_title='Counts',\n", 461 | " title_x=0.5,\n", 462 | " width=1000, height=600\n", 463 | " )" 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": 32, 469 | "metadata": {}, 470 | "outputs": [ 471 | { 472 | "name": "stdout", 473 | "output_type": "stream", 474 | "text": [ 475 | "(48, 2)\n" 476 | ] 477 | } 478 | ], 479 | "source": [ 480 | "observed_IR = df_covid_history.loc[:, ['infected', 'total_recovered_or_dead']].values\n", 481 | "print(observed_IR.shape)" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": 33, 487 | "metadata": {}, 488 | "outputs": [], 489 | "source": [ 490 | "tspan_fit_pred = np.arange(0, observed_IR.shape[0], 1)\n", 491 | "params['beta'].value = result.params['beta'].value\n", 492 | "params['sigma'].value = result.params['sigma'].value\n", 493 | "params['gamma'].value = result.params['gamma'].value\n", 494 | "fitted_predicted = ode_solver(tspan_fit_pred, initial_conditions, params)" 495 | ] 496 | }, 497 | { 498 | "cell_type": "code", 499 | "execution_count": 34, 500 | "metadata": {}, 501 | "outputs": [ 502 | { 503 | "name": "stdout", 504 | "output_type": "stream", 505 | "text": [ 506 | "(48, 2)\n" 507 | ] 508 | } 509 | ], 510 | "source": [ 511 | "fitted_predicted_IR = fitted_predicted[:, 2:4]\n", 512 | "print(fitted_predicted_IR.shape)" 513 | ] 514 | }, 515 | { 516 | "cell_type": "code", 517 | "execution_count": 35, 518 | "metadata": {}, 519 | "outputs": [ 520 | { 521 | "name": "stdout", 522 | "output_type": "stream", 523 | "text": [ 524 | "Fitted MAE\n", 525 | "Infected: 587.1564520730407\n", 526 | "Recovered/Deceased: 220.23711453849532\n", 527 | "\n", 528 | "Fitted RMSE\n", 529 | "Infected: 702.6622222460597\n", 530 | "Recovered/Deceased: 283.8331485287845\n" 531 | ] 532 | } 533 | ], 534 | "source": [ 535 | "print(\"Fitted MAE\")\n", 536 | "print('Infected: ', np.mean(np.abs(fitted_predicted_IR[:days, 0] - observed_IR[:days, 0])))\n", 537 | "print('Recovered/Deceased: ', np.mean(np.abs(fitted_predicted_IR[:days, 1] - observed_IR[:days, 1])))\n", 538 | "\n", 539 | "print(\"\\nFitted RMSE\")\n", 540 | "print('Infected: ', np.sqrt(np.mean((fitted_predicted_IR[:days, 0] - observed_IR[:days, 0])**2)))\n", 541 | "print('Recovered/Deceased: ', np.sqrt(np.mean((fitted_predicted_IR[:days, 1] - observed_IR[:days, 1])**2)))" 542 | ] 543 | }, 544 | { 545 | "cell_type": "code", 546 | "execution_count": 36, 547 | "metadata": {}, 548 | "outputs": [ 549 | { 550 | "name": "stdout", 551 | "output_type": "stream", 552 | "text": [ 553 | "Predicted MAE\n", 554 | "Infected: 3500.8548519859637\n", 555 | "Recovered/Deceased: 894.9307531861135\n", 556 | "\n", 557 | "Predicted RMSE\n", 558 | "Infected: 3590.026376405217\n", 559 | "Recovered/Deceased: 901.1249714019484\n" 560 | ] 561 | } 562 | ], 563 | "source": [ 564 | "print(\"Predicted MAE\")\n", 565 | "print('Infected: ', np.mean(np.abs(fitted_predicted_IR[days:observed_IR.shape[0], 0] - observed_IR[days:, 0])))\n", 566 | "print('Recovered/Deceased: ', np.mean(np.abs(fitted_predicted_IR[days:observed_IR.shape[0], 1] - observed_IR[days:, 1])))\n", 567 | "\n", 568 | "print(\"\\nPredicted RMSE\")\n", 569 | "print('Infected: ', np.sqrt(np.mean((fitted_predicted_IR[days:observed_IR.shape[0], 0] - observed_IR[days:, 0])**2)))\n", 570 | "print('Recovered/Deceased: ', np.sqrt(np.mean((fitted_predicted_IR[days:observed_IR.shape[0], 1] - observed_IR[days:, 1])**2)))" 571 | ] 572 | }, 573 | { 574 | "cell_type": "code", 575 | "execution_count": 37, 576 | "metadata": {}, 577 | "outputs": [ 578 | { 579 | "data": { 580 | "application/vnd.jupyter.widget-view+json": { 581 | "model_id": "6c4e20b45f724cec829aa0bba02c0ca2", 582 | "version_major": 2, 583 | "version_minor": 0 584 | }, 585 | "text/plain": [ 586 | "interactive(children=(IntSlider(value=1000, description='initE', layout=Layout(width='99%'), max=100000, style…" 587 | ] 588 | }, 589 | "metadata": {}, 590 | "output_type": "display_data" 591 | } 592 | ], 593 | "source": [ 594 | "interact(main, initE=IntSlider(min=0, max=100000, step=1, value=initE, description='initE', style=style, \\\n", 595 | " layout=slider_layout),\n", 596 | " initI=IntSlider(min=0, max=100000, step=10, value=initI, description='initI', style=style, \\\n", 597 | " layout=slider_layout),\n", 598 | " initR=IntSlider(min=0, max=100000, step=10, value=initR, description='initR', style=style, \\\n", 599 | " layout=slider_layout),\n", 600 | " initN=IntSlider(min=0, max=1380000000, step=1000, value=initN, description='initN', style=style, \\\n", 601 | " layout=slider_layout),\n", 602 | " beta=FloatSlider(min=0, max=4, step=0.01, value=result.params['beta'].value, description='Infection rate', style=style, \\\n", 603 | " layout=slider_layout),\n", 604 | " sigma=FloatSlider(min=0, max=4, step=0.01, value=result.params['sigma'].value, description='Incubation rate', style=style, \\\n", 605 | " layout=slider_layout),\n", 606 | " gamma=FloatSlider(min=0, max=4, step=0.01, value=result.params['gamma'].value, description='Recovery rate', style=style, \\\n", 607 | " layout=slider_layout),\n", 608 | " days=IntSlider(min=0, max=600, step=7, value=240, description='Days', style=style, \\\n", 609 | " layout=slider_layout),\n", 610 | " param_fitting=ToggleButton(value=False, description='Fitting Mode', disabled=True, button_style='', \\\n", 611 | " tooltip='Click to show fewer plots', icon='check-circle')\n", 612 | " );" 613 | ] 614 | }, 615 | { 616 | "cell_type": "markdown", 617 | "metadata": {}, 618 | "source": [ 619 | "**References:**
\n", 620 | "1. SEIR and SEIRS Model https://www.idmod.org/docs/hiv/model-seir.html
\n", 621 | "2. Compartmental models in epidemiology https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SEIR_model
\n", 622 | "3. Solve Differential Equations in Python https://www.youtube.com/watch?v=VV3BnroVjZo
\n", 623 | "4. Computational Statistics in Python https://people.duke.edu/~ccc14/sta-663/CalibratingODEs.html
\n", 624 | "5. Ordinary Differential Equations (ODE) with Python and Jupyter https://elc.github.io/posts/ordinary-differential-equations-with-python/
\n", 625 | "6. SEIRS+ Model https://github.com/ryansmcgee/seirsplus
\n", 626 | "7. Stack Overflow https://stackoverflow.com/questions/40753159/why-is-scipy-minimize-ignoring-my-constraints
\n", 627 | "8. Lotka–Volterra equations https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations
\n", 628 | "9. SEIR and Regression Model based COVID-19 outbreak predictions in India https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf
\n", 629 | "\n", 630 | "A simulator built with RShiny which provides many more parameters https://alhill.shinyapps.io/COVID19seir/" 631 | ] 632 | }, 633 | { 634 | "cell_type": "code", 635 | "execution_count": null, 636 | "metadata": {}, 637 | "outputs": [], 638 | "source": [] 639 | } 640 | ], 641 | "metadata": { 642 | "kernelspec": { 643 | "display_name": "Python 3", 644 | "language": "python", 645 | "name": "python3" 646 | }, 647 | "language_info": { 648 | "codemirror_mode": { 649 | "name": "ipython", 650 | "version": 3 651 | }, 652 | "file_extension": ".py", 653 | "mimetype": "text/x-python", 654 | "name": "python", 655 | "nbconvert_exporter": "python", 656 | "pygments_lexer": "ipython3", 657 | "version": "3.7.6" 658 | } 659 | }, 660 | "nbformat": 4, 661 | "nbformat_minor": 4 662 | } 663 | -------------------------------------------------------------------------------- /compartmental_models/SEIRD Simulator in Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Simulation of SEIRD Model\n", 8 | "\n", 9 | "SEIRD is a type of compartmental models which are used in modelling of infectious disease using differential equations. These types of models divide the population into groups or compartments and the dynamics of these groups are expressed with the help of a system of differential equations.\n", 10 | "\n", 11 | "These system of equations are parametrized which capture the mechanistic nature of the disease. For simulation, you select values of these parameters and the resulting curves simulate the behaviour by solving the set of equations. Finally the results are plotted in a graph to visually understand the effect of the parameters.\n", 12 | "\n", 13 | "## SEIRD Model\n", 14 | "\n", 15 | "For completeness the SEIR model is produced below:\n", 16 | "\n", 17 | "\n", 18 | "\n", 19 | "$\\displaystyle \\frac{dS}{dt} = -\\frac{\\beta S I}{N}$

\n", 20 | "$\\displaystyle \\frac{dE}{dt} = \\frac{\\beta S I}{N} - \\sigma E$

\n", 21 | "$\\displaystyle \\frac{dI}{dt} = \\sigma E - \\gamma I - \\mu I$

\n", 22 | "$\\displaystyle \\frac{dR}{dt} = \\gamma I$

\n", 23 | "$\\displaystyle \\frac{dD}{dt} = \\mu I$

\n", 24 | "$N = S + E + I + R + D$

\n", 25 | "Where,

\n", 26 | "$\\beta$ is infection rate or the rate of spread

\n", 27 | "$\\sigma$ is the incubation rate or the rate of latent individuals becoming infectious (average duration of incubation is $1/\\sigma$)

\n", 28 | "$\\gamma$ is the recovery rate or mortality rate. If the duration of indection is D then $\\gamma$ = 1/D

\n", 29 | "$\\mu$ is the mortality rate due to the disease\n", 30 | "\n", 31 | "#### A supplementary post to this notebook can be read at [Simulating Compartmental Models in Epidemiology using Python & Jupyter Widgets](https://dilbertai.com/2020/04/12/simulating-compartmental-models-in-epidemiology-using-python-jupyter-widgets/)" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 1, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "import os\n", 41 | "import sys\n", 42 | "import matplotlib.pyplot as plt\n", 43 | "import numpy as np\n", 44 | "import pandas as pd\n", 45 | "from scipy.integrate import odeint\n", 46 | "import plotly.graph_objects as go\n", 47 | "import plotly.io as pio\n", 48 | "pio.renderers.default = \"notebook\"\n", 49 | "%matplotlib inline\n", 50 | "plt.style.use('ggplot')" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 3, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "# Jupyter Specifics\n", 60 | "from IPython.display import HTML\n", 61 | "from ipywidgets.widgets import interact, IntSlider, FloatSlider, Layout\n", 62 | "\n", 63 | "style = {'description_width': '100px'}\n", 64 | "slider_layout = Layout(width='99%')" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 4, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "def ode_model(z, t, beta, sigma, gamma, mu):\n", 74 | " \"\"\"\n", 75 | " Reference https://www.idmod.org/docs/hiv/model-seir.html\n", 76 | " \"\"\"\n", 77 | " S, E, I, R, D = z\n", 78 | " N = S + E + I + R + D\n", 79 | " dSdt = -beta*S*I/N\n", 80 | " dEdt = beta*S*I/N - sigma*E\n", 81 | " dIdt = sigma*E - gamma*I - mu*I\n", 82 | " dRdt = gamma*I\n", 83 | " dDdt = mu*I\n", 84 | " return [dSdt, dEdt, dIdt, dRdt, dDdt]" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 5, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "def ode_solver(t, initial_conditions, params):\n", 94 | " initE, initI, initR, initN, initD = initial_conditions\n", 95 | " beta, sigma, gamma, mu = params\n", 96 | " initS = initN - (initE + initI + initR + initD)\n", 97 | " res = odeint(ode_model, [initS, initE, initI, initR, initD], t, args=(beta, sigma, gamma, mu))\n", 98 | " return res" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 6, 104 | "metadata": {}, 105 | "outputs": [], 106 | "source": [ 107 | "# ref: https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf\n", 108 | "initN = 1380000000\n", 109 | "# S0 = 966000000\n", 110 | "initE = 1\n", 111 | "initI = 1\n", 112 | "initR = 0\n", 113 | "initD = 0\n", 114 | "sigma = 1/5.2\n", 115 | "gamma = 1/2.9\n", 116 | "mu = 0.034\n", 117 | "R0 = 4\n", 118 | "beta = R0 * gamma\n", 119 | "days = 180" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 29, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "def main(initE, initI, initR, initD, initN, beta, sigma, gamma, mu, days):\n", 129 | " initial_conditions = [initE, initI, initR, initN, initD]\n", 130 | " params = [beta, sigma, gamma, mu]\n", 131 | " tspan = np.arange(0, days, 1)\n", 132 | " sol = ode_solver(tspan, initial_conditions, params)\n", 133 | " S, E, I, R, D = sol[:, 0], sol[:, 1], sol[:, 2], sol[:, 3], sol[:, 4]\n", 134 | " \n", 135 | " # Create traces\n", 136 | " fig = go.Figure()\n", 137 | " fig.add_trace(go.Scatter(x=tspan, y=S, mode='lines+markers', name='Susceptible'))\n", 138 | " fig.add_trace(go.Scatter(x=tspan, y=E, mode='lines+markers', name='Exposed'))\n", 139 | " fig.add_trace(go.Scatter(x=tspan, y=I, mode='lines+markers', name='Infected'))\n", 140 | " fig.add_trace(go.Scatter(x=tspan, y=R, mode='lines+markers',name='Recovered'))\n", 141 | " fig.add_trace(go.Scatter(x=tspan, y=D, mode='lines+markers',name='Death'))\n", 142 | " \n", 143 | " if days <= 30:\n", 144 | " step = 1\n", 145 | " elif days <= 90:\n", 146 | " step = 7\n", 147 | " else:\n", 148 | " step = 30\n", 149 | " \n", 150 | " # Edit the layout\n", 151 | " fig.update_layout(title='Simulation of SEIRD Model',\n", 152 | " xaxis_title='Day',\n", 153 | " yaxis_title='Counts',\n", 154 | " title_x=0.5,\n", 155 | " width=900, height=600\n", 156 | " )\n", 157 | " fig.update_xaxes(tickangle=-90, tickformat = None, tickmode='array', tickvals=np.arange(0, days + 1, step))\n", 158 | " if not os.path.exists(\"images\"):\n", 159 | " os.mkdir(\"images\")\n", 160 | " fig.write_image(\"images/seird_simulation.png\")\n", 161 | " fig.show()" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 8, 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "data": { 171 | "application/vnd.jupyter.widget-view+json": { 172 | "model_id": "ed764ea271e643938faad7a3d9421b66", 173 | "version_major": 2, 174 | "version_minor": 0 175 | }, 176 | "text/plain": [ 177 | "interactive(children=(IntSlider(value=1, description='initE', layout=Layout(width='99%'), max=100000, style=Sl…" 178 | ] 179 | }, 180 | "metadata": {}, 181 | "output_type": "display_data" 182 | } 183 | ], 184 | "source": [ 185 | "interact(main, \n", 186 | " initE=IntSlider(min=0, max=100000, step=1, value=initE, description='initE', style=style, layout=slider_layout),\n", 187 | " initI=IntSlider(min=0, max=100000, step=10, value=initI, description='initI', style=style, layout=slider_layout),\n", 188 | " initR=IntSlider(min=0, max=100000, step=10, value=initR, description='initR', style=style, layout=slider_layout),\n", 189 | " initD=IntSlider(min=0, max=100000, step=10, value=initD, description='initD', style=style, layout=slider_layout),\n", 190 | " initN=IntSlider(min=0, max=1380000000, step=1000, value=initN, description='initN', style=style, layout=slider_layout),\n", 191 | " beta=FloatSlider(min=0, max=4, step=0.01, value=beta, description='Infection rate', style=style, layout=slider_layout),\n", 192 | " sigma=FloatSlider(min=0, max=4, step=0.01, value=sigma, description='Incubation rate', style=style, layout=slider_layout),\n", 193 | " gamma=FloatSlider(min=0, max=4, step=0.01, value=gamma, description='Recovery rate', style=style, layout=slider_layout),\n", 194 | " mu=FloatSlider(min=0, max=1, step=0.01, value=mu, description='Mortality rate', style=style, layout=slider_layout),\n", 195 | " days=IntSlider(min=1, max=600, step=7, value=days, description='Days', style=style, layout=slider_layout)\n", 196 | " );" 197 | ] 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "metadata": {}, 202 | "source": [ 203 | "**References:**
\n", 204 | "1. SEIR and SEIRS Model https://www.idmod.org/docs/hiv/model-seir.html
\n", 205 | "2. Compartmental models in epidemiology https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SEIR_model
\n", 206 | "3. Solve Differential Equations in Python https://www.youtube.com/watch?v=VV3BnroVjZo
\n", 207 | "4. Computational Statistics in Python https://people.duke.edu/~ccc14/sta-663/CalibratingODEs.html
\n", 208 | "5. Ordinary Differential Equations (ODE) with Python and Jupyter https://elc.github.io/posts/ordinary-differential-equations-with-python/
\n", 209 | "6. SEIRS+ Model https://github.com/ryansmcgee/seirsplus
\n", 210 | "7. Stack Overflow https://stackoverflow.com/questions/40753159/why-is-scipy-minimize-ignoring-my-constraints
\n", 211 | "8. Lotka–Volterra equations https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations
\n", 212 | "9. SEIR and Regression Model based COVID-19 outbreak predictions in India https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf
\n", 213 | "\n", 214 | "A simulator built with RShiny which provides many more parameters https://alhill.shinyapps.io/COVID19seir/" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": null, 220 | "metadata": {}, 221 | "outputs": [], 222 | "source": [] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": null, 227 | "metadata": {}, 228 | "outputs": [], 229 | "source": [] 230 | } 231 | ], 232 | "metadata": { 233 | "kernelspec": { 234 | "display_name": "Python 3", 235 | "language": "python", 236 | "name": "python3" 237 | }, 238 | "language_info": { 239 | "codemirror_mode": { 240 | "name": "ipython", 241 | "version": 3 242 | }, 243 | "file_extension": ".py", 244 | "mimetype": "text/x-python", 245 | "name": "python", 246 | "nbconvert_exporter": "python", 247 | "pygments_lexer": "ipython3", 248 | "version": "3.7.6" 249 | } 250 | }, 251 | "nbformat": 4, 252 | "nbformat_minor": 4 253 | } 254 | -------------------------------------------------------------------------------- /compartmental_models/SEIRD Simulator with Parameter Estimation in Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Simulation & Parameter Estimation of SEIRD Model\n", 8 | "\n", 9 | "SEIRD is a type of compartmental models which are used in modelling of infectious disease using differential equations. These types of models divide the population into groups or compartments and the dynamics of these groups are expressed with the help of a system of differential equations.\n", 10 | "\n", 11 | "These system of equations are parametrized which capture the mechanistic nature of the disease. For simulation, you select values of these parameters and the resulting curves simulate the behaviour by solving the set of equations. Finally the results are plotted in a graph to visually understand the effect of the parameters.\n", 12 | "\n", 13 | "## SEIRD Model\n", 14 | "\n", 15 | "For completeness the SEIR model is produced below:\n", 16 | "\n", 17 | "\n", 18 | "\n", 19 | "$\\displaystyle \\frac{dS}{dt} = -\\frac{\\beta S I}{N}$

\n", 20 | "$\\displaystyle \\frac{dE}{dt} = \\frac{\\beta S I}{N} - \\sigma E$

\n", 21 | "$\\displaystyle \\frac{dI}{dt} = \\sigma E - \\gamma I - \\mu I$

\n", 22 | "$\\displaystyle \\frac{dR}{dt} = \\gamma I$

\n", 23 | "$\\displaystyle \\frac{dD}{dt} = \\mu I$

\n", 24 | "$N = S + E + I + R + D$

\n", 25 | "Where,

\n", 26 | "$\\beta$ is infection rate or the rate of spread

\n", 27 | "$\\sigma$ is the incubation rate or the rate of latent individuals becoming infectious (average duration of incubation is $1/\\sigma$)

\n", 28 | "$\\gamma$ is the recovery rate or mortality rate. If the duration of indection is D then $\\gamma$ = 1/D

\n", 29 | "$\\mu$ is the mortality rate due to the disease\n", 30 | "\n", 31 | "#### A supplementary blogpost to this notebook can be found at [Estimating Parameters of Compartmental Models from Observed Data](https://dilbertai.com/2020/04/27/estimating-parameters-of-compartmental-models-from-observed-data/)" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 1, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "import os\n", 41 | "import sys\n", 42 | "import matplotlib.pyplot as plt\n", 43 | "import numpy as np\n", 44 | "import pandas as pd\n", 45 | "from scipy.integrate import odeint\n", 46 | "import plotly.graph_objects as go\n", 47 | "import plotly.io as pio\n", 48 | "import requests\n", 49 | "from lmfit import minimize, Parameters, Parameter, report_fit\n", 50 | "pio.renderers.default = \"notebook\"\n", 51 | "%matplotlib inline\n", 52 | "plt.style.use('ggplot')" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 2, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "# Jupyter Specifics\n", 62 | "from IPython.display import HTML\n", 63 | "from ipywidgets.widgets import interact, IntSlider, FloatSlider, Layout, ToggleButton, ToggleButtons\n", 64 | "\n", 65 | "style = {'description_width': '100px'}\n", 66 | "slider_layout = Layout(width='99%')" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 3, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "def ode_model(z, t, beta, sigma, gamma, mu):\n", 76 | " \"\"\"\n", 77 | " Reference https://www.idmod.org/docs/hiv/model-seir.html\n", 78 | " \"\"\"\n", 79 | " S, E, I, R, D = z\n", 80 | " N = S + E + I + R + D\n", 81 | " dSdt = -beta*S*I/N\n", 82 | " dEdt = beta*S*I/N - sigma*E\n", 83 | " dIdt = sigma*E - gamma*I - mu*I\n", 84 | " dRdt = gamma*I\n", 85 | " dDdt = mu*I\n", 86 | " return [dSdt, dEdt, dIdt, dRdt, dDdt]" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 4, 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "def ode_solver(t, initial_conditions, params):\n", 96 | " initE, initI, initR, initN, initD = initial_conditions\n", 97 | " beta, sigma, gamma, mu = params['beta'].value, params['sigma'].value, params['gamma'].value, params['mu'].value\n", 98 | " initS = initN - (initE + initI + initR + initD)\n", 99 | " res = odeint(ode_model, [initS, initE, initI, initR, initD], t, args=(beta, sigma, gamma, mu))\n", 100 | " return res" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 5, 106 | "metadata": {}, 107 | "outputs": [ 108 | { 109 | "name": "stdout", 110 | "output_type": "stream", 111 | "text": [ 112 | "Request Success? True\n" 113 | ] 114 | } 115 | ], 116 | "source": [ 117 | "response = requests.get('https://api.rootnet.in/covid19-in/stats/history')\n", 118 | "print('Request Success? {}'.format(response.status_code == 200))\n", 119 | "covid_history = response.json()['data']" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 6, 125 | "metadata": {}, 126 | "outputs": [], 127 | "source": [ 128 | "keys = ['day', 'total', 'confirmedCasesIndian', 'confirmedCasesForeign', 'confirmedButLocationUnidentified',\n", 129 | " 'discharged', 'deaths']\n", 130 | "df_covid_history = pd.DataFrame([[d.get('day'), \n", 131 | " d['summary'].get('total'), \n", 132 | " d['summary'].get('confirmedCasesIndian'), \n", 133 | " d['summary'].get('confirmedCasesForeign'),\n", 134 | " d['summary'].get('confirmedButLocationUnidentified'),\n", 135 | " d['summary'].get('discharged'), \n", 136 | " d['summary'].get('deaths')] \n", 137 | " for d in covid_history],\n", 138 | " columns=keys)\n", 139 | "df_covid_history = df_covid_history.sort_values(by='day')\n", 140 | "df_covid_history['infected'] = df_covid_history['total'] - df_covid_history['discharged'] - df_covid_history['deaths']\n", 141 | "df_covid_history['total_recovered_or_dead'] = df_covid_history['discharged'] + df_covid_history['deaths']" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 41, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "data": { 151 | "text/html": [ 152 | "
\n", 153 | "\n", 166 | "\n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | "
daytotalconfirmedCasesIndianconfirmedCasesForeignconfirmedButLocationUnidentifieddischargeddeathsinfectedtotal_recovered_or_dead
02020-03-10473116000470
12020-03-11604416000600
22020-03-12735617000730
32020-03-1382651701027012
42020-03-1484671701027212
\n", 244 | "
" 245 | ], 246 | "text/plain": [ 247 | " day total confirmedCasesIndian confirmedCasesForeign \\\n", 248 | "0 2020-03-10 47 31 16 \n", 249 | "1 2020-03-11 60 44 16 \n", 250 | "2 2020-03-12 73 56 17 \n", 251 | "3 2020-03-13 82 65 17 \n", 252 | "4 2020-03-14 84 67 17 \n", 253 | "\n", 254 | " confirmedButLocationUnidentified discharged deaths infected \\\n", 255 | "0 0 0 0 47 \n", 256 | "1 0 0 0 60 \n", 257 | "2 0 0 0 73 \n", 258 | "3 0 10 2 70 \n", 259 | "4 0 10 2 72 \n", 260 | "\n", 261 | " total_recovered_or_dead \n", 262 | "0 0 \n", 263 | "1 0 \n", 264 | "2 0 \n", 265 | "3 12 \n", 266 | "4 12 " 267 | ] 268 | }, 269 | "execution_count": 41, 270 | "metadata": {}, 271 | "output_type": "execute_result" 272 | } 273 | ], 274 | "source": [ 275 | "df_covid_history.head()" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": 7, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [ 284 | "# ref: https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf\n", 285 | "initN = 1380000000\n", 286 | "# S0 = 966000000\n", 287 | "initE = 1000\n", 288 | "initI = 47\n", 289 | "initR = 0\n", 290 | "initD = 0\n", 291 | "sigma = 1/5.2\n", 292 | "gamma = 1/2.9\n", 293 | "mu = 0.034\n", 294 | "R0 = 4\n", 295 | "beta = R0 * gamma\n", 296 | "days = 112\n", 297 | "\n", 298 | "params = Parameters()\n", 299 | "params.add('beta', value=beta, min=0, max=10)\n", 300 | "params.add('sigma', value=sigma, min=0, max=10)\n", 301 | "params.add('gamma', value=gamma, min=0, max=10)\n", 302 | "params.add('mu', value=mu, min=0, max=10)" 303 | ] 304 | }, 305 | { 306 | "cell_type": "markdown", 307 | "metadata": {}, 308 | "source": [ 309 | "## Simulation" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 8, 315 | "metadata": {}, 316 | "outputs": [], 317 | "source": [ 318 | "def main(initE, initI, initR, initD, initN, beta, sigma, gamma, mu, days, param_fitting):\n", 319 | " initial_conditions = [initE, initI, initR, initN, initD]\n", 320 | " params['beta'].value, params['sigma'].value,params['gamma'].value, params['mu'].value = [beta, sigma, gamma, mu]\n", 321 | " tspan = np.arange(0, days, 1)\n", 322 | " sol = ode_solver(tspan, initial_conditions, params)\n", 323 | " S, E, I, R, D = sol[:, 0], sol[:, 1], sol[:, 2], sol[:, 3], sol[:, 4]\n", 324 | " \n", 325 | " # Create traces\n", 326 | " fig = go.Figure()\n", 327 | " if not param_fitting:\n", 328 | " fig.add_trace(go.Scatter(x=tspan, y=S, mode='lines+markers', name='Susceptible'))\n", 329 | " fig.add_trace(go.Scatter(x=tspan, y=E, mode='lines+markers', name='Exposed'))\n", 330 | " fig.add_trace(go.Scatter(x=tspan, y=I, mode='lines+markers', name='Infected'))\n", 331 | " fig.add_trace(go.Scatter(x=tspan, y=R, mode='lines+markers',name='Recovered'))\n", 332 | " fig.add_trace(go.Scatter(x=tspan, y=D, mode='lines+markers',name='Death'))\n", 333 | " if param_fitting:\n", 334 | " fig.add_trace(go.Scatter(x=tspan, y=df_covid_history.infected, mode='lines+markers',\\\n", 335 | " name='Infections Observed', line = dict(dash='dash')))\n", 336 | " fig.add_trace(go.Scatter(x=tspan, y=df_covid_history.discharged, mode='lines+markers',\\\n", 337 | " name='Recovered Observed', line = dict(dash='dash')))\n", 338 | " fig.add_trace(go.Scatter(x=tspan, y=df_covid_history.deaths, mode='lines+markers',\\\n", 339 | " name='Deaths Observed', line = dict(dash='dash')))\n", 340 | " \n", 341 | " if days <= 30:\n", 342 | " step = 1\n", 343 | " elif days <= 90:\n", 344 | " step = 7\n", 345 | " else:\n", 346 | " step = 30\n", 347 | " \n", 348 | " # Edit the layout\n", 349 | " fig.update_layout(title='Simulation of SEIRD Model',\n", 350 | " xaxis_title='Day',\n", 351 | " yaxis_title='Counts',\n", 352 | " title_x=0.5,\n", 353 | " width=900, height=600\n", 354 | " )\n", 355 | " fig.update_xaxes(tickangle=-90, tickformat = None, tickmode='array', tickvals=np.arange(0, days + 1, step))\n", 356 | " if not os.path.exists(\"images\"):\n", 357 | " os.mkdir(\"images\")\n", 358 | " fig.write_image(\"images/seird_simulation.png\")\n", 359 | " fig.show()" 360 | ] 361 | }, 362 | { 363 | "cell_type": "code", 364 | "execution_count": 9, 365 | "metadata": { 366 | "scrolled": false 367 | }, 368 | "outputs": [ 369 | { 370 | "data": { 371 | "application/vnd.jupyter.widget-view+json": { 372 | "model_id": "37f85c2d12ab4759938835c16d282fda", 373 | "version_major": 2, 374 | "version_minor": 0 375 | }, 376 | "text/plain": [ 377 | "interactive(children=(IntSlider(value=1000, description='initE', layout=Layout(width='99%'), max=100000, style…" 378 | ] 379 | }, 380 | "metadata": {}, 381 | "output_type": "display_data" 382 | } 383 | ], 384 | "source": [ 385 | "interact(main, \n", 386 | " initE=IntSlider(min=0, max=100000, step=1, value=initE, description='initE', style=style, layout=slider_layout),\n", 387 | " initI=IntSlider(min=0, max=100000, step=10, value=initI, description='initI', style=style, layout=slider_layout),\n", 388 | " initR=IntSlider(min=0, max=100000, step=10, value=initR, description='initR', style=style, layout=slider_layout),\n", 389 | " initD=IntSlider(min=0, max=100000, step=10, value=initD, description='initD', style=style, layout=slider_layout),\n", 390 | " initN=IntSlider(min=0, max=1380000000, step=1000, value=initN, description='initN', style=style, layout=slider_layout),\n", 391 | " beta=FloatSlider(min=0, max=4, step=0.01, value=beta, description='Infection rate', style=style, layout=slider_layout),\n", 392 | " sigma=FloatSlider(min=0, max=4, step=0.01, value=sigma, description='Incubation rate', style=style, layout=slider_layout),\n", 393 | " gamma=FloatSlider(min=0, max=4, step=0.01, value=gamma, description='Recovery rate', style=style, layout=slider_layout),\n", 394 | " mu=FloatSlider(min=0, max=1, step=0.001, value=mu, description='Mortality rate', style=style, layout=slider_layout),\n", 395 | " days=IntSlider(min=0, max=600, step=7, value=days, description='Days', style=style, layout=slider_layout),\n", 396 | " param_fitting=ToggleButton(value=False, description='Fitting Mode', disabled=False, button_style='', \\\n", 397 | " tooltip='Click to show fewer plots', icon='check-circle')\n", 398 | " );" 399 | ] 400 | }, 401 | { 402 | "cell_type": "markdown", 403 | "metadata": {}, 404 | "source": [ 405 | "## Parameter Estimation" 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": 24, 411 | "metadata": {}, 412 | "outputs": [], 413 | "source": [ 414 | "def error(params, initial_conditions, tspan, data):\n", 415 | " sol = ode_solver(tspan, initial_conditions, params)\n", 416 | " return (sol[:, 2:5] - data).ravel()" 417 | ] 418 | }, 419 | { 420 | "cell_type": "code", 421 | "execution_count": 25, 422 | "metadata": {}, 423 | "outputs": [], 424 | "source": [ 425 | "initial_conditions = [initE, initI, initR, initN, initD]\n", 426 | "beta = 1.14\n", 427 | "sigma = 0.02\n", 428 | "gamma = 0.02\n", 429 | "mu = 0.01\n", 430 | "params['beta'].value = beta\n", 431 | "params['sigma'].value = sigma\n", 432 | "params['gamma'].value = gamma\n", 433 | "params['mu'].value = mu\n", 434 | "days = 45\n", 435 | "tspan = np.arange(0, days, 1)\n", 436 | "data = df_covid_history.loc[0:(days-1), ['infected', 'discharged', 'deaths']].values" 437 | ] 438 | }, 439 | { 440 | "cell_type": "code", 441 | "execution_count": 26, 442 | "metadata": {}, 443 | "outputs": [ 444 | { 445 | "data": { 446 | "text/plain": [ 447 | "(45, 3)" 448 | ] 449 | }, 450 | "execution_count": 26, 451 | "metadata": {}, 452 | "output_type": "execute_result" 453 | } 454 | ], 455 | "source": [ 456 | "data.shape" 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": 27, 462 | "metadata": {}, 463 | "outputs": [ 464 | { 465 | "data": { 466 | "text/html": [ 467 | "
name value initial value min max vary
beta 1.14000000 1.3793103448275863 0.00000000 10.0000000 True
sigma 0.02000000 0.1923076923076923 0.00000000 10.0000000 True
gamma 0.02000000 0.3448275862068966 0.00000000 10.0000000 True
mu 0.01000000 0.034 0.00000000 10.0000000 True
" 468 | ], 469 | "text/plain": [ 470 | "Parameters([('beta', ),\n", 471 | " ('sigma', ),\n", 472 | " ('gamma', ),\n", 473 | " ('mu', )])" 474 | ] 475 | }, 476 | "execution_count": 27, 477 | "metadata": {}, 478 | "output_type": "execute_result" 479 | } 480 | ], 481 | "source": [ 482 | "params" 483 | ] 484 | }, 485 | { 486 | "cell_type": "code", 487 | "execution_count": 28, 488 | "metadata": {}, 489 | "outputs": [], 490 | "source": [ 491 | "# fit model and find predicted values\n", 492 | "result = minimize(error, params, args=(initial_conditions, tspan, data), method='leastsq')\n", 493 | "# result = minimize(error, params, args=(initial_conditions, tspan, data), method='leastsq', \\\n", 494 | "# **{'xtol':1.e-15, 'ftol':1.e-15})" 495 | ] 496 | }, 497 | { 498 | "cell_type": "code", 499 | "execution_count": 29, 500 | "metadata": {}, 501 | "outputs": [ 502 | { 503 | "data": { 504 | "text/html": [ 505 | "
name value standard error relative error initial value min max vary
beta 0.25012193 0.01967139 (7.86%) 1.14 0.00000000 10.0000000 True
sigma 0.07995170 0.00717541 (8.97%) 0.02 0.00000000 10.0000000 True
gamma 0.01816017 0.00102423 (5.64%) 0.02 0.00000000 10.0000000 True
mu 0.00378277 9.6475e-04 (25.50%) 0.01 0.00000000 10.0000000 True
" 506 | ], 507 | "text/plain": [ 508 | "Parameters([('beta',\n", 509 | " ),\n", 510 | " ('sigma',\n", 511 | " ),\n", 512 | " ('gamma',\n", 513 | " ),\n", 514 | " ('mu',\n", 515 | " )])" 516 | ] 517 | }, 518 | "execution_count": 29, 519 | "metadata": {}, 520 | "output_type": "execute_result" 521 | } 522 | ], 523 | "source": [ 524 | "result.params" 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": 30, 530 | "metadata": {}, 531 | "outputs": [ 532 | { 533 | "name": "stdout", 534 | "output_type": "stream", 535 | "text": [ 536 | "[[Fit Statistics]]\n", 537 | " # fitting method = leastsq\n", 538 | " # function evals = 67\n", 539 | " # data points = 135\n", 540 | " # variables = 4\n", 541 | " chi-square = 25872760.4\n", 542 | " reduced chi-square = 197501.988\n", 543 | " Akaike info crit = 1650.06257\n", 544 | " Bayesian info crit = 1661.68367\n", 545 | "[[Variables]]\n", 546 | " beta: 0.25012193 +/- 0.01967139 (7.86%) (init = 1.14)\n", 547 | " sigma: 0.07995170 +/- 0.00717541 (8.97%) (init = 0.02)\n", 548 | " gamma: 0.01816017 +/- 0.00102423 (5.64%) (init = 0.02)\n", 549 | " mu: 0.00378277 +/- 9.6475e-04 (25.50%) (init = 0.01)\n", 550 | "[[Correlations]] (unreported correlations are < 0.100)\n", 551 | " C(beta, sigma) = -0.983\n", 552 | " C(beta, gamma) = 0.383\n", 553 | " C(sigma, gamma) = -0.270\n", 554 | " C(beta, mu) = 0.143\n" 555 | ] 556 | } 557 | ], 558 | "source": [ 559 | "# display fitted statistics\n", 560 | "report_fit(result)" 561 | ] 562 | }, 563 | { 564 | "cell_type": "code", 565 | "execution_count": 43, 566 | "metadata": {}, 567 | "outputs": [ 568 | { 569 | "data": { 570 | "text/html": [ 571 | "
\n", 572 | " \n", 573 | " \n", 574 | "
\n", 575 | " \n", 613 | "
" 614 | ] 615 | }, 616 | "metadata": {}, 617 | "output_type": "display_data" 618 | } 619 | ], 620 | "source": [ 621 | "final = data + result.residual.reshape(data.shape)\n", 622 | "\n", 623 | "fig = go.Figure()\n", 624 | "fig.add_trace(go.Scatter(x=tspan, y=data[:, 0], mode='markers', name='Observed Infections', line = dict(dash='dot')))\n", 625 | "fig.add_trace(go.Scatter(x=tspan, y=data[:, 1], mode='markers', name='Observed Recovered', line = dict(dash='dot')))\n", 626 | "fig.add_trace(go.Scatter(x=tspan, y=data[:, 2], mode='markers', name='Observed Deaths', line = dict(dash='dot')))\n", 627 | "fig.add_trace(go.Scatter(x=tspan, y=final[:, 0], mode='lines+markers', name='Fitted Infections'))\n", 628 | "fig.add_trace(go.Scatter(x=tspan, y=final[:, 1], mode='lines+markers', name='Fitted Recovered'))\n", 629 | "fig.add_trace(go.Scatter(x=tspan, y=final[:, 2], mode='lines+markers', name='Fitted Deaths'))\n", 630 | "fig.update_layout(title='SEIRD: Observed vs Fitted',\n", 631 | " xaxis_title='Day',\n", 632 | " yaxis_title='Counts',\n", 633 | " title_x=0.5,\n", 634 | " width=1000, height=600\n", 635 | " )" 636 | ] 637 | }, 638 | { 639 | "cell_type": "code", 640 | "execution_count": 35, 641 | "metadata": {}, 642 | "outputs": [ 643 | { 644 | "name": "stdout", 645 | "output_type": "stream", 646 | "text": [ 647 | "(48, 3)\n" 648 | ] 649 | } 650 | ], 651 | "source": [ 652 | "observed_IRD = df_covid_history.loc[:, ['infected', 'discharged', 'deaths']].values\n", 653 | "print(observed_IRD.shape)" 654 | ] 655 | }, 656 | { 657 | "cell_type": "code", 658 | "execution_count": 36, 659 | "metadata": {}, 660 | "outputs": [], 661 | "source": [ 662 | "tspan_fit_pred = np.arange(0, observed_IRD.shape[0], 1)\n", 663 | "params['beta'].value = result.params['beta'].value\n", 664 | "params['sigma'].value = result.params['sigma'].value\n", 665 | "params['gamma'].value = result.params['gamma'].value\n", 666 | "params['mu'].value = result.params['mu'].value\n", 667 | "fitted_predicted = ode_solver(tspan_fit_pred, initial_conditions, params)" 668 | ] 669 | }, 670 | { 671 | "cell_type": "code", 672 | "execution_count": 37, 673 | "metadata": {}, 674 | "outputs": [ 675 | { 676 | "name": "stdout", 677 | "output_type": "stream", 678 | "text": [ 679 | "(48, 3)\n" 680 | ] 681 | } 682 | ], 683 | "source": [ 684 | "fitted_predicted_IRD = fitted_predicted[:, 2:5]\n", 685 | "print(fitted_predicted_IRD.shape)" 686 | ] 687 | }, 688 | { 689 | "cell_type": "code", 690 | "execution_count": 38, 691 | "metadata": {}, 692 | "outputs": [ 693 | { 694 | "name": "stdout", 695 | "output_type": "stream", 696 | "text": [ 697 | "Fitted MAE\n", 698 | "Infected: 587.5587793520101\n", 699 | "Recovered: 216.5347633701369\n", 700 | "Dead: 21.532532235433454\n", 701 | "\n", 702 | "Fitted RMSE\n", 703 | "Infected: 702.1643902204037\n", 704 | "Recovered: 284.9841911847741\n", 705 | "Dead: 26.4463792416788\n" 706 | ] 707 | } 708 | ], 709 | "source": [ 710 | "print(\"Fitted MAE\")\n", 711 | "print('Infected: ', np.mean(np.abs(fitted_predicted_IRD[:days, 0] - observed_IRD[:days, 0])))\n", 712 | "print('Recovered: ', np.mean(np.abs(fitted_predicted_IRD[:days, 1] - observed_IRD[:days, 1])))\n", 713 | "print('Dead: ', np.mean(np.abs(fitted_predicted_IRD[:days, 2] - observed_IRD[:days, 2])))\n", 714 | "\n", 715 | "print(\"\\nFitted RMSE\")\n", 716 | "print('Infected: ', np.sqrt(np.mean((fitted_predicted_IRD[:days, 0] - observed_IRD[:days, 0])**2)))\n", 717 | "print('Recovered: ', np.sqrt(np.mean((fitted_predicted_IRD[:days, 1] - observed_IRD[:days, 1])**2)))\n", 718 | "print('Dead: ', np.sqrt(np.mean((fitted_predicted_IRD[:days, 2] - observed_IRD[:days, 2])**2)))" 719 | ] 720 | }, 721 | { 722 | "cell_type": "code", 723 | "execution_count": 39, 724 | "metadata": {}, 725 | "outputs": [ 726 | { 727 | "name": "stdout", 728 | "output_type": "stream", 729 | "text": [ 730 | "Predicted MAE\n", 731 | "Infected: 3467.935464895652\n", 732 | "Recovered: 1024.9276874384093\n", 733 | "Dead: 117.13820975700246\n", 734 | "\n", 735 | "Predicted RMSE\n", 736 | "Infected: 3556.5050027558914\n", 737 | "Recovered: 1033.452662364444\n", 738 | "Dead: 120.34147562618548\n" 739 | ] 740 | } 741 | ], 742 | "source": [ 743 | "print(\"Predicted MAE\")\n", 744 | "print('Infected: ', np.mean(np.abs(fitted_predicted_IRD[days:observed_IRD.shape[0], 0] - observed_IRD[days:, 0])))\n", 745 | "print('Recovered: ', np.mean(np.abs(fitted_predicted_IRD[days:observed_IRD.shape[0], 1] - observed_IRD[days:, 1])))\n", 746 | "print('Dead: ', np.mean(np.abs(fitted_predicted_IRD[days:observed_IRD.shape[0], 2] - observed_IRD[days:, 2])))\n", 747 | "\n", 748 | "print(\"\\nPredicted RMSE\")\n", 749 | "print('Infected: ', np.sqrt(np.mean((fitted_predicted_IRD[days:observed_IRD.shape[0], 0] - observed_IRD[days:, 0])**2)))\n", 750 | "print('Recovered: ', np.sqrt(np.mean((fitted_predicted_IRD[days:observed_IRD.shape[0], 1] - observed_IRD[days:, 1])**2)))\n", 751 | "print('Dead: ', np.sqrt(np.mean((fitted_predicted_IRD[days:observed_IRD.shape[0], 2] - observed_IRD[days:, 2])**2)))" 752 | ] 753 | }, 754 | { 755 | "cell_type": "code", 756 | "execution_count": 40, 757 | "metadata": {}, 758 | "outputs": [ 759 | { 760 | "data": { 761 | "application/vnd.jupyter.widget-view+json": { 762 | "model_id": "d0841add0f994906a4d0e84d0a4d7506", 763 | "version_major": 2, 764 | "version_minor": 0 765 | }, 766 | "text/plain": [ 767 | "interactive(children=(IntSlider(value=1000, description='initE', layout=Layout(width='99%'), max=100000, style…" 768 | ] 769 | }, 770 | "metadata": {}, 771 | "output_type": "display_data" 772 | } 773 | ], 774 | "source": [ 775 | "interact(main, \n", 776 | " initE=IntSlider(min=0, max=100000, step=1, value=initE, description='initE', style=style, layout=slider_layout),\n", 777 | " initI=IntSlider(min=0, max=100000, step=10, value=initI, description='initI', style=style, layout=slider_layout),\n", 778 | " initR=IntSlider(min=0, max=100000, step=10, value=initR, description='initR', style=style, layout=slider_layout),\n", 779 | " initD=IntSlider(min=0, max=100000, step=10, value=initD, description='initD', style=style, layout=slider_layout),\n", 780 | " initN=IntSlider(min=0, max=1380000000, step=1000, value=initN, description='initN', style=style, layout=slider_layout),\n", 781 | " beta=FloatSlider(min=0, max=4, step=0.01, value=result.params['beta'].value, description='Infection rate', style=style, layout=slider_layout),\n", 782 | " sigma=FloatSlider(min=0, max=4, step=0.01, value=result.params['sigma'].value, description='Incubation rate', style=style, layout=slider_layout),\n", 783 | " gamma=FloatSlider(min=0, max=4, step=0.01, value=result.params['gamma'].value, description='Recovery rate', style=style, layout=slider_layout),\n", 784 | " mu=FloatSlider(min=0, max=1, step=0.01, value=result.params['mu'].value, description='Mortality rate', style=style, layout=slider_layout),\n", 785 | " days=IntSlider(min=1, max=600, step=7, value=240, description='Days', style=style, layout=slider_layout),\n", 786 | " param_fitting=ToggleButton(value=False, description='Fitting Mode', disabled=True, button_style='', \\\n", 787 | " tooltip='Click to show fewer plots', icon='check-circle')\n", 788 | " );" 789 | ] 790 | }, 791 | { 792 | "cell_type": "markdown", 793 | "metadata": {}, 794 | "source": [ 795 | "**References:**
\n", 796 | "1. SEIR and SEIRS Model https://www.idmod.org/docs/hiv/model-seir.html
\n", 797 | "2. Compartmental models in epidemiology https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SEIR_model
\n", 798 | "3. Solve Differential Equations in Python https://www.youtube.com/watch?v=VV3BnroVjZo
\n", 799 | "4. Computational Statistics in Python https://people.duke.edu/~ccc14/sta-663/CalibratingODEs.html
\n", 800 | "5. Ordinary Differential Equations (ODE) with Python and Jupyter https://elc.github.io/posts/ordinary-differential-equations-with-python/
\n", 801 | "6. SEIRS+ Model https://github.com/ryansmcgee/seirsplus
\n", 802 | "7. Stack Overflow https://stackoverflow.com/questions/40753159/why-is-scipy-minimize-ignoring-my-constraints
\n", 803 | "8. Lotka–Volterra equations https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations
\n", 804 | "9. SEIR and Regression Model based COVID-19 outbreak predictions in India https://www.medrxiv.org/content/10.1101/2020.04.01.20049825v1.full.pdf
\n", 805 | "\n", 806 | "A simulator built with RShiny which provides many more parameters https://alhill.shinyapps.io/COVID19seir/" 807 | ] 808 | }, 809 | { 810 | "cell_type": "code", 811 | "execution_count": null, 812 | "metadata": {}, 813 | "outputs": [], 814 | "source": [] 815 | } 816 | ], 817 | "metadata": { 818 | "kernelspec": { 819 | "display_name": "Python 3", 820 | "language": "python", 821 | "name": "python3" 822 | }, 823 | "language_info": { 824 | "codemirror_mode": { 825 | "name": "ipython", 826 | "version": 3 827 | }, 828 | "file_extension": ".py", 829 | "mimetype": "text/x-python", 830 | "name": "python", 831 | "nbconvert_exporter": "python", 832 | "pygments_lexer": "ipython3", 833 | "version": "3.7.6" 834 | } 835 | }, 836 | "nbformat": 4, 837 | "nbformat_minor": 4 838 | } 839 | -------------------------------------------------------------------------------- /compartmental_models/images/notations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/notations.png -------------------------------------------------------------------------------- /compartmental_models/images/seir_de_eqns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/seir_de_eqns.png -------------------------------------------------------------------------------- /compartmental_models/images/seir_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/seir_model.png -------------------------------------------------------------------------------- /compartmental_models/images/seir_simulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/seir_simulation.png -------------------------------------------------------------------------------- /compartmental_models/images/seird_de_eqns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/seird_de_eqns.png -------------------------------------------------------------------------------- /compartmental_models/images/seird_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/seird_model.png -------------------------------------------------------------------------------- /compartmental_models/images/seird_simulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/seird_simulation.png -------------------------------------------------------------------------------- /compartmental_models/images/seird_simulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silpara/simulators/8737465a08d48eec6763098f98c7772d2a21d2f8/compartmental_models/images/seird_simulator.png --------------------------------------------------------------------------------