├── .nbinteract.json
├── images
├── coronavirus_curve.mp4
├── 2020-03-20_ben_sparks.jpg
├── numberphile_background.jpg
├── 2020-03-20_ben_sparks_2.jpg
└── coronavirus_curve_no_background.mp4
├── notebooks
├── coronavirus_curve.mp4
├── numberphile_background.jpg
├── coronavirus_curve_no_background.mp4
└── The_Coronavirus_Curve.ipynb
├── .gitignore
├── requirements.in
├── README.md
└── requirements.txt
/.nbinteract.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec": "john-sandall/numberphile/master"
3 | }
--------------------------------------------------------------------------------
/images/coronavirus_curve.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/images/coronavirus_curve.mp4
--------------------------------------------------------------------------------
/images/2020-03-20_ben_sparks.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/images/2020-03-20_ben_sparks.jpg
--------------------------------------------------------------------------------
/images/numberphile_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/images/numberphile_background.jpg
--------------------------------------------------------------------------------
/notebooks/coronavirus_curve.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/notebooks/coronavirus_curve.mp4
--------------------------------------------------------------------------------
/images/2020-03-20_ben_sparks_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/images/2020-03-20_ben_sparks_2.jpg
--------------------------------------------------------------------------------
/notebooks/numberphile_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/notebooks/numberphile_background.jpg
--------------------------------------------------------------------------------
/images/coronavirus_curve_no_background.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/images/coronavirus_curve_no_background.mp4
--------------------------------------------------------------------------------
/notebooks/coronavirus_curve_no_background.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-sandall/numberphile/master/notebooks/coronavirus_curve_no_background.mp4
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### https://www.gitignore.io/ ###
2 |
3 | # Caches
4 | **/.ipynb_checkpoints/*
5 | **/__pycache__/*
6 | *.pyc
7 |
8 | # Other
9 | .DS_Store
10 |
--------------------------------------------------------------------------------
/requirements.in:
--------------------------------------------------------------------------------
1 | jupyter-contrib-nbextensions==0.5.1
2 | jupyterlab==2.0.1
3 | nb-black==1.0.7
4 | nbinteract==0.2.5
5 | pandas==1.0.3
6 | Pillow==7.0.0
7 | pip-tools==4.5.1
8 | scipy==1.4.1
9 | seaborn==0.10.0
10 | treon==0.1.3
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Numberphile
2 |
3 | [](https://www.youtube.com/watch?v=k6nLfCbAzgo)
4 |
5 |
6 |
7 | **Video Description**
8 | > _Ben Sparks explains (and codes) the so-called SIR Model being used to predict the spread of cornavirus (COVID-19)._
9 | >
10 | > **LINKS**
11 | > - National Health Service (UK) advice on Coronavirus: https://www.nhs.uk/conditions/coronavirus-covid-19/
12 | > - Ben Sparks: https://www.bensparks.co.uk
13 | > - Use the Geogebra file Ben created for this video: https://www.geogebra.org/m/nbjfjtpv
14 | > - Another good file courtesy of Juan Carlos Ponce Campuzano: https://www.geogebra.org/m/utbemrca
15 | > - Washington Post simulator: https://www.washingtonpost.com/graphics/2020/world/corona-simulator/
16 | > - Extended presentation by Nick Jewell for MSRI: https://youtu.be/MZ957qhzcjI
17 | > - More videos with Ben Sparks: http://bit.ly/Sparks_Playlist
18 | >
19 | > **SOME OTHER YOUTUBERS ON THIS TOPIC...**
20 | > - 3blue1brown on the exponential growth of epidemics: https://youtu.be/Kas0tIxDvrg
21 | > - Tom Crawford on the SIR Model: https://youtu.be/NKMHhm2Zbkw
22 | > - Kurzgesagt on COVID-19: https://youtu.be/BtN-goy9VOY
23 | >
24 | > **NUMBERPHILE**
25 | > - Website: http://www.numberphile.com/
26 | > - Numberphile on Facebook: http://www.facebook.com/numberphile
27 | > - Numberphile tweets: https://twitter.com/numberphile
28 | > Subscribe: http://bit.ly/Numberphile_Sub
29 | >
30 | > Videos by [Brady Haran](https://www.bradyharanblog.com/) ([@BradyHaran](https://twitter.com/BradyHaran))
31 |
32 |
33 | # Tips & Tricks
34 | ```
35 | # Activate environment
36 | workon numberphile
37 |
38 | # Update packages from requirements.txt
39 | pip-sync
40 |
41 | # Install new package & update requirements.txt
42 | pip install new-package-name
43 | pip freeze # to check version number
44 |
45 | # copy paste package & version to requirements.in
46 | pip-compile requirements.in
47 | pip-sync
48 | ```
49 |
50 | # Setup
51 | ```
52 | # Install virtualenv
53 | pip install virtualenv
54 |
55 |
56 | # Install virtualenvwrapper (http://virtualenvwrapper.readthedocs.org/en/latest/index.html)
57 | pip install virtualenvwrapper
58 | # Tell shell to source virtualenvwrapper.sh and where to put the virtualenvs by adding following to .zshrc
59 | zshconfig
60 | # # "Tell shell to source virtualenvwrapper.sh and where to put the virtualenvs"
61 | # export WORKON_HOME=$HOME/.virtualenvs
62 | # export PROJECT_HOME=$HOME/code
63 | # source /usr/local/bin/virtualenvwrapper.sh
64 | source ~/.zshrc
65 | source /usr/local/bin/virtualenvwrapper.sh
66 | # Now let's make a virtualenv
67 | mkvirtualenv venv
68 | workon venv
69 | # Commands `workon venv`, `deactivate`, `lsvirtualenv` and `rmvirtualenv` are useful
70 | # WARNING: When you brew install formulae that provide Python bindings, you should not be in an active virtual environment.
71 | # (https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Homebrew-and-Python.md)
72 | deactivate
73 |
74 |
75 | # Create virtualenv & install packasges
76 | mkvirtualenv numberphile
77 | pip install pip-tools
78 | pip-sync
79 | python -m ipykernel install --user --name numberphile --display-name "Python (numberphile)"
80 |
81 | # Install Jupyter extensions JS/CSS & enable required extensions
82 | jupyter contrib nbextension install
83 | jupyter nbextension enable toc2/main
84 |
85 | # Initialise nbinteract & create .html
86 | nbinteract init
87 | nbinteract notebooks/The_Coronavirus_Curve.ipynb
88 | ```
89 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | #
2 | # This file is autogenerated by pip-compile
3 | # To update, run:
4 | #
5 | # pip-compile requirements.in
6 | #
7 | appdirs==1.4.3 # via black
8 | appnope==0.1.0 # via ipykernel, ipython
9 | attrs==19.3.0 # via black, jsonschema
10 | backcall==0.1.0 # via ipython
11 | black==19.10b0 # via nb-black
12 | bleach==3.1.4 # via nbconvert
13 | bqplot==0.11.0 # via nbinteract
14 | click==7.1.1 # via black, pip-tools
15 | cycler==0.10.0 # via matplotlib
16 | decorator==4.4.2 # via ipython, traitlets
17 | defusedxml==0.6.0 # via nbconvert
18 | docopt==0.6.2 # via nbinteract, treon
19 | entrypoints==0.3 # via nbconvert
20 | importlib-metadata==1.6.0 # via jsonschema
21 | ipykernel==5.2.0 # via ipywidgets, jupyter, jupyter-console, notebook, qtconsole
22 | ipython-genutils==0.2.0 # via jupyter-contrib-nbextensions, nbformat, notebook, qtconsole, traitlets
23 | ipython==7.13.0 # via ipykernel, ipywidgets, jupyter-console, jupyter-latex-envs, nb-black, nbinteract
24 | ipywidgets==7.5.1 # via bqplot, jupyter, nbinteract
25 | jedi==0.16.0 # via ipython
26 | jinja2==2.11.1 # via jupyterlab, jupyterlab-server, nbconvert, nbinteract, notebook
27 | json5==0.9.4 # via jupyterlab-server
28 | jsonschema==3.2.0 # via jupyterlab-server, nbformat
29 | jupyter-client==6.1.2 # via ipykernel, jupyter-console, notebook, qtconsole, treon
30 | jupyter-console==6.1.0 # via jupyter
31 | jupyter-contrib-core==0.3.3 # via jupyter-contrib-nbextensions, jupyter-nbextensions-configurator
32 | jupyter-contrib-nbextensions==0.5.1 # via -r requirements.in
33 | jupyter-core==4.6.3 # via jupyter-client, jupyter-contrib-core, jupyter-contrib-nbextensions, jupyter-latex-envs, jupyter-nbextensions-configurator, nbconvert, nbformat, notebook, qtconsole
34 | jupyter-highlight-selected-word==0.2.0 # via jupyter-contrib-nbextensions
35 | jupyter-latex-envs==1.4.6 # via jupyter-contrib-nbextensions
36 | jupyter-nbextensions-configurator==0.4.1 # via jupyter-contrib-nbextensions
37 | jupyter==1.0.0 # via treon
38 | jupyterlab-server==1.0.7 # via jupyterlab
39 | jupyterlab==2.0.1 # via -r requirements.in
40 | kiwisolver==1.1.0 # via matplotlib
41 | lxml==4.5.0 # via jupyter-contrib-nbextensions
42 | markupsafe==1.1.1 # via jinja2
43 | matplotlib==3.2.1 # via seaborn
44 | mistune==0.8.4 # via nbconvert
45 | nb-black==1.0.7 # via -r requirements.in
46 | nbconvert==5.6.1 # via jupyter, jupyter-contrib-nbextensions, jupyter-latex-envs, nbinteract, notebook, treon
47 | nbformat==4.4.0 # via ipywidgets, nbconvert, nbinteract, notebook
48 | nbinteract==0.2.5 # via -r requirements.in
49 | notebook==6.0.3 # via jupyter, jupyter-contrib-core, jupyter-contrib-nbextensions, jupyter-latex-envs, jupyter-nbextensions-configurator, jupyterlab, jupyterlab-server, widgetsnbextension
50 | numpy==1.18.2 # via bqplot, matplotlib, nbinteract, pandas, scipy, seaborn
51 | pandas==1.0.3 # via -r requirements.in, bqplot, seaborn
52 | pandocfilters==1.4.2 # via nbconvert
53 | parso==0.6.2 # via jedi
54 | pathspec==0.7.0 # via black
55 | pexpect==4.8.0 # via ipython
56 | pickleshare==0.7.5 # via ipython
57 | pillow==7.0.0 # via -r requirements.in
58 | pip-tools==4.5.1 # via -r requirements.in
59 | prometheus-client==0.7.1 # via notebook
60 | prompt-toolkit==3.0.5 # via ipython, jupyter-console
61 | ptyprocess==0.6.0 # via pexpect, terminado
62 | pygments==2.6.1 # via ipython, jupyter-console, nbconvert, qtconsole
63 | pyparsing==2.4.6 # via matplotlib
64 | pyrsistent==0.16.0 # via jsonschema
65 | python-dateutil==2.8.1 # via jupyter-client, matplotlib, pandas
66 | pytz==2019.3 # via pandas
67 | pyyaml==5.3.1 # via jupyter-contrib-nbextensions, jupyter-nbextensions-configurator
68 | pyzmq==19.0.0 # via jupyter-client, notebook, qtconsole
69 | qtconsole==4.7.2 # via jupyter
70 | qtpy==1.9.0 # via qtconsole
71 | regex==2020.2.20 # via black
72 | scipy==1.4.1 # via -r requirements.in, seaborn
73 | seaborn==0.10.0 # via -r requirements.in
74 | send2trash==1.5.0 # via notebook
75 | six==1.14.0 # via bleach, cycler, jsonschema, pip-tools, pyrsistent, python-dateutil, traitlets
76 | terminado==0.8.3 # via notebook
77 | testpath==0.4.4 # via nbconvert
78 | toml==0.10.0 # via black
79 | toolz==0.10.0 # via nbinteract
80 | tornado==6.0.4 # via ipykernel, jupyter-client, jupyter-contrib-core, jupyter-contrib-nbextensions, jupyter-nbextensions-configurator, jupyterlab, notebook, terminado
81 | traitlets==4.3.3 # via bqplot, ipykernel, ipython, ipywidgets, jupyter-client, jupyter-contrib-core, jupyter-contrib-nbextensions, jupyter-core, jupyter-latex-envs, jupyter-nbextensions-configurator, nbconvert, nbformat, nbinteract, notebook, qtconsole, traittypes
82 | traittypes==0.2.1 # via bqplot
83 | treon==0.1.3 # via -r requirements.in
84 | typed-ast==1.4.1 # via black
85 | wcwidth==0.1.9 # via prompt-toolkit
86 | webencodings==0.5.1 # via bleach
87 | widgetsnbextension==3.5.1 # via ipywidgets
88 | zipp==3.1.0 # via importlib-metadata
89 |
90 | # The following packages are considered to be unsafe in a requirements file:
91 | # setuptools
92 |
--------------------------------------------------------------------------------
/notebooks/The_Coronavirus_Curve.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "toc": true
7 | },
8 | "source": [
9 | "Table of Contents
\n",
10 | ""
11 | ]
12 | },
13 | {
14 | "cell_type": "markdown",
15 | "metadata": {},
16 | "source": [
17 | "# The Coronavirus Curve - Numberphile"
18 | ]
19 | },
20 | {
21 | "cell_type": "markdown",
22 | "metadata": {},
23 | "source": [
24 | "[](https://www.youtube.com/watch?v=k6nLfCbAzgo)"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "metadata": {},
30 | "source": [
31 | ""
32 | ]
33 | },
34 | {
35 | "cell_type": "markdown",
36 | "metadata": {},
37 | "source": [
38 | "**Video Description**\n",
39 | "> _Ben Sparks explains (and codes) the so-called SIR Model being used to predict the spread of cornavirus (COVID-19)._\n",
40 | "> \n",
41 | "> **LINKS**\n",
42 | "> - National Health Service (UK) advice on Coronavirus: https://www.nhs.uk/conditions/coronavirus-covid-19/\n",
43 | "> - Ben Sparks: https://www.bensparks.co.uk\n",
44 | "> - Use the Geogebra file Ben created for this video: https://www.geogebra.org/m/nbjfjtpv\n",
45 | "> - Another good file courtesy of Juan Carlos Ponce Campuzano: https://www.geogebra.org/m/utbemrca \n",
46 | "> - Washington Post simulator: https://www.washingtonpost.com/graphics/2020/world/corona-simulator/\n",
47 | "> - Extended presentation by Nick Jewell for MSRI: https://youtu.be/MZ957qhzcjI\n",
48 | "> - More videos with Ben Sparks: http://bit.ly/Sparks_Playlist\n",
49 | "> \n",
50 | "> **SOME OTHER YOUTUBERS ON THIS TOPIC...**\n",
51 | "> - 3blue1brown on the exponential growth of epidemics: https://youtu.be/Kas0tIxDvrg\n",
52 | "> - Tom Crawford on the SIR Model: https://youtu.be/NKMHhm2Zbkw\n",
53 | "> - Kurzgesagt on COVID-19: https://youtu.be/BtN-goy9VOY\n",
54 | "> \n",
55 | "> **NUMBERPHILE**\n",
56 | "> - Website: http://www.numberphile.com/\n",
57 | "> - Numberphile on Facebook: http://www.facebook.com/numberphile\n",
58 | "> - Numberphile tweets: https://twitter.com/numberphile\n",
59 | "> Subscribe: http://bit.ly/Numberphile_Sub\n",
60 | "> \n",
61 | "> Videos by [Brady Haran](https://www.bradyharanblog.com/) ([@BradyHaran](https://twitter.com/BradyHaran))"
62 | ]
63 | },
64 | {
65 | "cell_type": "code",
66 | "execution_count": null,
67 | "metadata": {},
68 | "outputs": [],
69 | "source": [
70 | "# This is where the magic happens. ✨\n",
71 | "%load_ext nb_black\n",
72 | "\n",
73 | "import matplotlib.image as mpimg\n",
74 | "import numpy as np\n",
75 | "import pandas as pd\n",
76 | "import seaborn as sns\n",
77 | "from IPython.display import Video\n",
78 | "from ipywidgets import interact, interact_manual\n",
79 | "from matplotlib import pyplot as plt\n",
80 | "from matplotlib.animation import FuncAnimation\n",
81 | "from scipy.integrate import solve_ivp"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": null,
87 | "metadata": {},
88 | "outputs": [],
89 | "source": [
90 | "%matplotlib inline"
91 | ]
92 | },
93 | {
94 | "cell_type": "markdown",
95 | "metadata": {},
96 | "source": [
97 | "## The SIR Model (of disease spread)\n",
98 | "The three variables we'll use:\n",
99 | "- **S = Susceptible** (people who are possibly able to get the disease)\n",
100 | "- **I = Infected** (people who have got the disease)\n",
101 | "- **R = Recovered** (people who are not infected any more, may be recovered, may be dead)\n",
102 | "\n",
103 | "**Goal:** build up some simple naïve assumptions of how diseases spread & follow the mathematical consequences to make a prediction."
104 | ]
105 | },
106 | {
107 | "cell_type": "code",
108 | "execution_count": null,
109 | "metadata": {},
110 | "outputs": [],
111 | "source": [
112 | "## Set up some initial conditions\n",
113 | "\n",
114 | "# Population of size 1, i.e. 100% (N is between 0 and 1)\n",
115 | "N = 1\n",
116 | "\n",
117 | "# Assume some Infected people (1% are Infected)\n",
118 | "Istart = 0.01\n",
119 | "\n",
120 | "# Assume some people are Susceptible\n",
121 | "Sstart = N - Istart\n",
122 | "\n",
123 | "# Nobody yet has Recovered\n",
124 | "Rstart = 0\n",
125 | "\n",
126 | "print(f\"Starting conditions: N = {N}, S = {Sstart}, I = {Istart}, R = {Rstart}\")"
127 | ]
128 | },
129 | {
130 | "cell_type": "code",
131 | "execution_count": null,
132 | "metadata": {},
133 | "outputs": [],
134 | "source": [
135 | "## For now, fix these \"rate\" variables\n",
136 | "\n",
137 | "# transm = Transmission/infection rate, how quickly the disease gets transmitted.\n",
138 | "transm = 3.2\n",
139 | "\n",
140 | "# recov = Recovery rate, how quickly people recover, this should be\n",
141 | "# smaller as it takes people longer to recover from a disease.\n",
142 | "recov = 0.23\n",
143 | "\n",
144 | "# maxT = How long we're going to let the model run for.\n",
145 | "maxT = 1"
146 | ]
147 | },
148 | {
149 | "cell_type": "markdown",
150 | "metadata": {},
151 | "source": [
152 | "### The Rate Equations\n",
153 | "We'll set up some \"rate equations\" (a.k.a. \"differential equations\") that tell us about each variable's rate of change. If you would like to learn more about differential equations, [3Blue1Brown](https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw) has an [_excellent_ series on YouTube here](https://www.youtube.com/watch?v=p_di4Zn4wz4&list=PLZHQObOWTQDNPOjrT6KVlfJuKtYTftqH6) or you could try this [introductory course on Brilliant](https://brilliant.org/courses/differential-equations/).\n",
154 | "\n",
155 | "Differential equations describe the mathematics of change and appear in every branch of science and beyond. They can be used to describe and model anything that changes, from rockets to bodies to stock markets.\n",
156 | "\n",
157 | "The three equations that form the SIR Model are described (using mathematical notation) as follows:\n",
158 | "\n",
159 | "> $\\frac{dS}{dT} = - TransmissionRate * S * I$\n",
160 | "> \n",
161 | "> $\\frac{dI}{dT} = TransmissionRate * S * I - RecoveryRate * I$\n",
162 | "> \n",
163 | "> $\\frac{dR}{dt} = RecoveryRate * I$\n",
164 | "\n",
165 | "Do these make sense?\n",
166 | "- The rate of change of **Susceptibles**, $\\frac{dS}{dT}$, is negative because the rate will go down as more Susceptible people get Infected.\n",
167 | "- The rate of change of **Infected**, $\\frac{dI}{dT}$, is the number who will become Infected next (those who are Susceptible) less those who Recover (the more people become Infected, the more people can Recover).\n",
168 | "- The rate of change of **Recovery**, $\\frac{dR}{dt}$, is decided by how many people are Infected."
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": null,
174 | "metadata": {},
175 | "outputs": [],
176 | "source": [
177 | "## Let's write these in Python.\n",
178 | "\n",
179 | "\n",
180 | "def dS_dT(S, I, transm):\n",
181 | " \"\"\"The rate of change of Susceptibles over time.\n",
182 | " \n",
183 | " Args:\n",
184 | " S (float): Total who are Susceptible.\n",
185 | " I (float): Total who are Infected.\n",
186 | " transm (float): transmission rate.\n",
187 | " \n",
188 | " Returns:\n",
189 | " float: rate of change of Suscpetibles.\n",
190 | " \n",
191 | " Examples:\n",
192 | " \n",
193 | " >> dS_dT(S=0.99, I=0.01, transm=3.2)\n",
194 | " -0.03168\n",
195 | " \"\"\"\n",
196 | " # Negative because rate will go down as more Susceptible people get Infected.\n",
197 | " return -transm * S * I\n",
198 | "\n",
199 | "\n",
200 | "def dI_dT(S, I, transm, recov):\n",
201 | " \"\"\"The rate of change of Infected people over time.\n",
202 | " \n",
203 | " Args:\n",
204 | " S (float): Total who are Susceptible.\n",
205 | " I (float): Total who are Infected.\n",
206 | " transm (float): transmission rate.\n",
207 | " recov (float): recovery rate.\n",
208 | " \n",
209 | " Returns:\n",
210 | " float: rate of change of Infected.\n",
211 | " \n",
212 | " Examples:\n",
213 | " \n",
214 | " >> dI_dT(S=0.99, I=0.01, transm=3.2, recov=0.23)\n",
215 | " 0.02938\n",
216 | " \"\"\"\n",
217 | " return (\n",
218 | " transm * S * I # If people were Susceptible, they'll become Infected next.\n",
219 | " - recov * I # The more people become Infected, the more people can Recover.\n",
220 | " )\n",
221 | "\n",
222 | "\n",
223 | "def dR_dT(I, recov):\n",
224 | " \"\"\"The rate of change of Recovered people over time.\n",
225 | " \n",
226 | " Args:\n",
227 | " I (float): Total who are Infected.\n",
228 | " recov (float): recovery rate.\n",
229 | " \n",
230 | " Returns:\n",
231 | " float: rate of change of Recovered.\n",
232 | " \n",
233 | " Examples:\n",
234 | " \n",
235 | " >> dR_dT(I=0.01, recov=0.23)\n",
236 | " 0.0023\n",
237 | " \"\"\"\n",
238 | " return recov * I # Anyone who's Infected can Recover."
239 | ]
240 | },
241 | {
242 | "cell_type": "markdown",
243 | "metadata": {},
244 | "source": [
245 | "### Solve the system of differential equations!\n",
246 | "First we create a single function to hold all three rate equations, because Python's [solver function](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) wants to be given a single input function, not three."
247 | ]
248 | },
249 | {
250 | "cell_type": "code",
251 | "execution_count": null,
252 | "metadata": {},
253 | "outputs": [],
254 | "source": [
255 | "def SIR(t, y):\n",
256 | " \"\"\"\n",
257 | " This function specifies a system of differential equations to be solved,\n",
258 | " and their parameters. We will pass this to the solve_ivp [1]_ function\n",
259 | " from the scipy library.\n",
260 | " \n",
261 | " Args:\n",
262 | " t (float): time step.\n",
263 | " y (list): parameters, in this case a list containing [S, I, R, transm, recov].\n",
264 | " \n",
265 | " Returns:\n",
266 | " list: Calculated values [S, I, R, transm, recov]\n",
267 | " \n",
268 | " Examples:\n",
269 | " \n",
270 | " >>> SIR(t=0, y=[0.99, 0.01, 0.0, 3.2, 0.23])\n",
271 | " [-0.03168, 0.02938, 0.0023]\n",
272 | " \n",
273 | " .. [1] https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html\n",
274 | " \"\"\"\n",
275 | " S, I, R = y\n",
276 | " return [\n",
277 | " dS_dT(S, I, transm),\n",
278 | " dI_dT(S, I, transm, recov),\n",
279 | " dR_dT(I, recov),\n",
280 | " ]\n",
281 | "\n",
282 | "\n",
283 | "# Let's take it for a spin\n",
284 | "SIR(t=0, y=[0.99, 0.01, 0.0])"
285 | ]
286 | },
287 | {
288 | "cell_type": "markdown",
289 | "metadata": {},
290 | "source": [
291 | "Now we can solve the system of differential equations! You can learn more about [scipy's `solve_ivp()` function here](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html)."
292 | ]
293 | },
294 | {
295 | "cell_type": "code",
296 | "execution_count": null,
297 | "metadata": {},
298 | "outputs": [],
299 | "source": [
300 | "# Solve the system of differential equations!\n",
301 | "solution = solve_ivp(\n",
302 | " fun=SIR, # input function\n",
303 | " t_span=[0, maxT], # start at time 0 and continue until we get to maxT\n",
304 | " t_eval=np.arange(0, maxT, 0.1), # points at which to store the computed solutions\n",
305 | " y0=[Sstart, Istart, Rstart], # initial conditions\n",
306 | ")\n",
307 | "solution"
308 | ]
309 | },
310 | {
311 | "cell_type": "markdown",
312 | "metadata": {},
313 | "source": [
314 | "Let's create a pandas DataFrame with the calculated SIR values (solution.y) in the cells and the time steps (solution.t) as the index."
315 | ]
316 | },
317 | {
318 | "cell_type": "code",
319 | "execution_count": null,
320 | "metadata": {},
321 | "outputs": [],
322 | "source": [
323 | "df = pd.DataFrame(\n",
324 | " solution.y.T, columns=[\"Susceptible\", \"Infected\", \"Recovered\"], index=solution.t,\n",
325 | ")\n",
326 | "df"
327 | ]
328 | },
329 | {
330 | "cell_type": "code",
331 | "execution_count": null,
332 | "metadata": {},
333 | "outputs": [],
334 | "source": [
335 | "# Visualise the result!\n",
336 | "plot = df.plot(color=[\"blue\", \"red\", \"green\"], lw=2)"
337 | ]
338 | },
339 | {
340 | "cell_type": "markdown",
341 | "metadata": {},
342 | "source": [
343 | "### Visualisation, Numberphile-style\n",
344 | "Let make a helper function which also adds a larger x-axis, and also let's add the official Numberphile brown paper as a background."
345 | ]
346 | },
347 | {
348 | "cell_type": "code",
349 | "execution_count": null,
350 | "metadata": {},
351 | "outputs": [],
352 | "source": [
353 | "background = mpimg.imread(\"../images/numberphile_background.jpg\")\n",
354 | "\n",
355 | "\n",
356 | "def plot_curves(solution, xlim=[0, 10], title=None, add_background=True):\n",
357 | " \"\"\"Helper function that takes a solution and optionally visualises it\n",
358 | " using official Numberphile brown paper.\n",
359 | " \n",
360 | " Args:\n",
361 | " solution (scipy.integrate._ivp.ivp.OdeResult): Output of solve_ivp() function.\n",
362 | " xlim (list): x-axis limits in format [min, max].\n",
363 | " title (str): Optional graph title.\n",
364 | " add_background (bool): Add Numberphile brown paper background?\n",
365 | " \n",
366 | " Returns:\n",
367 | " matplotlib graph of SIR model curves.\n",
368 | " \n",
369 | " Examples:\n",
370 | " \n",
371 | " >>> solution = solve_ivp(SIR, t_span=[0, maxT], t_eval=np.arange(0, maxT, 0.1),\n",
372 | " y0=[Sstart, Istart, Rstart])\n",
373 | " >>> plot_curves(solution, title=\"The SIR Model of disease spread\")\n",
374 | " \"\"\"\n",
375 | " # Set up plot\n",
376 | " fig, ax = plt.subplots(figsize=(14, 6))\n",
377 | " plt.title(title, fontsize=15)\n",
378 | " plt.xlabel(\"Time\", fontsize=12)\n",
379 | " plt.ylabel(\"Percentage of population\", fontsize=12)\n",
380 | " # Create DataFrame\n",
381 | " df = pd.DataFrame(\n",
382 | " solution.y.T,\n",
383 | " columns=[\"Susceptible\", \"Infected\", \"Recovered\"],\n",
384 | " index=solution.t,\n",
385 | " )\n",
386 | " # Make the plot\n",
387 | " plot = df.plot(color=[\"blue\", \"red\", \"green\"], lw=2, ax=ax)\n",
388 | " plot.set_xlim(xlim[0], xlim[1])\n",
389 | " # Add background?\n",
390 | " if add_background:\n",
391 | " plot.imshow(\n",
392 | " background,\n",
393 | " aspect=plot.get_aspect(),\n",
394 | " extent=plot.get_xlim() + plot.get_ylim(),\n",
395 | " zorder=1,\n",
396 | " )\n",
397 | "\n",
398 | "\n",
399 | "plot_curves(solution, title=\"The SIR Model of disease spread\")"
400 | ]
401 | },
402 | {
403 | "cell_type": "markdown",
404 | "metadata": {},
405 | "source": [
406 | "Let's also create another helper function that plugs into the ipywidgets `interact`, so we can play around with the parameters."
407 | ]
408 | },
409 | {
410 | "cell_type": "code",
411 | "execution_count": null,
412 | "metadata": {},
413 | "outputs": [],
414 | "source": [
415 | "def solve_and_plot(\n",
416 | " Istart=0.01,\n",
417 | " Rstart=0,\n",
418 | " transm=3.2,\n",
419 | " recov=0.23,\n",
420 | " maxT=20,\n",
421 | " title=None,\n",
422 | " add_background=True,\n",
423 | "):\n",
424 | " \"\"\"Helper function so we can play around with the parameters using the interact ipywidget.\n",
425 | " \n",
426 | " Args:\n",
427 | " Istart (float): Starting value for Infected (as percent of population).\n",
428 | " Rstart (float): Starting value for Recovered (as percent of population).\n",
429 | " transm (float): transmission rate.\n",
430 | " recov (float): recovery rate.\n",
431 | " maxT (int): maximum time step.\n",
432 | " title (str): Optional graph title.\n",
433 | " add_background (bool): Optionally add Numberphile background.\n",
434 | " \n",
435 | " Returns:\n",
436 | " matplotlib graph of SIR model curves.\n",
437 | " \n",
438 | " Examples:\n",
439 | " \n",
440 | " >>> solve_and_plot(maxT=20, title=\"Set maxT = 20\")\n",
441 | " \"\"\"\n",
442 | "\n",
443 | " N = 1\n",
444 | " Sstart = N - Istart\n",
445 | "\n",
446 | " def SIR(t, y):\n",
447 | " \"\"\"We need to redefine this inside solve_and_plot() otherwise it\n",
448 | " won't pick up any changes to transm or recov.\n",
449 | " \"\"\"\n",
450 | " S, I, R = y\n",
451 | " return [\n",
452 | " dS_dT(S, I, transm),\n",
453 | " dI_dT(S, I, transm, recov),\n",
454 | " dR_dT(I, recov),\n",
455 | " ]\n",
456 | "\n",
457 | " solution = solve_ivp(\n",
458 | " fun=SIR,\n",
459 | " t_span=[0, maxT],\n",
460 | " t_eval=np.arange(0, maxT, 0.1),\n",
461 | " y0=[Sstart, Istart, Rstart],\n",
462 | " )\n",
463 | " plot_curves(solution, xlim=[0, maxT], title=title, add_background=add_background)\n",
464 | "\n",
465 | "\n",
466 | "# Let's set maxT to 20 to see how things pan out\n",
467 | "solve_and_plot(maxT=20, title=\"Set maxT = 20\")"
468 | ]
469 | },
470 | {
471 | "cell_type": "markdown",
472 | "metadata": {},
473 | "source": [
474 | "### Adding interactivity\n",
475 | "We can make this interactive using the interact function from ipywidgets!\n",
476 | "\n",
477 | "> Note: we use `interact_manual` in order to add a button as the graph can take a couple seconds to update."
478 | ]
479 | },
480 | {
481 | "cell_type": "code",
482 | "execution_count": null,
483 | "metadata": {
484 | "scrolled": false
485 | },
486 | "outputs": [],
487 | "source": [
488 | "interact_manual(\n",
489 | " solve_and_plot,\n",
490 | " Istart=(0, 1, 0.01),\n",
491 | " Rstart=(0, 1, 0.01),\n",
492 | " transm=(0, 10, 0.01),\n",
493 | " recov=(0, 1, 0.01),\n",
494 | " maxT=(0, 20, 1),\n",
495 | " title=\"\",\n",
496 | ")"
497 | ]
498 | },
499 | {
500 | "cell_type": "markdown",
501 | "metadata": {},
502 | "source": [
503 | "---\n",
504 | "\n",
505 | "[](https://www.youtube.com/watch?v=k6nLfCbAzgo&t=850)\n",
506 | "(click the image to be taken to the equivalent part of the Numberphile video)
\n",
507 | "\n",
508 | "> \"In this model, almost 80% of the population got it at once. If you project that to our NHS, that's a problem.\"\n",
509 | "---"
510 | ]
511 | },
512 | {
513 | "cell_type": "markdown",
514 | "metadata": {},
515 | "source": [
516 | "## Flatten The Curve\n",
517 | "If we disable the background then re-drawing the graph becomes fast enough to make changes to the parameters in real-time! Have a play, see if you can squash the sombrero!"
518 | ]
519 | },
520 | {
521 | "cell_type": "code",
522 | "execution_count": null,
523 | "metadata": {},
524 | "outputs": [],
525 | "source": [
526 | "interact(\n",
527 | " solve_and_plot,\n",
528 | " Istart=(0, 1, 0.01),\n",
529 | " Rstart=(0, 1, 0.01),\n",
530 | " transm=(0, 10, 0.01),\n",
531 | " recov=(0, 1, 0.01),\n",
532 | " maxT=(0, 50, 1),\n",
533 | " title=\"\",\n",
534 | " add_background=False,\n",
535 | ")"
536 | ]
537 | },
538 | {
539 | "cell_type": "markdown",
540 | "metadata": {},
541 | "source": [
542 | "### Social distancing\n",
543 | "Let's try decreasing the transmission rate (e.g. implement social distancing). Notice how the red curve flattens as the transmission rate is reduced."
544 | ]
545 | },
546 | {
547 | "cell_type": "code",
548 | "execution_count": null,
549 | "metadata": {},
550 | "outputs": [],
551 | "source": [
552 | "# Transmission rate = 1.4\n",
553 | "solve_and_plot(\n",
554 | " transm=1.4,\n",
555 | " recov=0.23,\n",
556 | " maxT=15,\n",
557 | " add_background=False,\n",
558 | " title=\"Transmission rate = 1.4\",\n",
559 | ")\n",
560 | "\n",
561 | "# Transmission rate = 0.8\n",
562 | "solve_and_plot(\n",
563 | " transm=0.8,\n",
564 | " recov=0.23,\n",
565 | " maxT=15,\n",
566 | " add_background=False,\n",
567 | " title=\"Transmission rate = 0.8\",\n",
568 | ")"
569 | ]
570 | },
571 | {
572 | "cell_type": "markdown",
573 | "metadata": {},
574 | "source": [
575 | "If you reduce the transmission rate enough, it turns out that not _everyone_ ends up being infected. There's still 10% - 20% of people who remain susceptible in the long run but they never get the disease.\n",
576 | "\n",
577 | "This is what we're hoping for with COVID-19, as there are some people who will not survive the disease. If you can stop the 10% most vulnerable from getting it, this would be great."
578 | ]
579 | },
580 | {
581 | "cell_type": "code",
582 | "execution_count": null,
583 | "metadata": {},
584 | "outputs": [],
585 | "source": [
586 | "solve_and_plot(\n",
587 | " transm=0.6,\n",
588 | " recov=0.23,\n",
589 | " maxT=50,\n",
590 | " add_background=False,\n",
591 | " title=\"Transmission rate = 0.6\",\n",
592 | ")"
593 | ]
594 | },
595 | {
596 | "cell_type": "markdown",
597 | "metadata": {},
598 | "source": [
599 | "### Increasing the recovery rate\n",
600 | "Increasing the recovery rate also flattens the curve but this is much harder to do. The NHS is currently doing this by helping people to recover quickly, but in practice we know it takes people a week to recover and sometimes a lot longer."
601 | ]
602 | },
603 | {
604 | "cell_type": "code",
605 | "execution_count": null,
606 | "metadata": {},
607 | "outputs": [],
608 | "source": [
609 | "solve_and_plot(\n",
610 | " transm=3, recov=0.2, maxT=50, add_background=False, title=\"Recovery rate = 0.2\"\n",
611 | ")\n",
612 | "solve_and_plot(\n",
613 | " transm=3, recov=0.9, maxT=50, add_background=False, title=\"Recovery rate = 0.9\"\n",
614 | ")"
615 | ]
616 | },
617 | {
618 | "cell_type": "markdown",
619 | "metadata": {},
620 | "source": [
621 | "## R0: the basic reproductive number\n",
622 | "You may have heard people talking about [R0, the basic reproductive number](https://en.wikipedia.org/wiki/Basic_reproduction_number):\n",
623 | "- An R0 of 2-3 means that every infected person infects, on average, two to three others.\n",
624 | "- An R0 less than 1 indicates that each infected person results in fewer than one new infection. When this happens, the outbreak will slowly grind to a halt.\n",
625 | "- For COVID-19, [current estimates predict an R0 between 1.4-3.9](https://en.wikipedia.org/wiki/Basic_reproduction_number)."
626 | ]
627 | },
628 | {
629 | "cell_type": "code",
630 | "execution_count": null,
631 | "metadata": {},
632 | "outputs": [],
633 | "source": [
634 | "# Let's calculate R0. This can be calculated as transm / recov.\n",
635 | "transm = 0.5\n",
636 | "recov = 0.14\n",
637 | "R_0 = transm / recov\n",
638 | "print(f\"R_0 = {R_0}\")"
639 | ]
640 | },
641 | {
642 | "cell_type": "markdown",
643 | "metadata": {},
644 | "source": [
645 | "If the COVIF-19 R0 is 3, let's see what that looks like. (Remember that the units of time doesn't mean too much here.)"
646 | ]
647 | },
648 | {
649 | "cell_type": "code",
650 | "execution_count": null,
651 | "metadata": {},
652 | "outputs": [],
653 | "source": [
654 | "solve_and_plot(\n",
655 | " transm=0.5,\n",
656 | " recov=0.14,\n",
657 | " maxT=30,\n",
658 | " add_background=False,\n",
659 | " title=f\"Transmission rate = 0.5, Recovery rate = 0.2, R0 = {0.5/0.14:.1f}\",\n",
660 | ")"
661 | ]
662 | },
663 | {
664 | "cell_type": "markdown",
665 | "metadata": {},
666 | "source": [
667 | "We'll leave it to you to play with the sliders and get a feel for how each input changes the landscape of how the infection could play out."
668 | ]
669 | },
670 | {
671 | "cell_type": "code",
672 | "execution_count": null,
673 | "metadata": {},
674 | "outputs": [],
675 | "source": [
676 | "interact(\n",
677 | " solve_and_plot,\n",
678 | " Istart=(0, 1, 0.01),\n",
679 | " Rstart=(0, 1, 0.01),\n",
680 | " transm=(0, 10, 0.01),\n",
681 | " recov=(0, 1, 0.01),\n",
682 | " maxT=(0, 50, 1),\n",
683 | " title=\"The Coronavirus Curve\",\n",
684 | " add_background=False,\n",
685 | ")"
686 | ]
687 | },
688 | {
689 | "cell_type": "markdown",
690 | "metadata": {},
691 | "source": [
692 | "## Lights, camera, animate!\n",
693 | "Let's create an animated chart in Python for maximum social media virality."
694 | ]
695 | },
696 | {
697 | "cell_type": "code",
698 | "execution_count": null,
699 | "metadata": {},
700 | "outputs": [],
701 | "source": [
702 | "# First, let's run the numbers\n",
703 | "transm = 2.4\n",
704 | "recov = 0.3\n",
705 | "maxT = 13\n",
706 | "\n",
707 | "\n",
708 | "def SIR(t, y):\n",
709 | " S, I, R = y\n",
710 | " return [\n",
711 | " dS_dT(S, I, transm),\n",
712 | " dI_dT(S, I, transm, recov),\n",
713 | " dR_dT(I, recov),\n",
714 | " ]\n",
715 | "\n",
716 | "\n",
717 | "solution = solve_ivp(\n",
718 | " fun=SIR,\n",
719 | " t_span=[0, maxT],\n",
720 | " t_eval=np.arange(0, maxT, 0.1),\n",
721 | " y0=[Sstart, Istart, Rstart],\n",
722 | ")\n",
723 | "df = pd.DataFrame(\n",
724 | " solution.y.T, columns=[\"Susceptible\", \"Infected\", \"Recovered\"], index=solution.t,\n",
725 | ")\n",
726 | "df.head()"
727 | ]
728 | },
729 | {
730 | "cell_type": "markdown",
731 | "metadata": {},
732 | "source": [
733 | "We need to enable matplotlib's \"notebook\" plotting mode in order to see the graphs animating live in the notebook."
734 | ]
735 | },
736 | {
737 | "cell_type": "code",
738 | "execution_count": null,
739 | "metadata": {},
740 | "outputs": [],
741 | "source": [
742 | "%matplotlib notebook\n",
743 | "%matplotlib notebook\n",
744 | "# See here for why we call this twice: https://github.com/ipython/ipython/issues/10873"
745 | ]
746 | },
747 | {
748 | "cell_type": "markdown",
749 | "metadata": {},
750 | "source": [
751 | "First, let's animate without the Numberphile background."
752 | ]
753 | },
754 | {
755 | "cell_type": "code",
756 | "execution_count": null,
757 | "metadata": {},
758 | "outputs": [],
759 | "source": [
760 | "def animate(i):\n",
761 | " \"\"\"We put all the animation update code into one function\n",
762 | " that loads the first i rows from the dataframe df.\"\"\"\n",
763 | " ax.clear()\n",
764 | " data = df.iloc[: i + 1]\n",
765 | " plot = data.plot(color=[\"blue\", \"red\", \"green\"], lw=4, ax=ax)\n",
766 | " plt.title(\"The Coronavirus Curve\", fontsize=15)\n",
767 | " plt.xlabel(\"Time (units)\", fontsize=12)\n",
768 | " plt.ylabel(\"Percentage of population\", fontsize=12)\n",
769 | " plot.set_xlim(0, maxT)\n",
770 | " plot.set_ylim(0, 1)\n",
771 | "\n",
772 | "\n",
773 | "fig, ax = plt.subplots(figsize=(14, 6))\n",
774 | "animate(0) # initialise the plot\n",
775 | "ani = FuncAnimation(\n",
776 | " fig, animate, frames=list(range(len(df))), repeat=False, interval=50, blit=True,\n",
777 | ")\n",
778 | "plt.show()"
779 | ]
780 | },
781 | {
782 | "cell_type": "markdown",
783 | "metadata": {},
784 | "source": [
785 | "The live animation is a bit slow as it's rendering in real time, but you can also save the full-speed animation as a smooth movie."
786 | ]
787 | },
788 | {
789 | "cell_type": "code",
790 | "execution_count": null,
791 | "metadata": {},
792 | "outputs": [],
793 | "source": [
794 | "ani.save(\"../images/coronavirus_curve_no_background.mp4\")"
795 | ]
796 | },
797 | {
798 | "cell_type": "code",
799 | "execution_count": null,
800 | "metadata": {},
801 | "outputs": [],
802 | "source": [
803 | "Video(\"../images/coronavirus_curve_no_background.mp4\", width=1000)"
804 | ]
805 | },
806 | {
807 | "cell_type": "markdown",
808 | "metadata": {},
809 | "source": [
810 | "\n",
811 | "Danger: This next cell may be quite slow to run. Proceed with caution.\n",
812 | "
"
813 | ]
814 | },
815 | {
816 | "cell_type": "markdown",
817 | "metadata": {},
818 | "source": [
819 | "Let's now add the Numberphile brown paper background."
820 | ]
821 | },
822 | {
823 | "cell_type": "code",
824 | "execution_count": null,
825 | "metadata": {},
826 | "outputs": [],
827 | "source": [
828 | "# N.B. this may be very slow\n",
829 | "\n",
830 | "\n",
831 | "def animate(i):\n",
832 | " ax.clear()\n",
833 | " data = df.iloc[: i + 1]\n",
834 | " plot = data.plot(color=[\"blue\", \"red\", \"green\"], lw=4, ax=ax)\n",
835 | " plt.title(\"The Coronavirus Curve\", fontsize=15)\n",
836 | " plt.xlabel(\"Time (units)\", fontsize=12)\n",
837 | " plt.ylabel(\"Percentage of population\", fontsize=12)\n",
838 | " plot.set_xlim(0, maxT)\n",
839 | " plot.set_ylim(0, 1)\n",
840 | " plot.imshow(\n",
841 | " background,\n",
842 | " aspect=plot.get_aspect(),\n",
843 | " extent=plot.get_xlim() + plot.get_ylim(),\n",
844 | " zorder=1,\n",
845 | " )\n",
846 | "\n",
847 | "\n",
848 | "fig, ax = plt.subplots(figsize=(14, 6))\n",
849 | "background = mpimg.imread(\"../images/numberphile_background.jpg\")\n",
850 | "animate(0)\n",
851 | "ani = FuncAnimation(\n",
852 | " fig, animate, frames=list(range(len(df))), repeat=False, interval=50, blit=True,\n",
853 | ")"
854 | ]
855 | },
856 | {
857 | "cell_type": "markdown",
858 | "metadata": {},
859 | "source": [
860 | "This is definitely quite slow, so let's save it as a full screen movie that you can be proud to share with friends & family."
861 | ]
862 | },
863 | {
864 | "cell_type": "code",
865 | "execution_count": null,
866 | "metadata": {},
867 | "outputs": [],
868 | "source": [
869 | "ani.save(\"../images/coronavirus_curve.mp4\")"
870 | ]
871 | },
872 | {
873 | "cell_type": "code",
874 | "execution_count": null,
875 | "metadata": {},
876 | "outputs": [],
877 | "source": [
878 | "Video(\"../images/coronavirus_curve.mp4\", width=1000)"
879 | ]
880 | },
881 | {
882 | "cell_type": "markdown",
883 | "metadata": {},
884 | "source": [
885 | "---\n",
886 | "\n",
887 | "\n",
888 | "
Congratulations!\n",
889 | "
\n",
890 | " If you enjoyed this notebook please consider subscribing to Numberphile on YouTube. You can see more of Ben Sparks' videos here.\n",
891 | "
\n",
892 | "
Disclaimer: This notebook and its creator are not affiliated, associated, authorised, endorsed by, or in any way officially connected with Brady Haran, Ben Sparks, Numberphile or its affiliates. We made this simply because they inspire us.
\n",
893 | "
\n",
894 | "\n",
895 | "---\n",
896 | "\n",
897 | "\n",
898 | "
About\n",
899 | "
\n",
900 | " This notebook has been made by @John_Sandall. I run training workshops in Python, data science and data engineering.\n",
901 | "
\n",
902 | "
\n",
903 | " If you are interested in registering for our paid workshops in Python for data science and engineering, you can sign up to our mailing list here.\n",
904 | "
\n",
905 | "
\n",
906 | " You can follow my free First Steps with Python and First Steps with pandas workshops for free as part of PyData Bristol's Zero To Hero 2020 monthly free workshop series. PyData Bristol will be running more free virtual workshops over the coming months so sign up via Meetup.com or follow us @PyDataBristol on Twitter.\n",
907 | "
\n",
908 | "
\n",
909 | " I am the Founder of data science consultancy Coefficient. If you would like to work with us, our team can help you with your data science, software engineering and machine learning projects as an on-demand resource. We can also create bespoke training workshops adapted to your industry, virtual or in-person, with training clients currently including BNP Paribas, EY, the Met Police and the BBC.\n",
910 | "
\n",
911 | "
\n",
912 | "\n",
913 | "---\n",
914 | "\n",
915 | "\n",
916 | "
COVID-19\n",
917 | "
\n",
918 | " - \n",
919 | " National Health Service (UK) advice on Coronavirus: https://www.nhs.uk/conditions/coronavirus-covid-19/\n",
920 | "
\n",
921 | " - \n",
922 | " CDC (US) advice on Coronavirus: https://www.cdc.gov/coronavirus/2019-ncov/index.html\n",
923 | "
\n",
924 | " - \n",
925 | " The Coronavirus Tech Handbook by Newspeak House is an amazing collaboratively curated resource on everything from COVID-19 datasets to isolation toolkits to open medical projects to finding mutual aid groups in your local area: https://coronavirustechhandbook.com/.\n",
926 | "
\n",
927 | " - \n",
928 | " Help With COVID is a clearing house for COVID-19 projects looking for volunteers. If you're looking for a way to donate your time and skills, look here first: https://helpwithcovid.com/\n",
929 | "
\n",
930 | "
\n",
931 | "
"
932 | ]
933 | }
934 | ],
935 | "metadata": {
936 | "kernelspec": {
937 | "display_name": "Python (numberphile)",
938 | "language": "python",
939 | "name": "numberphile"
940 | },
941 | "language_info": {
942 | "codemirror_mode": {
943 | "name": "ipython",
944 | "version": 3
945 | },
946 | "file_extension": ".py",
947 | "mimetype": "text/x-python",
948 | "name": "python",
949 | "nbconvert_exporter": "python",
950 | "pygments_lexer": "ipython3",
951 | "version": "3.7.7"
952 | },
953 | "toc": {
954 | "base_numbering": 1,
955 | "nav_menu": {},
956 | "number_sections": true,
957 | "sideBar": true,
958 | "skip_h1_title": true,
959 | "title_cell": "Table of Contents",
960 | "title_sidebar": "Contents",
961 | "toc_cell": true,
962 | "toc_position": {
963 | "height": "calc(100% - 180px)",
964 | "left": "10px",
965 | "top": "150px",
966 | "width": "232px"
967 | },
968 | "toc_section_display": true,
969 | "toc_window_display": true
970 | }
971 | },
972 | "nbformat": 4,
973 | "nbformat_minor": 4
974 | }
975 |
--------------------------------------------------------------------------------