{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For a recent talk in my department I talked a little bit about agent based modeling and in the process I came across the simple but quite interesting SIR model in epidemiology. The inspiration for this post was Simon Dobson's post on [Epidemic spreading processes](http://www.simondobson.org/complex-networks-complex-processes/epidemic-spreading.html), which will provide a much more detailed scientific background and take you through some of the code step by step. However as a brief introduction\n",
    "\n",
    "I've made some minor tweaks to the model by adding vaccinated and dead states. I've also unified the function based approach into a single Parameterized class, which takes care of initializing, running and visualizing the network.\n",
    "\n",
    "In this blog post I'll primarily look at how we can quickly create complex visualization about this model using HoloViews. In the process I'll look at some predictions this model can make about herd immunity but won't be giving it any rigorous scientific treatment."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Code"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's the code for the model relying only on numpy, networkx, holoviews and matplotlib in the background."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import collections\n",
    "import itertools\n",
    "import math\n",
    "\n",
    "import numpy as np\n",
    "np.seterr(divide='ignore')\n",
    "import numpy.random as rnd\n",
    "import networkx as nx\n",
    "\n",
    "import param\n",
    "import holoviews as hv\n",
    "\n",
    "\n",
    "class SRI_Model(param.Parameterized):\n",
    "    \"\"\"\n",
    "    Implementation of the SRI epidemiology model\n",
    "    using NetworkX and HoloViews for visualization.\n",
    "    This code has been adapted from Simon Dobson's\n",
    "    code here:\n",
    "    \n",
    "    http://www.simondobson.org/complex-networks-complex-processes/epidemic-spreading.html\n",
    "    \n",
    "    In addition to his basic parameters I've added\n",
    "    additional states to the model, a node may be\n",
    "    in one of the following states:\n",
    "    \n",
    "      * Susceptible: Can catch the disease from a connected node.\n",
    "      * Vaccinated: Immune to infection.\n",
    "      * Infected: Has the disease and may pass it on to any connected node.\n",
    "      * Recovered: Immune to infection.\n",
    "      * Dead: Edges are removed from graph.\n",
    "    \"\"\"\n",
    "\n",
    "    network = param.ClassSelector(class_=nx.Graph, default=None, doc=\"\"\"\n",
    "        A custom NetworkX graph, instead of the default Erdos-Renyi graph.\"\"\")\n",
    "    \n",
    "    visualize = param.Boolean(default=True, doc=\"\"\"\n",
    "        Whether to compute layout of network for visualization.\"\"\")\n",
    "    \n",
    "    N = param.Integer(default=1000, doc=\"\"\"\n",
    "        Number of nodes to simulate.\"\"\")\n",
    "    \n",
    "    mean_connections = param.Number(default=10, doc=\"\"\"\n",
    "        Mean number of connections to make to other nodes.\"\"\")\n",
    "    \n",
    "    pSick = param.Number(default=0.01, doc=\"\"\"\n",
    "        Probability of a node to be initialized in sick state.\"\"\", bounds=(0, 1))\n",
    "\n",
    "    pVaccinated = param.Number(default=0.1, bounds=(0, 1), doc=\"\"\"\n",
    "        Probability of a node to be initialized in vaccinated state.\"\"\")\n",
    "    \n",
    "    pInfect = param.Number(default=0.3, doc=\"\"\"\n",
    "        Probability of infection on each time step.\"\"\", bounds=(0, 1))\n",
    "    \n",
    "    pRecover = param.Number(default=0.05, doc=\"\"\"\n",
    "        Probability of recovering if infected on each timestep.\"\"\", bounds=(0, 1))\n",
    "    \n",
    "    pDeath = param.Number(default=0.1, doc=\"\"\"\n",
    "        Probability of death if infected on each timestep.\"\"\", bounds=(0, 1))\n",
    "    \n",
    "    \n",
    "    SPREADING_SUSCEPTIBLE = 'S'\n",
    "    SPREADING_VACCINATED = 'V'\n",
    "    SPREADING_INFECTED = 'I'\n",
    "    SPREADING_RECOVERED = 'R'\n",
    "    DEAD = 'D'\n",
    "\n",
    "    def __init__(self, **params):\n",
    "        super(SRI_Model, self).__init__(**params)\n",
    "        if not self.network:\n",
    "            self.g = nx.erdos_renyi_graph(self.N, float(self.mean_connections)/self.N)\n",
    "        else:\n",
    "            self.g = self.network\n",
    "        self.vaccinated, self.infected = self.spreading_init()\n",
    "        self.model = self.spreading_make_sir_model()\n",
    "        self.color_mapping = [self.SPREADING_SUSCEPTIBLE,\n",
    "                              self.SPREADING_VACCINATED,\n",
    "                              self.SPREADING_INFECTED,\n",
    "                              self.SPREADING_RECOVERED, self.DEAD]\n",
    "        if self.visualize:\n",
    "            k = 2/(math.sqrt(self.g.order()))\n",
    "            self.pos = hv.Graph.from_networkx(self.g, nx.spring_layout, iterations=50, k=k)\n",
    "\n",
    "    def spreading_init(self):\n",
    "        \"\"\"Initialise the network with vaccinated, susceptible and infected states.\"\"\"\n",
    "        vaccinated, infected = 0, []\n",
    "        for i in self.g.node.keys():\n",
    "            self.g.node[i]['transmissions'] = 0\n",
    "            if(rnd.random() <= self.pVaccinated): \n",
    "                self.g.node[i]['state'] = self.SPREADING_VACCINATED\n",
    "                vaccinated += 1\n",
    "            elif(rnd.random() <= self.pSick):\n",
    "                self.g.node[i]['state'] = self.SPREADING_INFECTED\n",
    "                infected.append(i)\n",
    "            else:\n",
    "                self.g.node[i]['state'] = self.SPREADING_SUSCEPTIBLE\n",
    "        return vaccinated, infected\n",
    "\n",
    "    def spreading_make_sir_model(self):\n",
    "        \"\"\"Return an SIR model function for given infection and recovery probabilities.\"\"\"\n",
    "        # model (local rule) function\n",
    "        def model( g, i ):\n",
    "            if g.node[i]['state'] == self.SPREADING_INFECTED:\n",
    "                # infect susceptible neighbours with probability pInfect\n",
    "                for m in g.neighbors(i):\n",
    "                    if g.node[m]['state'] == self.SPREADING_SUSCEPTIBLE:\n",
    "                        if rnd.random() <= self.pInfect:\n",
    "                            g.node[m]['state'] = self.SPREADING_INFECTED\n",
    "                            self.infected.append(m)\n",
    "                            g.node[i]['transmissions'] += 1\n",
    "\n",
    "                # recover with probability pRecover\n",
    "                if rnd.random() <= self.pRecover:\n",
    "                    g.node[i]['state'] = self.SPREADING_RECOVERED\n",
    "                elif rnd.random() <= self.pDeath:\n",
    "                    edges = [edge for edge in self.g.edges() if i in edge] \n",
    "                    g.node[i]['state'] = self.DEAD\n",
    "                    g.remove_edges_from(edges)\n",
    "\n",
    "        return model\n",
    "\n",
    "    def step(self):\n",
    "        \"\"\"Run a single step of the model over the graph.\"\"\"\n",
    "        for i in self.g.node.keys():\n",
    "            self.model(self.g, i)\n",
    "\n",
    "    def run(self, steps):\n",
    "        \"\"\"\n",
    "        Run the network for the specified number of time steps\n",
    "        \"\"\"\n",
    "        for i in range(steps):\n",
    "            self.step()\n",
    "\n",
    "    def stats(self):\n",
    "        \"\"\"\n",
    "        Return an ItemTable with statistics on the network data.\n",
    "        \"\"\"\n",
    "        state_labels = hv.OrderedDict([('S', 'Susceptible'), ('V', 'Vaccinated'), ('I', 'Infected'),\n",
    "                                       ('R', 'Recovered'), ('D', 'Dead')])\n",
    "        counts = collections.Counter()\n",
    "        transmissions = []\n",
    "        for n in self.g.nodes():\n",
    "            state = state_labels[self.g.node[n]['state']]\n",
    "            counts[state] += 1\n",
    "            if n in self.infected:\n",
    "                transmissions.append(self.g.node[n]['transmissions'])\n",
    "        data = hv.OrderedDict([(l, counts[l])\n",
    "                               for l in state_labels.values()])\n",
    "        \n",
    "        infected = len(set(self.infected))\n",
    "        unvaccinated = float(self.N-self.vaccinated)\n",
    "        \n",
    "        data['$R_0$'] = np.mean(transmissions) if transmissions else 0\n",
    "        data['Death rate DR'] = np.divide(float(data['Dead']),self.N)\n",
    "        data['Infection rate IR'] = np.divide(float(infected), self.N)\n",
    "        if unvaccinated:\n",
    "            unvaccinated_dr = data['Dead']/unvaccinated\n",
    "            unvaccinated_ir = infected/unvaccinated\n",
    "        else:\n",
    "            unvaccinated_dr = 0\n",
    "            unvaccinated_ir = 0\n",
    "        data['Unvaccinated DR'] = unvaccinated_dr\n",
    "        data['Unvaccinated IR'] = unvaccinated_ir\n",
    "        return hv.ItemTable(data)\n",
    "\n",
    "    def animate(self, steps):\n",
    "        \"\"\"\n",
    "        Run the network for the specified number of steps accumulating animations\n",
    "        of the network nodes and edges changing states and curves tracking the\n",
    "        spread of the disease.\n",
    "        \"\"\"\n",
    "        if not self.visualize:\n",
    "            raise Exception(\"Enable visualize option to get compute network visulizations.\")\n",
    "\n",
    "        # Declare HoloMap for network animation and counts array\n",
    "        network_hmap = hv.HoloMap(kdims='Time')\n",
    "        sird = np.zeros((steps, 5))\n",
    "        \n",
    "        # Declare labels\n",
    "        state_labels = ['Susceptible', 'Vaccinated', 'Infected', 'Recovered', 'Dead']\n",
    "\n",
    "        # Text annotation\n",
    "        nlabel = hv.Text(0.9, 0.05, 'N=%d' % self.N)\n",
    "\n",
    "        for i in range(steps):\n",
    "            # Get path, point, states and count data\n",
    "            states = [self.g.node[n]['state'] for n in self.g.nodes()]\n",
    "            state_ints = [self.color_mapping.index(v) for v in states]\n",
    "            state_array = np.array(state_ints, ndmin=2).T\n",
    "            (sird[i, :], _) = np.histogram(state_array, bins=list(range(6)))\n",
    "\n",
    "            # Create network path and node Elements\n",
    "            nodes = self.pos.nodes.clone(datatype=['dictionary'])\n",
    "            nodes = nodes.add_dimension('State', 0, states, True)\n",
    "            graph = self.pos.clone((self.pos.data.copy(), nodes))\n",
    "            # Create overlay and accumulate in network HoloMap\n",
    "            network_hmap[i] = (graph * nlabel).relabel(group='Network', label='SRI')\n",
    "            self.step()\n",
    "\n",
    "        # Create Overlay of Curves\n",
    "        #extents = (-1, -1, steps, np.max(sird)+2)\n",
    "        curves = hv.NdOverlay({label: hv.Curve(zip(range(steps), sird[:, i]),\n",
    "                                               'Time', 'Count')\n",
    "                              for i, label in enumerate(state_labels)},\n",
    "                              kdims=[hv.Dimension('State', values=state_labels)])\n",
    "        \n",
    "        # Animate VLine on top of Curves\n",
    "        distribution = hv.HoloMap({i: (curves * hv.VLine(i)).relabel(group='Counts', label='SRI')\n",
    "                                   for i in range(steps)}, kdims='Time')\n",
    "        \n",
    "        return network_hmap + distribution"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The style"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "HoloViews allows use to define various style options in advance on the Store.options object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.extension('bokeh', 'matplotlib')\n",
    "\n",
    "# Set colors and style options for the Element types\n",
    "from holoviews import Store, Options\n",
    "opts = Store.options()\n",
    "\n",
    "colormap = {k: v for k, v in zip('SVIRD', hv.Cycle().values)}\n",
    "\n",
    "opts.Graph     = Options('plot', color_index='State')\n",
    "opts.Graph     = Options('style', cmap=colormap, node_size=6, edge_line_width=1)\n",
    "opts.Histogram = Options('plot', show_grid=False)\n",
    "opts.Overlay   = Options('plot', show_frame=False)\n",
    "opts.HeatMap   = Options('plot', xrotation=90)\n",
    "opts.ItemTable = Options('plot', width=900, height=50)\n",
    "\n",
    "opts.Overlay.Network = Options('plot', xaxis=None, yaxis=None)\n",
    "opts.Overlay.Counts  = Options('plot', show_grid=True)\n",
    "\n",
    "opts.VLine     = {'style': Options(color='black', line_width=1),\n",
    "                  'plot':  Options(show_grid=True)}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Herd Immunity"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Experiment 1: Evaluating the effects of a highly infectious and deadly disease in a small population with varying levels of vaccination"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Having defined the model and defined the model we can run some real experiments. In particular we can investigate the effect of vaccination on our model.\n",
    "\n",
    "We'll initialize our model with only 50 inviduals, who will on average make 10 connections to other individuals. Then we will infect a small population ($p=0.1$) so we can track how the disease spreads through the population. To really drive the point home we'll use a very infectious and deadly disease."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "experiment1_params = dict(pInfect=0.08, pRecover=0.08, pSick=0.15,\n",
    "                          N=50, mean_connections=10, pDeath=0.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Low vaccination population (10%)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we'll investigate the spread of the disease in population with a 10% vaccination rate:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sri_model = SRI_Model(pVaccinated=0.1, **experiment1_params)\n",
    "sri_model.animate(21).redim.range(x=(-1.2, 1.2), y=(-1.2, 1.2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In figure **A** we can observe how the disease quickly spreads across almost the entire unvaccinated population. Additionally we can track the number of individuals in a particular state in **B**. As the disease spreads unimpeded the most individuals either die or recover and therefore gain immunity. Individuals that die are obviously no longer part of the network so their connections to other individuals get deleted, this way we can see the network thin out as the disease wreaks havok among the population.\n",
    "\n",
    "Next we can view a breakdown of the final state of the simulation including infection and death rates:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts ItemTable [width=900 height=50]\n",
    "sri_model.stats()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see both the infection and death rates are very high in this population. The disease reached a large percentage all individuals causing death in a large fraction of them. Among the unvaccinated population they are of course even higher with almost >90% infected and >40% dead. The disease spread through our network completely unimpeded. Now let's see what happens if a large fraction of the population is vaccinated."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## High vaccination population (65%)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we increase the initial probability of being vaccinated to $p=0.65$ we'll be able to observe how this affects the spread of the disease through the network: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sri_model = SRI_Model(pVaccinated=0.65, **experiment1_params)\n",
    "sri_model.animate(21)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even though we can still see the disease spreading among non-vaccinated individuals we can also observe how the vaccinated individuals stop the spread. If an infected individual is connected with a majority of vaccinated indivuals the probability of the disease spreading is strongly impeded. Unlike in low vaccinated population the disease stops its spread not because too many individuals have died off, rather it quickly runs out of steam, such that a majority of the initial, susceptible but healthy population remains completely unaffected. \n",
    "\n",
    "This is what's known as herd immunity and its very important. This is because a small percentage of any population cannot be vaccinated, usually because they are immuno-compromised. However when a larger percentage of people decide that they do not want to get vaccinated (for various and invariably stupid reasons), they place the rest of the population in danger, particularly those that cannot get vaccinated for health reasons.\n",
    "\n",
    "Let's look what higher vaccination rates did to our experimental population:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sri_model.stats()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The precipetous drop in the whole populations infection rate and death rate are obviously easily explained by the fact that a smaller fraction of the population was susceptible to the disease in the first place, however as herd immunity would predict, a smaller fraction of the unvaccinated population contracted and died of the disease as well. I hope this toy example once again emphasizes how important vaccination and herd immunity is."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Large networks"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before we have a more systematic look at herd immunity we'll increase the population size to 1000 individuals and have a look at what our virulent disease does to this population, if nothing else it'll produce a pretty plot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%output holomap='scrubber' size=150\n",
    "sri_model_lv = SRI_Model(pVaccinated=0.1, **dict(experiment1_params, N=1000))\n",
    "sri_layout = sri_model_lv.animate(31)\n",
    "sri_layout.Network.SRI[::2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sri_model_hv = SRI_Model(pVaccinated=0.65, visualize=False, **dict(experiment1_params, N=1000))\n",
    "sri_model_hv.run(100)\n",
    "(sri_model_lv.stats().relabel('Low Vaccination Population') +\n",
    " sri_model_hv.stats().relabel('High Vaccination Population')).cols(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see the effect we observed in our smaller simulations from above still hold. Unvaccinated individuals are much safer in the high vaccination population than they are in the low vaccine population."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Experiment 2: Systematically exploring the effect of vaccination rates and connectivity on infection and death rates in a large population"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's conduct a more systematic experiment by varying the vaccination rate and number of connections between individuals. In Experiment 1 we saw that vaccination rates could drastically reduce infection and death rates even among the unvaccinated population. Here we'll use a much less deadly disease as we're primarily interested in is how the disease spreads through populations with more and less connections and different vaccination rates. We'll also use a larger population (N=1000) to get a more representative sample. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "experiment2_params = dict(N=1000, pInfect=0.05, pRecover=0.05,\n",
    "                          pSick=0.05, pDeath=0.001, visualize=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we explore the parameter space, we'll run the model for vaccination rates from 0% to 100% in 5% increments and for increasing numbers of connections. To speed the whole thing up we've disabled computing the network layout with the ``visualize`` parameter and will only be collecting the final simulation statistics. Finally we can simply deconstruct our data into a pandas data frame."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "exp2_dims = ['Connections', 'pVaccinated']\n",
    "hmap = hv.HoloMap(kdims=exp2_dims)\n",
    "vacc_rates = np.linspace(0, 1, 21)\n",
    "mean_conns = [2**i for i in range(7)]\n",
    "for v, c in itertools.product(vacc_rates, mean_conns):\n",
    "    sri_model = SRI_Model(mean_connections=c, pVaccinated=v, **experiment2_params)\n",
    "    sri_model.run(100)\n",
    "    hmap[c, v] = sri_model.stats()\n",
    "df = hmap.dframe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before we start visualizing this data let's have a look at it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[::20]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Regressions between vaccination, infection and death rates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the HoloViews pandas and seaborn extensions we can now perform regressions on the vaccination rates against infection and death rates. However since we also varied the mean number of connections between individuals in the network we want to consider these variables independently. By assigning the number of connections to a HoloMap we can view each plot independently with a widget.\n",
    "\n",
    "Let's define the quantities we want to visualize and switch to matplotlib for these last few plots:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "quantities = ['Unvaccinated IR', 'Infection rate IR', 'Death rate DR', '$R_0$']\n",
    "state_labels = ['Susceptible', 'Vaccinated', 'Infected', 'Recovered', 'Dead']\n",
    "\n",
    "%output backend='matplotlib'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Layout [fig_size=200] \n",
    "%%opts TriSurface (cmap='Reds_r' linewidth=0.1)\n",
    "ds = hv.Dataset(df)\n",
    "(ds.to.trisurface(['pVaccinated', 'Connections'], '$R_0$', [], group='$R_0$') +\n",
    " ds.to.trisurface(['pVaccinated', 'Connections'], 'Infection rate IR', [], group='Infection Rate'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By varying the number of connections we can observe second order effects that would usually be invisible to us. After playing around with it for a little we can draw the following conclusions:\n",
    "\n",
    "* Greater number of connections in the network lead to drastically higher infection and death rates.\n",
    "* Infection rates scale linearly with death rates for very low and very high number of connections.\n",
    "* For intermediate levels of network connectivity the relationship between vaccination and infection rates more closely resembles exponential decay, i.e. achieving a basic level of vaccination in a population has a greater payoff than boosting vaccination rates in populations where they are already high.\n",
    "* The more highly connected a population the higher the vaccination rates have to be to effectively protect the population.\n",
    "\n",
    "These results emphasize how important it is to maintain high vaccination rates in the highly connected societies we live in today. Even more importantly they show how important it is to continue vaccination programs in developing countries where they'll have the greatest impact.\n",
    "\n",
    "We can also present the data in a different way examining all the data at once in a HeatMap."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts HeatMap [show_values=False aspect=1.5 xrotation=90] Histogram {+axiswise} Layout [fig_size=150]\n",
    "group_colors = zip(quantities, ['Blues', 'Reds', 'Greens', 'Purples'])\n",
    "hv.Layout([hv.Table(df).to.heatmap(['pVaccinated', 'Connections'],\n",
    "                                   q, [], group=q).opts(style=dict(cmap=c)).hist()\n",
    "           for q, c in group_colors]).cols(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This view highlights the gradient along which vaccination is highly effective and then becoming less effective as the saturation of the colors increases."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Summary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remember \"It's only a model\", and a fairly simple one at that, but it provides some very clear predictions, which we've also observed in the real world. Getting the most out of models like this or even far more complex simulations requires tools that will allow us to make sense of interactions between many variables. I hope this post has gone some way towards persuading you that HoloViews is that tool, if so have a look at the [HoloViews website](www.holoviews.org) for more information."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Your turn"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``SRI_Model`` class provided above is deliberately very customizable. If you want to play with the model further try varying some other parameters and explore the effects on the model or supply your own network via the ``network`` parameter. There are a variety of tools to extract NetworkX Graph structures, so you could put together a model of your own social network. Hope you enjoyed it and have fun!"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
