diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ec18f25a..a17cba76 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,15 @@ What's new All notable changes to the codebase are documented in this file. Changes that may result in differences in model output, or are required in order to run an old parameter set with the current version, are flagged with the term "Regression information". +Version 2.2.0 (2024-11-18) +--------------------------- +- Starsim is now available for R! See https://r.starsim.org for details. +- The ``Calibration`` class has been completely rewritten. See the calibration tutorial for more information. +- A negative binomial distribution is now available as ``ss.nbinom()``. +- ``ss.Births()`` now uses a binomial draw of births per timestep, rather than the expected value. +- Added ``ss.load()`` and ``ss.save()`` functions, and removed ``ss.Sim.load()``. +- *GitHub info*: PR `778 `_ + Version 2.1.1 (2024-11-08) --------------------------- diff --git a/README.rst b/README.rst index 7ae30537..b721ae3f 100644 --- a/README.rst +++ b/README.rst @@ -7,13 +7,13 @@ Examples of diseases that have already been implemented in Starsim include sexua Note: Starsim is a general-purpose, multi-disease framework that builds on our previous suite of disease-specific models, which included `Covasim `_, `HPVsim `_, and `FPsim `_. In cases where a distinction needs to be made, Starsim is also known as the "Starsim framework", while this collection of other models is known as the "Starsim suite". -For more information about Starsim, please see the `documentation `__. +For more information about Starsim, please see the `documentation `__. Information about Starsim for R is available at `r.starsim.org `__. Requirements ------------ -Python 3.9-3.12. +Python 3.9-3.12 or R. We recommend, but do not require, installing Starsim in a virtual environment, such as `Anaconda `__. @@ -21,11 +21,21 @@ We recommend, but do not require, installing Starsim in a virtual environment, s Installation ------------ +Python +~~~~~~ + Starsim is most easily installed via PyPI: ``pip install starsim``. Starsim can also be installed locally. To do this, clone first this repository, then run ``pip install -e .`` (don't forget the dot at the end!). -*Note:* Starsim leverages Intel's `short vector math library `_. If you want to use this (for a ~10% speed improvement), install via `conda install intel-cmplr-lib-rt`. +R +~ +R-Starsim is still under development. You can install it with:: + + # install.packages("devtools") + devtools::install_github("starsimhub/rstarsim") + library(starsim) + init_starsim() Usage and documentation diff --git a/docs/tutorials.rst b/docs/tutorials.rst index b121e51d..5bce2d2e 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -12,6 +12,7 @@ User tutorials tutorials/tut_diseases.ipynb tutorials/tut_transmission.ipynb tutorials/tut_interventions.ipynb + tutorials/tut_calibration.ipynb Developer tutorials ------------------- diff --git a/docs/tutorials/clean_outputs b/docs/tutorials/clean_outputs index acb57c38..c8c22657 100755 --- a/docs/tutorials/clean_outputs +++ b/docs/tutorials/clean_outputs @@ -1,7 +1,7 @@ #!/bin/bash # Remove auto-generated files; use -f in case they don't exist echo 'Deleting:' -echo `ls -1 ./my-*.* 2> /dev/null` -echo '...in 1 second' -sleep 1 -rm -vf ./my-*.* \ No newline at end of file +echo `ls -1 ./my-*.* ./example*.* 2> /dev/null` +echo '...in 2 seconds' +sleep 2 +rm -vf ./my-*.* ./example*.* \ No newline at end of file diff --git a/docs/tutorials/tut_buildsim.ipynb b/docs/tutorials/tut_buildsim.ipynb index 978e063b..a1d0cc68 100644 --- a/docs/tutorials/tut_buildsim.ipynb +++ b/docs/tutorials/tut_buildsim.ipynb @@ -126,7 +126,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "id": "d17dd68a", "metadata": { "collapsed": false, @@ -186,6 +186,82 @@ "sim.run().plot()" ] }, + { + "cell_type": "markdown", + "id": "f75fdf7e", + "metadata": {}, + "source": [ + "## Loading and saving\n", + "You can save a sim to disk with `sim.save()`, and then reload it:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7357b64b", + "metadata": {}, + "outputs": [], + "source": [ + "sim.save('example.sim')\n", + "new_sim = ss.load('example.sim')" + ] + }, + { + "cell_type": "markdown", + "id": "a4fc66bf", + "metadata": {}, + "source": [ + "By default, to save space, this saves a \"shrunken\" version of the sim with most of the large objects (e.g. the `People`) removed. To save everything (for example, if you want to save a partially run sim, then reload it and continue running), you can use `shrink=False`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a682778f", + "metadata": {}, + "outputs": [], + "source": [ + "sim.save('example-big.sim', shrink=False)" + ] + }, + { + "cell_type": "markdown", + "id": "591e58ff", + "metadata": {}, + "source": [ + "All Starsim objects can also be saved via `ss.save()`; this will save the entire object. This is useful for quickly storing objects for use by other Python functions, for example:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "36a48fa5", + "metadata": {}, + "outputs": [], + "source": [ + "df = sim.to_df()\n", + "ss.save('example.df', df)\n", + "new_df = ss.load('example.df')" + ] + }, + { + "cell_type": "markdown", + "id": "9429232c", + "metadata": {}, + "source": [ + "However, for a human-readable format, you may want to use a different format. For example, if you've exported the results as a dataframe, you can then save as an Excel file:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "383c1c1b", + "metadata": {}, + "outputs": [], + "source": [ + "df.to_excel('example.xlsx')" + ] + }, { "cell_type": "markdown", "id": "c42bb5d0", diff --git a/docs/tutorials/tut_calibration.ipynb b/docs/tutorials/tut_calibration.ipynb new file mode 100644 index 00000000..2480e5a8 --- /dev/null +++ b/docs/tutorials/tut_calibration.ipynb @@ -0,0 +1,332 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# T7 - Calibration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " \n", + "An interactive version of this notebook is available on [Google Colab](https://colab.research.google.com/github/starsimhub/starsim/blob/main/docs/tutorials/tut_calibration.ipynb?install=starsim) or [Binder](https://mybinder.org/v2/gh/starsimhub/starsim/HEAD?labpath=docs%2Ftutorials%2Ftut_calibration.ipynb).\n", + " \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Disease models typically require contextualization to a relevant setting of interest prior to addressing \"what-if\" scenario questions. The process of tuning model input parameters so that model outputs match observed data is known as calibration. There are many approaches to model calibration, ranging from manual tuning to fully Bayesian methods.\n", + "\n", + "For many applications, we have found that an optimization-based approach is sufficient. Such methods avoid the tedious process of manual tuning and are less computationally expensive than fully Bayesian methods. One such optimization-based approach is the Optuna library, which is a Bayesian hyperparameter optimization framework. Optuna is designed for tuning hyperparameters of machine learning models, but it can also be used to calibrate disease models.\n", + "\n", + "Calibration libraries often treat the disease model as a black box, where the input parameters are the \"hyperparameters\" to be tuned. The calibration process is often iterative and requires a combination of expert knowledge and computational tools. The optimization algorithm iteratively chooses new parameter values to evaluate, and the model is run with these values to generate outputs. The outputs are compared to observed data, and a loss function is calculated to quantify the difference between the model outputs and the observed data. The optimization algorithm then uses this loss function to update its search strategy and choose new parameter values to evaluate. This process continues until the algorithm converges to a set of parameter values that minimize the loss function.\n", + "\n", + "While many optimization algorithms are available, Starsim has a built-in interface to the Optuna library, which we will demonstrate in this tutorial. We will use a simple Susceptible-Infected-Recovered (SIR) model as an example. We will tune three input parameters, the infectivity parameter, `beta`, the initial prevalence parameter, `init_prev`, and the Poisson-distributed degree distribution parameter, `n_contacts`. We will calibrate the model using a beta-binomial likelihood function so as to match prevalence at three distinct time points." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We begin with a few imports and default settings:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "#%% Imports and settings\n", + "import sciris as sc\n", + "import starsim as ss\n", + "import pandas as pd\n", + "\n", + "n_agents = 2e3\n", + "debug = False # If true, will run in serial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The calibration class will require a base `Sim` object. This `sim` will later be modified according to parameters selected by the optimization engine. The following function creates the base `Sim` object." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def make_sim():\n", + " \"\"\" Helper function to create the base simulation object \"\"\"\n", + " sir = ss.SIR(\n", + " beta = ss.beta(0.075),\n", + " init_prev = ss.bernoulli(0.02),\n", + " )\n", + " random = ss.RandomNet(n_contacts=ss.poisson(4))\n", + "\n", + " sim = ss.Sim(\n", + " n_agents = n_agents,\n", + " start = sc.date('1990-01-01'),\n", + " dur = 40,\n", + " dt = 1,\n", + " unit = 'day',\n", + " diseases = sir,\n", + " networks = random,\n", + " verbose = 0,\n", + " )\n", + "\n", + " # Remember to return the sim object\n", + " return sim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's define the calibration parameters. These are the inputs that Optuna will be able to modify. Here, we define three such parameters, `beta`, `init_prev`, and `n_contacts`.\n", + "\n", + "Each parameter entry should have range defined by `low` and `high` as well as a `guess` values. The `guess` value is not used by Optuna, rather only for a check after calibration completes to see if the new parameters are better than the `guess` values.\n", + "\n", + "You'll notice there are a few other parameters that can be specified. For example, the data type of the parameter appears in `suggest_type`. Possible values are listed in the Optuna documentation, and include [suggest_float](https://optuna.readthedocs.io/en/stable/reference/generated/optuna.trial.Trial.html#optuna.trial.Trial.suggest_float) for float values and [suggest_int](https://optuna.readthedocs.io/en/stable/reference/generated/optuna.trial.Trial.html#optuna.trial.Trial.suggest_int) for integer types.\n", + "\n", + "To make things easier for the search algorithm, it's helpful to indicate how outputs are expected to change with inputs. For example, increasing `beta` from 0.01 to 0.02 should double disease transmission, but increasing from 0.11 to 0.12 will have a small effect. Thus, we indicate that this parameter should be calibrated with `log=True`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Define the calibration parameters\n", + "calib_pars = dict(\n", + " beta = dict(low=0.01, high=0.30, guess=0.15, suggest_type='suggest_float', log=True), # Note the log scale\n", + " init_prev = dict(low=0.01, high=0.05, guess=0.15), # Default type is suggest_float, no need to re-specify\n", + " n_contacts = dict(low=2, high=10, guess=3, suggest_type='suggest_int'), # Suggest int just for this demo\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimization engine iteratively chooses input parameters to simulate. Those parameters are passed into the following `build_sim` function as a dictionary of `calib_pars` along with the base `sim` and any other key word arguments. The `calib_pars` will be as above, but importantly will have an additional key named `value` containing the value selected by Optuna.\n", + "\n", + "When modifying a `sim`, it is important to realize that the simulation has not been initialized yet. Nonetheless, the configuration is available for modification at `sim.pars`, as demonstrated in the function below for the SIR example." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def build_sim(sim, calib_pars, **kwargs):\n", + " \"\"\" Modify the base simulation by applying calib_pars \"\"\"\n", + "\n", + " sir = sim.pars.diseases # There is only one disease in this simulation and it is a SIR\n", + " net = sim.pars.networks # There is only one network in this simulation and it is a RandomNet\n", + "\n", + " for k, pars in calib_pars.items(): # Loop over the calibration parameters\n", + " if k == 'rand_seed':\n", + " sim.pars.rand_seed = v\n", + " continue\n", + "\n", + " # Each item in calib_pars is a dictionary with keys like 'low', 'high',\n", + " # 'guess', 'suggest_type', and importantly 'value'. The 'value' key is\n", + " # the one we want to use as that's the one selected by the algorithm\n", + " v = pars['value']\n", + " if k == 'beta':\n", + " sir.pars.beta = ss.beta(v)\n", + " elif k == 'init_prev':\n", + " sir.pars.init_prev = ss.bernoulli(v)\n", + " elif k == 'n_contacts':\n", + " net.pars.n_contacts = ss.poisson(v)\n", + " else:\n", + " raise NotImplementedError(f'Parameter {k} not recognized')\n", + "\n", + " return sim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Starsim framework has been integrated with the Optuna hyperparameter optimization algorithm to facilitate calibration through the `Calibration` class. Recall that an optimization-based approach to calibration minimizes a function of the input parameters. This function is key to achieving an acceptable calibration.\n", + "\n", + "There are two ways to describe the goodness-of-fit function for the `Calibration`. The first method is to directly provide a function that the algorithm will call. The `eval_fn` will be passed each completed `sim` after running, and is expected to return a float representing the goodness of fit (higher is better). Data can be passed into the `eval_fn` via `eval_kwargs`.\n", + "\n", + "As an alternative to directly specifying the evaluation function, you can use `CalibComponent`s. Each component includes real data, for example from a survey, that is compared against simulation data from the model. Several components and be used at the same time, for example one for disease prevalence and another for treatment coverage. Each component computes a likelihood of the data given the input parameters, as assess via simulation. Components are combined assuming independence.\n", + "\n", + "When defining a `CalibComponent`, we give it a `name` and pass in `expected` (the real data to be calibrated to). The required data fields depend on the likelihood function. Importantly, the functional form of the negative log likelihood, or nll, is defined by the `nll_fn`. The value for `nll_fn` can be `'beta'`, `'gamma'`, or a negative log likelihood function of your own creation. If designing your own function for `nll_fn`, it should take two arguments: `expected` and `actual`. For a beta binomial, the data must define `n` and `x`, where `n` is the number of individuals who were sampled and `x` is the number that were found, e.g. identified as positive.\n", + "\n", + "Output from the simulation is obtained via a function. The function takes a completed `sim` object as input and returns a dictionary with fields as required for the evaluation function of your choice. In the example below, we use an in-line lambda function to extract `n` and `x` from the simulation, as required by the Beta binomial component.\n", + "\n", + "Each component has a `weight`. The final goodness of fit is a weighted sum of negative log likelihoods.\n", + "\n", + "Finally, the `conform` argument describes how the simulation output is adjusted to align with the real data. For example, if the real data is a prevalence measurement, choosing `'prevalent'` will interpolate the simulation output at the time points of the real data. Choosing `'incident'`, the simulation output will be aggregated between time points of the real data." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "infectious = ss.CalibComponent(\n", + " name = 'Infectious',\n", + "\n", + " # For this example, the \"expected\" comes from a simulation with pars\n", + " # beta=0.075, init_prev=0.02, n_contacts=4\n", + " expected = pd.DataFrame({\n", + " 'n': [200, 197, 195], # Number of individuals sampled\n", + " 'x': [30, 30, 10], # Number of individuals found to be infectious\n", + " }, index=pd.Index([ss.date(d) for d in ['1990-01-12', '1990-01-25', '1990-02-02']], name='t')), # On these dates\n", + "\n", + " extract_fn = lambda sim: pd.DataFrame({\n", + " 'n': sim.results.n_alive, # Number of individuals sampled\n", + " 'x': sim.results.sir.n_infected, # Number of individuals found to be infectious\n", + " }, index=pd.Index(sim.results.timevec, name='t')), # Index is time\n", + "\n", + " conform = 'prevalent',\n", + " nll_fn = 'beta',\n", + "\n", + " weight = 1, # Not required if only one component\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we can bring all the pieces together. We make a single base simulation and create an instance of a Starsim Calibration object. This object requires a few arguments, like the `calib_pars` and `sim`. We also pass in the function that modifies the base `sim`, here our `build_sim` function. No additional `build_kw` are required in this example.\n", + "\n", + "We also pass in a list of `components`. Instead of using this \"component-based\" system, a user could simply provide an `eval_fn`, which takes in a completed sim an any `eval_kwargs` and returns a \"goodness of fit\" score to be maximized.\n", + "\n", + "We can also specify the total number of trial to run, the number of parallel works, and a few other parameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sc.heading('Beginning calibration')\n", + "\n", + "# Make the sim and data\n", + "sim = make_sim()\n", + "\n", + "# Make the calibration\n", + "calib = ss.Calibration(\n", + " calib_pars = calib_pars,\n", + " sim = sim,\n", + "\n", + " build_fn = build_sim, # Use default builder, Calibration.translate_pars\n", + " build_kw = None,\n", + "\n", + " components = [infectious],\n", + "\n", + " total_trials = 100,\n", + " n_workers = None, # None indicates to use all available CPUs\n", + " die = True,\n", + " debug = debug,\n", + ")\n", + "\n", + "# Perform the calibration\n", + "sc.printcyan('\\nPeforming calibration...')\n", + "calib.calibrate();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's look at the best parameters that were found. Note that the `rand_seed` was selected at random, but the other parameters are meaningful." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "calib.best_pars" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the calibration is complete, we can compare the `guess` values to the best values found by calling `check_fit`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Confirm\n", + "sc.printcyan('\\nConfirming fit...')\n", + "calib.check_fit(n_runs=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we can view some plots of the results. Blue is before calibration using the `guess` values whereas orange is after." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "calib.plot_sims()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "calib.plot_trend()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/tutorials/tut_transmission.ipynb b/docs/tutorials/tut_transmission.ipynb index 5634948c..a693666d 100644 --- a/docs/tutorials/tut_transmission.ipynb +++ b/docs/tutorials/tut_transmission.ipynb @@ -260,7 +260,7 @@ "id": "46760c64", "metadata": {}, "source": [ - "# Mixing Pools" + "## Mixing Pools" ] }, { diff --git a/examples/demo.py b/examples/demo.py deleted file mode 100644 index 545b5ec9..00000000 --- a/examples/demo.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Simple demo of Starsim -""" - -import starsim as ss -sim = ss.demo() \ No newline at end of file diff --git a/examples/samples-documentation.ipynb b/examples/samples-documentation.ipynb deleted file mode 100644 index b299d8f4..00000000 --- a/examples/samples-documentation.ipynb +++ /dev/null @@ -1,3445 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "0", - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2" - ] - }, - { - "cell_type": "markdown", - "id": "1", - "metadata": {}, - "source": [ - "# Managing samples" - ] - }, - { - "cell_type": "markdown", - "id": "2", - "metadata": {}, - "source": [ - "As STIsim models are usually stochastic, for a single scenario it is often desirable to run the model multiple times with different random seeds. The role of the `Samples` class is to facilitate working with large numbers of simulations and scenarios, to ease:\n", - "\n", - "- Loading large result sets\n", - "- Filtering/selecting simulation runs\n", - "- Plotting individual simulations and aggregate results\n", - "- Slicing result sets to compare scenarios\n", - "\n", - "Essentially, if we think of the processed results of a model run as being\n", - "\n", - "- A collection of scalar outputs (e.g., cumulative infections, total deaths)\n", - "- A dataframe of time-varying outputs (e.g., new diagnoses per day, number of people on treatment each day)\n", - "\n", - "then the classes `Dataset` and `Samples` manage collections of these results. In particular, the `Samples` class manages different random samples of the same parameters, and the `Dataset` class manages a collection of `Samples`. \n", - "\n", - "
\n", - "These classes are particularly designed to facilitate working with tens of thousands of simulation runs, where other approaches such as those based on the `MultiSim` class may not be feasible.\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starsim 0.2.1 (2024-02-22) — © 2023-2024 by IDM\n" - ] - } - ], - "source": [ - "import starsim as ss\n", - "import numpy as np\n", - "import pandas as pd\n", - "from pathlib import Path\n", - "import matplotlib.pyplot as plt\n", - "import sciris as sc" - ] - }, - { - "cell_type": "markdown", - "id": "4", - "metadata": {}, - "source": [ - "## Obtaining simulation output" - ] - }, - { - "cell_type": "markdown", - "id": "5", - "metadata": {}, - "source": [ - "To demonstrate usage of this class, we will first consider constructing the kinds of output that the `Samples` class stores. We begin by running a basic simulation using the SIR model:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Initializing sim with 10000 agents\n", - " Running 1995.0 ( 0/36) (0.16 s) ———————————————————— 3%\n", - " Running 2005.0 (10/36) (0.20 s) ••••••—————————————— 31%\n", - " Running 2015.0 (20/36) (0.23 s) •••••••••••————————— 58%\n", - " Running 2025.0 (30/36) (0.25 s) •••••••••••••••••——— 86%\n" - ] - } - ], - "source": [ - "ppl = ss.People(10000)\n", - "net = ss.ndict(ss.RandomNet(n_contacts=ss.poisson(5)))\n", - "sir = ss.SIR()\n", - "sim = ss.Sim(people=ppl, networks=net, diseases=sir, rand_seed=0)\n", - "sim.run();" - ] - }, - { - "cell_type": "markdown", - "id": "7", - "metadata": {}, - "source": [ - "### Dataframe output\n", - "\n", - "A `Sim` instance is (in general) too large and complex to efficiently store on disk - the file size and loading time make it prohibitive to work with tens of thousands of simulations. Therefore, rather than storing entire `Sim` instances, we instead store dataframes containing just the simulation results and any other pre-processed calculated quantities. There are broadly speaking two types of outputs\n", - "\n", - "- Scalar outputs at each timepoint (e.g., daily new cases)\n", - "- Scalar outputs for each simulation (e.g., total number of deaths)\n", - "\n", - "These outputs can each be produced from a `Sim` - the former has a tabular structure, and the latter has a dictionary structure (which can later be assembled into a table where the rows correspond to each simulation). The `export_df` method is a quick way to obtain a dataframe with the appropriate structure retaining all results from the `Sim`.\n", - "\n", - "\n", - "
\n", - "In real-world use, it is often helpful to write your own function to extract a dataframe of simulation outputs, because typically some of the outputs need to be extracted from custom Analyzers.\n", - "
\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
n_alivenew_deathssir.n_susceptiblesir.n_infectedsir.n_recoveredsir.prevalencesir.new_infectionssir.cum_infections
t
1995.09813.0187.06638.02358.0817.00.2402933362.00.0
1996.09329.0484.03023.03615.02691.00.3875013615.03362.0
1997.08389.0940.0609.02414.05366.00.2877582414.06977.0
1998.07572.0817.0131.0478.06963.00.063127478.09391.0
1999.07422.0150.0110.021.07291.00.00282921.09869.0
2000.07420.02.0110.00.07310.00.0000000.09890.0
2001.07420.00.0110.00.07310.00.0000000.09890.0
2002.07420.00.0110.00.07310.00.0000000.09890.0
2003.07420.00.0110.00.07310.00.0000000.09890.0
2004.07420.00.0110.00.07310.00.0000000.09890.0
2005.07420.00.0110.00.07310.00.0000000.09890.0
2006.07420.00.0110.00.07310.00.0000000.09890.0
2007.07420.00.0110.00.07310.00.0000000.09890.0
2008.07420.00.0110.00.07310.00.0000000.09890.0
2009.07420.00.0110.00.07310.00.0000000.09890.0
2010.07420.00.0110.00.07310.00.0000000.09890.0
2011.07420.00.0110.00.07310.00.0000000.09890.0
2012.07420.00.0110.00.07310.00.0000000.09890.0
2013.07420.00.0110.00.07310.00.0000000.09890.0
2014.07420.00.0110.00.07310.00.0000000.09890.0
2015.07420.00.0110.00.07310.00.0000000.09890.0
2016.07420.00.0110.00.07310.00.0000000.09890.0
2017.07420.00.0110.00.07310.00.0000000.09890.0
2018.07420.00.0110.00.07310.00.0000000.09890.0
2019.07420.00.0110.00.07310.00.0000000.09890.0
2020.07420.00.0110.00.07310.00.0000000.09890.0
2021.07420.00.0110.00.07310.00.0000000.09890.0
2022.07420.00.0110.00.07310.00.0000000.09890.0
2023.07420.00.0110.00.07310.00.0000000.09890.0
2024.07420.00.0110.00.07310.00.0000000.09890.0
2025.07420.00.0110.00.07310.00.0000000.09890.0
2026.07420.00.0110.00.07310.00.0000000.09890.0
2027.07420.00.0110.00.07310.00.0000000.09890.0
2028.07420.00.0110.00.07310.00.0000000.09890.0
2029.07420.00.0110.00.07310.00.0000000.09890.0
2030.07420.00.0110.00.07310.00.0000000.09890.0
\n", - "
" - ], - "text/plain": [ - " n_alive new_deaths sir.n_susceptible sir.n_infected \\\n", - "t \n", - "1995.0 9813.0 187.0 6638.0 2358.0 \n", - "1996.0 9329.0 484.0 3023.0 3615.0 \n", - "1997.0 8389.0 940.0 609.0 2414.0 \n", - "1998.0 7572.0 817.0 131.0 478.0 \n", - "1999.0 7422.0 150.0 110.0 21.0 \n", - "2000.0 7420.0 2.0 110.0 0.0 \n", - "2001.0 7420.0 0.0 110.0 0.0 \n", - "2002.0 7420.0 0.0 110.0 0.0 \n", - "2003.0 7420.0 0.0 110.0 0.0 \n", - "2004.0 7420.0 0.0 110.0 0.0 \n", - "2005.0 7420.0 0.0 110.0 0.0 \n", - "2006.0 7420.0 0.0 110.0 0.0 \n", - "2007.0 7420.0 0.0 110.0 0.0 \n", - "2008.0 7420.0 0.0 110.0 0.0 \n", - "2009.0 7420.0 0.0 110.0 0.0 \n", - "2010.0 7420.0 0.0 110.0 0.0 \n", - "2011.0 7420.0 0.0 110.0 0.0 \n", - "2012.0 7420.0 0.0 110.0 0.0 \n", - "2013.0 7420.0 0.0 110.0 0.0 \n", - "2014.0 7420.0 0.0 110.0 0.0 \n", - "2015.0 7420.0 0.0 110.0 0.0 \n", - "2016.0 7420.0 0.0 110.0 0.0 \n", - "2017.0 7420.0 0.0 110.0 0.0 \n", - "2018.0 7420.0 0.0 110.0 0.0 \n", - "2019.0 7420.0 0.0 110.0 0.0 \n", - "2020.0 7420.0 0.0 110.0 0.0 \n", - "2021.0 7420.0 0.0 110.0 0.0 \n", - "2022.0 7420.0 0.0 110.0 0.0 \n", - "2023.0 7420.0 0.0 110.0 0.0 \n", - "2024.0 7420.0 0.0 110.0 0.0 \n", - "2025.0 7420.0 0.0 110.0 0.0 \n", - "2026.0 7420.0 0.0 110.0 0.0 \n", - "2027.0 7420.0 0.0 110.0 0.0 \n", - "2028.0 7420.0 0.0 110.0 0.0 \n", - "2029.0 7420.0 0.0 110.0 0.0 \n", - "2030.0 7420.0 0.0 110.0 0.0 \n", - "\n", - " sir.n_recovered sir.prevalence sir.new_infections \\\n", - "t \n", - "1995.0 817.0 0.240293 3362.0 \n", - "1996.0 2691.0 0.387501 3615.0 \n", - "1997.0 5366.0 0.287758 2414.0 \n", - "1998.0 6963.0 0.063127 478.0 \n", - "1999.0 7291.0 0.002829 21.0 \n", - "2000.0 7310.0 0.000000 0.0 \n", - "2001.0 7310.0 0.000000 0.0 \n", - "2002.0 7310.0 0.000000 0.0 \n", - "2003.0 7310.0 0.000000 0.0 \n", - "2004.0 7310.0 0.000000 0.0 \n", - "2005.0 7310.0 0.000000 0.0 \n", - "2006.0 7310.0 0.000000 0.0 \n", - "2007.0 7310.0 0.000000 0.0 \n", - "2008.0 7310.0 0.000000 0.0 \n", - "2009.0 7310.0 0.000000 0.0 \n", - "2010.0 7310.0 0.000000 0.0 \n", - "2011.0 7310.0 0.000000 0.0 \n", - "2012.0 7310.0 0.000000 0.0 \n", - "2013.0 7310.0 0.000000 0.0 \n", - "2014.0 7310.0 0.000000 0.0 \n", - "2015.0 7310.0 0.000000 0.0 \n", - "2016.0 7310.0 0.000000 0.0 \n", - "2017.0 7310.0 0.000000 0.0 \n", - "2018.0 7310.0 0.000000 0.0 \n", - "2019.0 7310.0 0.000000 0.0 \n", - "2020.0 7310.0 0.000000 0.0 \n", - "2021.0 7310.0 0.000000 0.0 \n", - "2022.0 7310.0 0.000000 0.0 \n", - "2023.0 7310.0 0.000000 0.0 \n", - "2024.0 7310.0 0.000000 0.0 \n", - "2025.0 7310.0 0.000000 0.0 \n", - "2026.0 7310.0 0.000000 0.0 \n", - "2027.0 7310.0 0.000000 0.0 \n", - "2028.0 7310.0 0.000000 0.0 \n", - "2029.0 7310.0 0.000000 0.0 \n", - "2030.0 7310.0 0.000000 0.0 \n", - "\n", - " sir.cum_infections \n", - "t \n", - "1995.0 0.0 \n", - "1996.0 3362.0 \n", - "1997.0 6977.0 \n", - "1998.0 9391.0 \n", - "1999.0 9869.0 \n", - "2000.0 9890.0 \n", - "2001.0 9890.0 \n", - "2002.0 9890.0 \n", - "2003.0 9890.0 \n", - "2004.0 9890.0 \n", - "2005.0 9890.0 \n", - "2006.0 9890.0 \n", - "2007.0 9890.0 \n", - "2008.0 9890.0 \n", - "2009.0 9890.0 \n", - "2010.0 9890.0 \n", - "2011.0 9890.0 \n", - "2012.0 9890.0 \n", - "2013.0 9890.0 \n", - "2014.0 9890.0 \n", - "2015.0 9890.0 \n", - "2016.0 9890.0 \n", - "2017.0 9890.0 \n", - "2018.0 9890.0 \n", - "2019.0 9890.0 \n", - "2020.0 9890.0 \n", - "2021.0 9890.0 \n", - "2022.0 9890.0 \n", - "2023.0 9890.0 \n", - "2024.0 9890.0 \n", - "2025.0 9890.0 \n", - "2026.0 9890.0 \n", - "2027.0 9890.0 \n", - "2028.0 9890.0 \n", - "2029.0 9890.0 \n", - "2030.0 9890.0 " - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sim.export_df()" - ] - }, - { - "cell_type": "markdown", - "id": "9", - "metadata": {}, - "source": [ - "### Scalar/summary outputs\n", - "\n", - "We can also consider extracting a summary dictionary of scalar values. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "10", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'seed': 0, 'p_death': 0.2, 'cum_infections': 9890.0, 'cum_deaths': 2580.0}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "summary = {}\n", - "summary['seed'] = sim.pars['rand_seed']\n", - "summary['p_death'] = sim.diseases[0].pars.p_death.mean()\n", - "summary['cum_infections'] = sum(sim.results.sir.new_infections)\n", - "summary['cum_deaths'] = sum(sim.results.new_deaths)\n", - "summary" - ] - }, - { - "cell_type": "markdown", - "id": "11", - "metadata": {}, - "source": [ - "
\n", - "Notice how in the example above, the summary contains both simulation inputs (seed, probability of death) as well as simulation outputs (total infections, total deaths). The simulation summary should contain sufficient information about the simulation inputs to identify the simulation. The seed should generally be present. The other inputs normally correspond to variables that scenarios are being run over. In this example, we will run scenarios comparing simulations with different probabilities of death. Therefore, we need to include the death probability in the simulation summary. \n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "12", - "metadata": {}, - "source": [ - "### Running the model\n", - "\n", - "For usage at scale, the steps of creating a simulation, running it and producing these outputs are usually encapsulated in functions" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "13", - "metadata": {}, - "outputs": [], - "source": [ - "def get_sim(seed, p_death):\n", - " ppl = ss.People(10000)\n", - " net = ss.ndict(ss.RandomNet(n_contacts=ss.poisson(5)))\n", - " sir = ss.SIR(pars={'p_death':p_death})\n", - " sim = ss.Sim(people=ppl, networks=net, diseases=sir, rand_seed=seed)\n", - " sim.initialize(verbose=0)\n", - " return sim\n", - " \n", - "def run_sim(seed, p_death):\n", - " sim = get_sim(seed, p_death)\n", - " sim.run(verbose=0)\n", - " df = sim.export_df()\n", - " \n", - " summary = {}\n", - " summary['seed'] = sim.pars['rand_seed']\n", - " summary['p_death']= sim.diseases[0].pars.p_death.mean()\n", - " summary['cum_infections'] = sum(sim.results.sir.new_infections)\n", - " summary['cum_deaths'] = sum(sim.results.new_deaths)\n", - " \n", - " return df, summary" - ] - }, - { - "cell_type": "markdown", - "id": "14", - "metadata": {}, - "source": [ - "
\n", - "The functions above could be combined into a single function. However, in real world usage it is often convenient to be able to construct a simulation independently of running it (e.g., for diagnostic purposes or to allow running the sim in a range of different ways). The suggested structure above, with a get_sim() function and a run_sim() function are recommended as standard practice.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "15", - "metadata": {}, - "source": [ - "Now running a simulation for a given beta/seed value and returning the processed outputs can be done in a single step" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "16", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'seed': 0, 'p_death': 0.2, 'cum_infections': 9890.0, 'cum_deaths': 2580.0}" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Scalar output\n", - "df, summary = run_sim(0, 0.2);\n", - "summary" - ] - }, - { - "cell_type": "markdown", - "id": "17", - "metadata": {}, - "source": [ - "We can produce all of the samples associated with a scenario by iterating over the input seed values. This is being done in a basic loop here, but could be done in more sophistical ways to leverage parallel computing (e.g., with `sc.parallelize` for single host parallelization, or with `celery` for distributed computation). " - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "18", - "metadata": {}, - "outputs": [], - "source": [ - "# Run a collection of sims\n", - "n = 100\n", - "seeds = np.arange(n)\n", - "outputs = [run_sim(seed, 0.2) for seed in seeds]" - ] - }, - { - "cell_type": "markdown", - "id": "19", - "metadata": {}, - "source": [ - "## Saving and loading the samples" - ] - }, - { - "cell_type": "markdown", - "id": "20", - "metadata": {}, - "source": [ - "We have now produced simulation outputs (dataframes and summary statistics) for 100 simulation runs. The `outputs` here are a list of tuples, containing the dataframe and dictionary outputs for each sample. This list can be passed to the `cvv.Samples` class to produce a single compressed file on disk" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "21", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[PosixPath('results/0.75-1.zip'),\n", - " PosixPath('results/0.75-3.zip'),\n", - " PosixPath('results/0.75-2.zip'),\n", - " PosixPath('results/0.0-2.zip'),\n", - " PosixPath('results/0.2.zip'),\n", - " PosixPath('results/0.0-3.zip'),\n", - " PosixPath('results/0.0-1.zip'),\n", - " PosixPath('results/0.25-3.zip'),\n", - " PosixPath('results/0.25-2.zip'),\n", - " PosixPath('results/0.25-1.zip'),\n", - " PosixPath('results/0.5-3.zip'),\n", - " PosixPath('results/0.5-2.zip'),\n", - " PosixPath('results/0.5-1.zip')]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "resultsdir = Path('results')\n", - "resultsdir.mkdir(exist_ok=True, parents=True)\n", - "ss.Samples.new(resultsdir, outputs, identifiers=[\"p_death\"])\n", - "list(resultsdir.iterdir())" - ] - }, - { - "cell_type": "markdown", - "id": "22", - "metadata": {}, - "source": [ - "Notice that a list of `identifiers` should be passed to the `Samples` constructor. This is a list of keys in the simulation summary dictionaries that identifies the scenario. These would be model inputs rather than model outputs, and they should be the same for all of the outputs passed into the `Samples` object. If no file name is explicitly provided, the file will automatically be assigned a name based on the identifiers.\n", - "\n", - "
\n", - "The Samples file internally contains metadata recording the identifiers. When Samples are accessed using the Dataset class, they can be accessed via the internal metadata. Therefore for a typical workflow, the file name largely doesn't matter, and it usually doesn't need to be manually specified.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "23", - "metadata": {}, - "source": [ - "The saved file can be loaded and accessed via the `Samples` class. **Importantly, individual files can be extracted from a `.zip` file without decompressing the entire archive**. This means that loading the summary dataframe and using it to selectively load the full outputs for individual runs can be done efficiently. For example, loading retrieving a single result from a `Samples` file would take a similar amount of time regardless of whether the file contained 10 samples or 100000 samples. " - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "24", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
cum_infectionscum_deaths
seedp_death
00.29890.02580.0
10.29902.02682.0
20.29894.02724.0
30.29885.02662.0
40.29895.02603.0
............
950.29881.02594.0
960.29886.02655.0
970.29898.02675.0
980.29906.02655.0
990.29897.02665.0
\n", - "

100 rows × 2 columns

\n", - "
" - ], - "text/plain": [ - " cum_infections cum_deaths\n", - "seed p_death \n", - "0 0.2 9890.0 2580.0\n", - "1 0.2 9902.0 2682.0\n", - "2 0.2 9894.0 2724.0\n", - "3 0.2 9885.0 2662.0\n", - "4 0.2 9895.0 2603.0\n", - "... ... ...\n", - "95 0.2 9881.0 2594.0\n", - "96 0.2 9886.0 2655.0\n", - "97 0.2 9898.0 2675.0\n", - "98 0.2 9906.0 2655.0\n", - "99 0.2 9897.0 2665.0\n", - "\n", - "[100 rows x 2 columns]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Load the samples\n", - "res = ss.Samples('results/0.2.zip')\n", - "res.summary" - ] - }, - { - "cell_type": "markdown", - "id": "25", - "metadata": {}, - "source": [ - "When the `Samples` file was created, a dictionary of scalars was provided for each result. These are automatically used to populate a 'summary' dataframe, where each identifier (and the seed) are used as the index, and the remaining keys appear as columns, as shown above. As a shortcut, columns of the summary dataframe can be accessed by indexing the `Samples` object directly, without having to access the `.summary` attribute e.g.," - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "26", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "seed p_death\n", - "0 0.2 9890.0\n", - "1 0.2 9902.0\n", - "2 0.2 9894.0\n", - "3 0.2 9885.0\n", - "4 0.2 9895.0\n", - " ... \n", - "95 0.2 9881.0\n", - "96 0.2 9886.0\n", - "97 0.2 9898.0\n", - "98 0.2 9906.0\n", - "99 0.2 9897.0\n", - "Name: cum_infections, Length: 100, dtype: float64" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res['cum_infections']" - ] - }, - { - "cell_type": "markdown", - "id": "27", - "metadata": {}, - "source": [ - "Each simulation is uniquely identified by its seed, and the time series dataframe for each simulation can be accessed by indexing the `Samples` object with the seed:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "28", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
n_alivenew_deathssir.n_susceptiblesir.n_infectedsir.n_recoveredsir.prevalencesir.new_infectionssir.cum_infections
t
1995.09813.0187.06638.02358.0817.00.2402933362.00.0
1996.09329.0484.03023.03615.02691.00.3875013615.03362.0
1997.08389.0940.0609.02414.05366.00.2877582414.06977.0
1998.07572.0817.0131.0478.06963.00.063127478.09391.0
1999.07422.0150.0110.021.07291.00.00282921.09869.0
2000.07420.02.0110.00.07310.00.0000000.09890.0
2001.07420.00.0110.00.07310.00.0000000.09890.0
2002.07420.00.0110.00.07310.00.0000000.09890.0
2003.07420.00.0110.00.07310.00.0000000.09890.0
2004.07420.00.0110.00.07310.00.0000000.09890.0
2005.07420.00.0110.00.07310.00.0000000.09890.0
2006.07420.00.0110.00.07310.00.0000000.09890.0
2007.07420.00.0110.00.07310.00.0000000.09890.0
2008.07420.00.0110.00.07310.00.0000000.09890.0
2009.07420.00.0110.00.07310.00.0000000.09890.0
2010.07420.00.0110.00.07310.00.0000000.09890.0
2011.07420.00.0110.00.07310.00.0000000.09890.0
2012.07420.00.0110.00.07310.00.0000000.09890.0
2013.07420.00.0110.00.07310.00.0000000.09890.0
2014.07420.00.0110.00.07310.00.0000000.09890.0
2015.07420.00.0110.00.07310.00.0000000.09890.0
2016.07420.00.0110.00.07310.00.0000000.09890.0
2017.07420.00.0110.00.07310.00.0000000.09890.0
2018.07420.00.0110.00.07310.00.0000000.09890.0
2019.07420.00.0110.00.07310.00.0000000.09890.0
2020.07420.00.0110.00.07310.00.0000000.09890.0
2021.07420.00.0110.00.07310.00.0000000.09890.0
2022.07420.00.0110.00.07310.00.0000000.09890.0
2023.07420.00.0110.00.07310.00.0000000.09890.0
2024.07420.00.0110.00.07310.00.0000000.09890.0
2025.07420.00.0110.00.07310.00.0000000.09890.0
2026.07420.00.0110.00.07310.00.0000000.09890.0
2027.07420.00.0110.00.07310.00.0000000.09890.0
2028.07420.00.0110.00.07310.00.0000000.09890.0
2029.07420.00.0110.00.07310.00.0000000.09890.0
2030.07420.00.0110.00.07310.00.0000000.09890.0
\n", - "
" - ], - "text/plain": [ - " n_alive new_deaths sir.n_susceptible sir.n_infected \\\n", - "t \n", - "1995.0 9813.0 187.0 6638.0 2358.0 \n", - "1996.0 9329.0 484.0 3023.0 3615.0 \n", - "1997.0 8389.0 940.0 609.0 2414.0 \n", - "1998.0 7572.0 817.0 131.0 478.0 \n", - "1999.0 7422.0 150.0 110.0 21.0 \n", - "2000.0 7420.0 2.0 110.0 0.0 \n", - "2001.0 7420.0 0.0 110.0 0.0 \n", - "2002.0 7420.0 0.0 110.0 0.0 \n", - "2003.0 7420.0 0.0 110.0 0.0 \n", - "2004.0 7420.0 0.0 110.0 0.0 \n", - "2005.0 7420.0 0.0 110.0 0.0 \n", - "2006.0 7420.0 0.0 110.0 0.0 \n", - "2007.0 7420.0 0.0 110.0 0.0 \n", - "2008.0 7420.0 0.0 110.0 0.0 \n", - "2009.0 7420.0 0.0 110.0 0.0 \n", - "2010.0 7420.0 0.0 110.0 0.0 \n", - "2011.0 7420.0 0.0 110.0 0.0 \n", - "2012.0 7420.0 0.0 110.0 0.0 \n", - "2013.0 7420.0 0.0 110.0 0.0 \n", - "2014.0 7420.0 0.0 110.0 0.0 \n", - "2015.0 7420.0 0.0 110.0 0.0 \n", - "2016.0 7420.0 0.0 110.0 0.0 \n", - "2017.0 7420.0 0.0 110.0 0.0 \n", - "2018.0 7420.0 0.0 110.0 0.0 \n", - "2019.0 7420.0 0.0 110.0 0.0 \n", - "2020.0 7420.0 0.0 110.0 0.0 \n", - "2021.0 7420.0 0.0 110.0 0.0 \n", - "2022.0 7420.0 0.0 110.0 0.0 \n", - "2023.0 7420.0 0.0 110.0 0.0 \n", - "2024.0 7420.0 0.0 110.0 0.0 \n", - "2025.0 7420.0 0.0 110.0 0.0 \n", - "2026.0 7420.0 0.0 110.0 0.0 \n", - "2027.0 7420.0 0.0 110.0 0.0 \n", - "2028.0 7420.0 0.0 110.0 0.0 \n", - "2029.0 7420.0 0.0 110.0 0.0 \n", - "2030.0 7420.0 0.0 110.0 0.0 \n", - "\n", - " sir.n_recovered sir.prevalence sir.new_infections \\\n", - "t \n", - "1995.0 817.0 0.240293 3362.0 \n", - "1996.0 2691.0 0.387501 3615.0 \n", - "1997.0 5366.0 0.287758 2414.0 \n", - "1998.0 6963.0 0.063127 478.0 \n", - "1999.0 7291.0 0.002829 21.0 \n", - "2000.0 7310.0 0.000000 0.0 \n", - "2001.0 7310.0 0.000000 0.0 \n", - "2002.0 7310.0 0.000000 0.0 \n", - "2003.0 7310.0 0.000000 0.0 \n", - "2004.0 7310.0 0.000000 0.0 \n", - "2005.0 7310.0 0.000000 0.0 \n", - "2006.0 7310.0 0.000000 0.0 \n", - "2007.0 7310.0 0.000000 0.0 \n", - "2008.0 7310.0 0.000000 0.0 \n", - "2009.0 7310.0 0.000000 0.0 \n", - "2010.0 7310.0 0.000000 0.0 \n", - "2011.0 7310.0 0.000000 0.0 \n", - "2012.0 7310.0 0.000000 0.0 \n", - "2013.0 7310.0 0.000000 0.0 \n", - "2014.0 7310.0 0.000000 0.0 \n", - "2015.0 7310.0 0.000000 0.0 \n", - "2016.0 7310.0 0.000000 0.0 \n", - "2017.0 7310.0 0.000000 0.0 \n", - "2018.0 7310.0 0.000000 0.0 \n", - "2019.0 7310.0 0.000000 0.0 \n", - "2020.0 7310.0 0.000000 0.0 \n", - "2021.0 7310.0 0.000000 0.0 \n", - "2022.0 7310.0 0.000000 0.0 \n", - "2023.0 7310.0 0.000000 0.0 \n", - "2024.0 7310.0 0.000000 0.0 \n", - "2025.0 7310.0 0.000000 0.0 \n", - "2026.0 7310.0 0.000000 0.0 \n", - "2027.0 7310.0 0.000000 0.0 \n", - "2028.0 7310.0 0.000000 0.0 \n", - "2029.0 7310.0 0.000000 0.0 \n", - "2030.0 7310.0 0.000000 0.0 \n", - "\n", - " sir.cum_infections \n", - "t \n", - "1995.0 0.0 \n", - "1996.0 3362.0 \n", - "1997.0 6977.0 \n", - "1998.0 9391.0 \n", - "1999.0 9869.0 \n", - "2000.0 9890.0 \n", - "2001.0 9890.0 \n", - "2002.0 9890.0 \n", - "2003.0 9890.0 \n", - "2004.0 9890.0 \n", - "2005.0 9890.0 \n", - "2006.0 9890.0 \n", - "2007.0 9890.0 \n", - "2008.0 9890.0 \n", - "2009.0 9890.0 \n", - "2010.0 9890.0 \n", - "2011.0 9890.0 \n", - "2012.0 9890.0 \n", - "2013.0 9890.0 \n", - "2014.0 9890.0 \n", - "2015.0 9890.0 \n", - "2016.0 9890.0 \n", - "2017.0 9890.0 \n", - "2018.0 9890.0 \n", - "2019.0 9890.0 \n", - "2020.0 9890.0 \n", - "2021.0 9890.0 \n", - "2022.0 9890.0 \n", - "2023.0 9890.0 \n", - "2024.0 9890.0 \n", - "2025.0 9890.0 \n", - "2026.0 9890.0 \n", - "2027.0 9890.0 \n", - "2028.0 9890.0 \n", - "2029.0 9890.0 \n", - "2030.0 9890.0 " - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res[0]" - ] - }, - { - "cell_type": "markdown", - "id": "29", - "metadata": {}, - "source": [ - "The dataframes in the `Samples` object are cached, so that the dataframes don't all need to be loaded in order to start working with the file. The first time a dataframe is accessed, it will be loaded from disk. Subsequent requests for the dataframe will return a cached version instead. The cached dataframe is copied each time it is retrieved, to prevent accidentally modifying the original data. " - ] - }, - { - "cell_type": "markdown", - "id": "30", - "metadata": {}, - "source": [ - "## Common analysis operations\n", - "\n", - "Here are some examples of common analyses that can be performed using functionality in the `Samples` class\n", - "\n", - "### Plotting summary quantities\n", - "\n", - "Often it's useful to be able plot distributions of summary quantities, such as the total infections. This can be performed by directly indexing the `Samples` object and then using the appropriate plotting command:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "31", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0, 0.5, 'Probability density')" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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y5crtTgAAABXcDog/frXv1Zubq4k6cCBA4qPj9egQYN0/PhxxcfHV3qBAAAA1c3pL6s9fvy4OnbsKEn6+9//roceeki///3vlZGRoUGDBlV6gQAAANXN6RUkd3d3Xb16VZK0bds2DRgwQJLk5+dnX1kCAACozZxeQbrvvvsUHx+ve++9V7t379a6deskSV988YXuuuuuSi8QgKPQme+5ugQAqPOcXkFaunSp6tevr3feeUfLly9Xy5YtJUn//Oc/NXDgwEovEAAAoLo5vYLUqlUrbd68uVT7K6+8UikFAQAAuJrTAUmSSkpKdOzYMZ0/f14lJSUO2x544IFKKQwAAMBVnA5In3zyiR5//HGdPHlSxhiHbXzVCAAAqAucDkhPP/20evTooffee08tWrQo83vZAAAAajOnA9LRo0f1zjvvqG3btlVRDwAAgMs5/Sm2qKgoHTt2rCpqAQAAqBGcXkGaPHmyZsyYoczMTIWHh6tBgwYO27t06VJpxQEAALiC0wFp6NChkqQnn3zS3maz2WSM4SJtAABQJzh9iu348eOlHl999ZX9v3di2bJlCg0Nlaenp6KiorR79+7b9l+/fr3at28vT09PhYeHa8uWLfZthYWFeu655xQeHi4vLy8FBwdr9OjROnv2rMMYly5d0qhRo+Tt7S1fX1+NGzdOV65cuaP6AQBA3eJ0QGrduvVtH85at26d4uPjlZiYqIyMDEVERCg2Nlbnz58vs/+uXbs0cuRIjRs3Tvv27VNcXJzi4uJ08OBBSdLVq1eVkZGhOXPmKCMjQxs2bNCRI0f08MMPO4wzatQoff7559q6das2b96snTt3asKECU7XDwAA6h6bsd7MqALeeustrVixQsePH1dqaqpat26txYsXq02bNhoyZIhTY0VFRalnz55aunSppBs3oQwJCdHkyZM1c+bMUv2HDx+uvLw8h7t59+7dW5GRkVqxYkWZx9izZ4969eqlkydPqlWrVjp06JA6duyoPXv2qEePHpKk5ORkDRo0SF9//bWCg4O/s+7c3Fz5+PgoJydH3t7eTs0Z+D74LjaU58TCwa4uAajxKvr32+kVpOXLlys+Pl6DBg1Sdna2/ZojX19fLV682KmxCgoKlJ6erpiYmP8U5OammJgYpaamlrlPamqqQ39Jio2NLbe/JOXk5Mhms8nX19c+hq+vrz0cSVJM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AACAusjpi7QnTpyoF198UUVFRVVRDwAAgMs5fQ3Snj17lJKSon/9618KDw+Xl5eXw/YNGzZUWnEAAACu4HRA8vX11dChQ6uiFgAAgBrB6YC0evXqqqgDAACgxqjwNUglJSV68cUXde+996pnz56aOXOmrl27VpW1AQAAuESFA9ILL7ygWbNmqXHjxmrZsqVeffVVTZw4sSprAwAAcIkKB6S//OUvev311/X+++9r48aNevfdd/X222+rpKSkKusDAACodhUOSKdOndKgQYPsz2NiYmSz2XT27NkqKQwAAMBVKhyQioqK5Onp6dDWoEEDFRYWVnpRAAAArlThT7EZY/SLX/xCHh4e9rbr16/r6aefdrgXEvdBAgAAtV2FA9KYMWNKtT3xxBOVWgwAAEBNUOGAxP2PAADAD4XT38UGAABQ1xGQAAAALAhIAAAAFgQkAAAACwISAACABQEJAADAgoAEAABgQUACAACwICABAABYuDwgLVu2TKGhofL09FRUVJR279592/7r169X+/bt5enpqfDwcG3ZssVh+4YNGzRgwAA1a9ZMNptN+/fvLzXG9evXNXHiRDVr1kyNGzfW0KFDlZWVVZnTAgAAtZhLA9K6desUHx+vxMREZWRkKCIiQrGxsTp//nyZ/Xft2qWRI0dq3Lhx2rdvn+Li4hQXF6eDBw/a++Tl5em+++7Tiy++WO5xp0+frnfffVfr16/XBx98oLNnz+rRRx+t9PkBAIDayWaMMa46eFRUlHr27KmlS5dKkkpKShQSEqLJkydr5syZpfoPHz5ceXl52rx5s72td+/eioyM1IoVKxz6njhxQm3atNG+ffsUGRlpb8/JyVHz5s21Zs0a/exnP5MkHT58WB06dFBqaqp69+5dodpzc3Pl4+OjnJwceXt7Ozt14I6FznzP1SUAlebEwsGuLgE/MBX9++2yFaSCggKlp6crJibmP8W4uSkmJkapqall7pOamurQX5JiY2PL7V+W9PR0FRYWOozTvn17tWrV6rbj5OfnKzc31+EBAADqJpcFpIsXL6q4uFiBgYEO7YGBgcrMzCxzn8zMTKf6lzeGu7u7fH19nRonKSlJPj4+9kdISEiFjwkAAGoXl1+kXVskJCQoJyfH/jh9+rSrSwIAAFWkvqsO7O/vr3r16pX69FhWVpaCgoLK3CcoKMip/uWNUVBQoOzsbIdVpO8ax8PDQx4eHhU+DgAAqL1ctoLk7u6u7t27KyUlxd5WUlKilJQURUdHl7lPdHS0Q39J2rp1a7n9y9K9e3c1aNDAYZwjR47o1KlTTo0DAADqLpetIElSfHy8xowZox49eqhXr15avHix8vLyNHbsWEnS6NGj1bJlSyUlJUmSpk6dqr59+2rRokUaPHiw1q5dq71792rlypX2MS9duqRTp07p7Nmzkm6EH+nGylFQUJB8fHw0btw4xcfHy8/PT97e3po8ebKio6Mr/Ak2AABQt7k0IA0fPlwXLlzQ3LlzlZmZqcjISCUnJ9svxD516pTc3P6zyNWnTx+tWbNGs2fP1qxZsxQWFqaNGzeqc+fO9j6bNm2yByxJGjFihCQpMTFR8+bNkyS98sorcnNz09ChQ5Wfn6/Y2Fi9/vrr1TBjAABQG7j0Pki1GfdBgqtwHyTUJdwHCdWton+/XbqCBLgaYQOAs2rjvxsEUefxMX8AAAALAhIAAIAFAQkAAMCCgAQAAGBBQAIAALAgIAEAAFgQkAAAACwISAAAABYEJAAAAAsCEgAAgAUBCQAAwIKABAAAYEFAAgAAsCAgAQAAWBCQAAAALAhIAAAAFgQkAAAACwISAACABQEJAADAgoAEAABgQUACAACwICABAABYEJAAAAAsCEgAAAAWBCQAAAALAhIAAIAFAQkAAMCCgAQAAGBBQAIAALAgIAEAAFgQkAAAACwISAAAABYEJAAAAAsCEgAAgAUBCQAAwIKABAAAYEFAAgAAsCAgAQAAWBCQAAAALAhIAAAAFgQkAAAAi/quLkCSli1bpj/84Q/KzMxURESEXnvtNfXq1avc/uvXr9ecOXN04sQJhYWF6cUXX9SgQYPs240xSkxM1BtvvKHs7Gzde++9Wr58ucLCwux9QkNDdfLkSYdxk5KSNHPmzMqfoJNCZ77n6hKcdmLhYFeXAABApXH5CtK6desUHx+vxMREZWRkKCIiQrGxsTp//nyZ/Xft2qWRI0dq3Lhx2rdvn+Li4hQXF6eDBw/a+7z00ktasmSJVqxYobS0NHl5eSk2NlbXr193GGvBggU6d+6c/TF58uQqnSsAAKgdXB6QXn75ZY0fP15jx45Vx44dtWLFCjVq1Ehvvvlmmf1fffVVDRw4UL/+9a/VoUMHPf/88+rWrZuWLl0q6cbq0eLFizV79mwNGTJEXbp00V/+8hedPXtWGzdudBirSZMmCgoKsj+8vLyqeroAAKAWcGlAKigoUHp6umJiYuxtbm5uiomJUWpqapn7pKamOvSXpNjYWHv/48ePKzMz06GPj4+PoqKiSo25cOFCNWvWTF27dtUf/vAHFRUVlVtrfn6+cnNzHR4AAKBucuk1SBcvXlRxcbECAwMd2gMDA3X48OEy98nMzCyzf2Zmpn37zbby+kjSlClT1K1bN/n5+WnXrl1KSEjQuXPn9PLLL5d53KSkJM2fP9+5CQIAgFqpRlyk7Qrx8fH2n7t06SJ3d3c99dRTSkpKkoeHR6n+CQkJDvvk5uYqJCSkWmoFAADVy6Wn2Pz9/VWvXj1lZWU5tGdlZSkoKKjMfYKCgm7b/+Z/nRlTkqKiolRUVKQTJ06Uud3Dw0Pe3t4ODwAAUDe5NCC5u7ure/fuSklJsbeVlJQoJSVF0dHRZe4THR3t0F+Stm7dau/fpk0bBQUFOfTJzc1VWlpauWNK0v79++Xm5qaAgIDvMyUAAFAHuPwUW3x8vMaMGaMePXqoV69eWrx4sfLy8jR27FhJ0ujRo9WyZUslJSVJkqZOnaq+fftq0aJFGjx4sNauXau9e/dq5cqVkiSbzaZp06bpd7/7ncLCwtSmTRvNmTNHwcHBiouLk3TjQu+0tDQ9+OCDatKkiVJTUzV9+nQ98cQTatq0qUteBwAAUHO4PCANHz5cFy5c0Ny5c5WZmanIyEglJyfbL7I+deqU3Nz+s9DVp08frVmzRrNnz9asWbMUFhamjRs3qnPnzvY+v/nNb5SXl6cJEyYoOztb9913n5KTk+Xp6SnpxumytWvXat68ecrPz1ebNm00ffp0h2uMAADAD5fNGGNcXURtlJubKx8fH+Xk5FT69UjcSbv61MbXGqhLauO/HbXx343a+DpXlYr+/Xb5jSIBAABqGgISAACABQEJAADAgoAEAABgQUACAACwICABAABYEJAAAAAsCEgAAAAWBCQAAAALAhIAAIAFAQkAAMCCgAQAAGBBQAIAALAgIAEAAFgQkAAAACwISAAAABYEJAAAAAsCEgAAgIXNGGNcXURtlJubKx8fH+Xk5Mjb27tSxw6d+V6ljgcAQG1zYuHgKhm3on+/WUECAACwICABAABYEJAAAAAsCEgAAAAWBCQAAAALAhIAAIAFAQkAAMCCgAQAAGBBQAIAALAgIAEAAFgQkAAAACwISAAAABYEJAAAAAsCEgAAgAUBCQAAwIKABAAAYEFAAgAAsCAgAQAAWBCQAAAALAhIAAAAFgQkAAAACwISAACABQEJAADAokYEpGXLlik0NFSenp6KiorS7t27b9t//fr1at++vTw9PRUeHq4tW7Y4bDfGaO7cuWrRooUaNmyomJgYHT161KHPpUuXNGrUKHl7e8vX11fjxo3TlStXKn1uAACg9nF5QFq3bp3i4+OVmJiojIwMRUREKDY2VufPny+z/65duzRy5EiNGzdO+/btU1xcnOLi4nTw4EF7n5deeklLlizRihUrlJaWJi8vL8XGxur69ev2PqNGjdLnn3+urVu3avPmzdq5c6cmTJhQ5fMFAAA1n80YY1xZQFRUlHr27KmlS5dKkkpKShQSEqLJkydr5syZpfoPHz5ceXl52rx5s72td+/eioyM1IoVK2SMUXBwsGbMmKFnn31WkpSTk6PAwED9z//8j0aMGKFDhw6pY8eO2rNnj3r06CFJSk5O1qBBg/T1118rODj4O+vOzc2Vj4+PcnJy5O3tXRkvhV3ozPcqdTwAAGqbEwsHV8m4Ff37Xb9Kjl5BBQUFSk9PV0JCgr3Nzc1NMTExSk1NLXOf1NRUxcfHO7TFxsZq48aNkqTjx48rMzNTMTEx9u0+Pj6KiopSamqqRowYodTUVPn6+trDkSTFxMTIzc1NaWlpeuSRR0odNz8/X/n5+fbnOTk5km680JWtJP9qpY8JAEBtUhV/X28d97vWh1wakC5evKji4mIFBgY6tAcGBurw4cNl7pOZmVlm/8zMTPv2m2236xMQEOCwvX79+vLz87P3sUpKStL8+fNLtYeEhJQ3PQAAcId8Flft+N9++618fHzK3e7SgFSbJCQkOKxclZSU6NKlS2rWrJlsNpvL6srNzVVISIhOnz5d6af6aqIf2nylH96cmW/dxnzrttowX2OMvv322++8nMalAcnf31/16tVTVlaWQ3tWVpaCgoLK3CcoKOi2/W/+NysrSy1atHDoExkZae9jvQi8qKhIly5dKve4Hh4e8vDwcGjz9fW9/QSrkbe3d439ZawKP7T5Sj+8OTPfuo351m01fb63Wzm6yaWfYnN3d1f37t2VkpJibyspKVFKSoqio6PL3Cc6OtqhvyRt3brV3r9NmzYKCgpy6JObm6u0tDR7n+joaGVnZys9Pd3eZ/v27SopKVFUVFSlzQ8AANROLj/FFh8frzFjxqhHjx7q1auXFi9erLy8PI0dO1aSNHr0aLVs2VJJSUmSpKlTp6pv375atGiRBg8erLVr12rv3r1auXKlJMlms2natGn63e9+p7CwMLVp00Zz5sxRcHCw4uLiJEkdOnTQwIEDNX78eK1YsUKFhYWaNGmSRowYUaFPsAEAgLrN5QFp+PDhunDhgubOnavMzExFRkYqOTnZfpH1qVOn5Ob2n4WuPn36aM2aNZo9e7ZmzZqlsLAwbdy4UZ07d7b3+c1vfqO8vDxNmDBB2dnZuu+++5ScnCxPT097n7fffluTJk1S//795ebmpqFDh2rJkiXVN/FK4uHhocTExFKn/+qqH9p8pR/enJlv3cZ867a6NF+X3wcJAACgpnH5nbQBAABqGgISAACABQEJAADAgoAEAABgQUCqIb799ltNmzZNrVu3VsOGDdWnTx/t2bPHvv3KlSuaNGmS7rrrLjVs2FAdO3bUihUrSo2Tmpqq//qv/5KXl5e8vb31wAMP6Nq1a/btly5d0qhRo+Tt7S1fX1+NGzdOV65cqZY53ur7zvfEiROy2WxlPtavX2/vd+rUKQ0ePFiNGjVSQECAfv3rX6uoqKha5ypVzvubmZmpn//85woKCpKXl5e6deumv//97w596sr7K0lffvmlHnnkETVv3lze3t4aNmxYqZvE1pb5ZmVl6Re/+IWCg4PVqFEjDRw4UEePHnUY4/r165o4caKaNWumxo0ba+jQoaXmW1t+nysy35UrV6pfv37y9vaWzWZTdnZ2qePUlff30qVLmjx5stq1a6eGDRuqVatWmjJliv07PW+qS+/vU089pbvvvlsNGzZU8+bNNWTIkFJfIVZT5lsugxph2LBhpmPHjuaDDz4wR48eNYmJicbb29t8/fXXxhhjxo8fb+6++26zY8cOc/z4cfOnP/3J1KtXz/zjH/+wj7Fr1y7j7e1tkpKSzMGDB83hw4fNunXrzPXr1+19Bg4caCIiIswnn3xiPvzwQ9O2bVszcuTIWjffoqIic+7cOYfH/PnzTePGjc23335r79O5c2cTExNj9u3bZ7Zs2WL8/f1NQkJCrZuvMcb8+Mc/Nj179jRpaWnmyy+/NM8//7xxc3MzGRkZ9j515f29cuWK+dGPfmQeeeQR89lnn5nPPvvMDBkyxPTs2dMUFxfXqvmWlJSY3r17m/vvv9/s3r3bHD582EyYMMG0atXKXLlyxT7G008/bUJCQkxKSorZu3ev6d27t+nTp499e235fa7ofF955RWTlJRkkpKSjCRz+fLlUsepK+/vgQMHzKOPPmo2bdpkjh07ZlJSUkxYWJgZOnSo/Rh17f3905/+ZD744ANz/Phxk56ebh566CETEhJiioqKatx8y0NAqgGuXr1q6tWrZzZv3uzQ3q1bN/Pb3/7WGGNMp06dzIIFC8rdbowxUVFRZvbs2eUe59///reRZPbs2WNv++c//2lsNps5c+ZMZUylQiprvlaRkZHmySeftD/fsmWLcXNzM5mZmfa25cuXG29vb5Ofn18ZU6mQypqvl5eX+ctf/uLQx8/Pz7zxxhvGmLr1/r7//vvGzc3N5OTk2LdnZ2cbm81mtm7daoypPfM9cuSIkWQOHjxo31ZcXGyaN29uf++ys7NNgwYNzPr16+19Dh06ZCSZ1NRUY0zt+X2uyHxvtWPHjjIDUl16f8vyt7/9zbi7u5vCwkJjTN19f2/69NNPjSRz7NgxY0zNme/tcIqtBigqKlJxcbHDjSwlqWHDhvroo48k3bhB5qZNm3TmzBkZY7Rjxw598cUXGjBggCTp/PnzSktLU0BAgPr06aPAwED17dvXvr904/Sbr6+vevToYW+LiYmRm5ub0tLSqmGmN1TGfK3S09O1f/9+jRs3zt6Wmpqq8PBw+01HJSk2Nla5ubn6/PPPq2BmZaus+fbp00fr1q3TpUuXVFJSorVr1+r69evq16+fpLr1/ubn58tmszncbM7T01Nubm72MWrLfPPz8+313+Tm5iYPDw/7XNLT01VYWKiYmBh7n/bt26tVq1ZKTU2VVHt+nysy34qoS+9vWXJycuTt7a369W/cr7kuv795eXlavXq12rRpo5CQEEk1Z7635dp8hpuio6NN3759zZkzZ0xRUZF56623jJubm7nnnnuMMcZcv37djB492kgy9evXN+7u7ubPf/6zff/U1FQjyfj5+Zk333zTZGRkmGnTphl3d3fzxRdfGGOMeeGFF+zj3ap58+bm9ddfr56J/v++73ytfvWrX5kOHTo4tI0fP94MGDDAoS0vL89IMlu2bKn8Sd1GZcz38uXLZsCAAfY+3t7e5v3337dvr0vv7/nz5423t7eZOnWqycvLM1euXDGTJk0yksyECROMMbVnvgUFBaZVq1bmscceM5cuXTL5+flm4cKFRpL99/Ptt9827u7upcbt2bOn+c1vfmOMqT2/zxWZ763KW0GqS++v1YULF0yrVq3MrFmz7G118f1dtmyZ8fLyMpJMu3bt7KtHxtSs+ZaHFaQa4q233pIxRi1btpSHh4eWLFmikSNH2r9m5bXXXtMnn3yiTZs2KT09XYsWLdLEiRO1bds2STe+5Fe6cWHc2LFj1bVrV73yyitq166d3nzzTZfNqzzfd763unbtmtasWeOwelTTVMZ858yZo+zsbG3btk179+5VfHy8hg0bpgMHDrhqWuX6vvNt3ry51q9fr3fffVeNGzeWj4+PsrOz1a1bN4evHqopbjffBg0aaMOGDfriiy/k5+enRo0aaceOHfrJT35SI+dSEcz3zuebm5urwYMHq2PHjpo3b171T6YCKmu+o0aN0r59+/TBBx/onnvu0bBhw3T9+nUXzeoOuC6boSxXrlwxZ8+eNcbcuFBu0KBB5urVq6ZBgwalzgmPGzfOxMbGGmOM+eqrr4wk89Zbbzn0GTZsmHn88ceNMcasWrXK+Pr6OmwvLCw09erVMxs2bKiqKd3Wnc73Vn/5y19MgwYNzPnz5x3a58yZYyIiIhzabr5Ot17YXJ3udL7Hjh0rdd7fGGP69+9vnnrqKWNM3X1/L1y4YF9dCAwMNC+99JIxpvbM91bZ2dn239NevXqZZ555xhhjTEpKSpmrKK1atTIvv/yyMab2/D7fqrz53qq8FaS69P7elJuba6Kjo03//v3NtWvXHLbV1ff3pvz8fNOoUSOzZs0aY0zNnK9V7YzzdZiXl5datGihy5cv6/3339eQIUNUWFiowsLCUum8Xr169pWj0NBQBQcH68iRIw59vvjiC7Vu3VqSFB0drezsbKWnp9u3b9++XSUlJYqKiqrimZXtTud7q1WrVunhhx9W8+bNHdqjo6N14MABnT9/3t62detWeXt7q2PHjlUzoe9wp/O9evWqJN22T119f/39/eXr66vt27fr/PnzevjhhyXVnvneysfHR82bN9fRo0e1d+9e+/bu3burQYMGSklJsfc9cuSITp06pejoaEm15/f5VuXNtyLq0vsr3Vg5GjBggNzd3bVp06ZS1/jU9ffX3PhQmP0appo431JcndBwQ3JysvnnP/9pvvrqK/Ovf/3LREREmKioKFNQUGCMMaZv376mU6dOZseOHearr74yq1evNp6eng7n4l955RXj7e1t1q9fb44ePWpmz55tPD09Hc77Dhw40HTt2tWkpaWZjz76yISFhbnkY7OVMV9jjDl69Kix2Wzmn//8Z6lj3PwY6YABA8z+/ftNcnKyad68uUs+Rvp951tQUGDatm1r7r//fpOWlmaOHTtm/vjHPxqbzWbee+89+3Hq0vv75ptvmtTUVHPs2DHz1ltvGT8/PxMfH+9wnNoy37/97W9mx44d5ssvvzQbN240rVu3No8++qjDGE8//bRp1aqV2b59u9m7d6+Jjo420dHR9u216fe5IvM9d+6c2bdvn3njjTeMJLNz506zb98+880339j71JX3Nycnx0RFRZnw8HBz7Ngxh9uTWD/2Xhfe3y+//NL8/ve/N3v37jUnT540H3/8sXnooYeMn5+fycrKqnHzLQ8BqYZYt26d+dGPfmTc3d1NUFCQmThxosnOzrZvP3funPnFL35hgoODjaenp2nXrp1ZtGiRKSkpcRgnKSnJ3HXXXaZRo0YmOjrafPjhhw7bv/nmGzNy5EjTuHFj4+3tbcaOHWu/b1B1qqz5JiQkmJCQEId749zqxIkT5ic/+Ylp2LCh8ff3NzNmzLB/rLY6VcZ8v/jiC/Poo4+agIAA06hRI9OlS5dSH/uvS+/vc889ZwIDA02DBg1MWFhYme9/bZnvq6++au666y7ToEED06pVKzN79uxSH2W+du2aeeaZZ0zTpk1No0aNzCOPPGLOnTvn0Ke2/D5XZL6JiYlGUqnH6tWr7X3qyvt78zRiWY/jx4/b+9WV9/fMmTPmJz/5iQkICDANGjQwd911l3n88cfN4cOHHY5TU+ZbHpsxxrhm7QoAAKBm4hokAAAACwISAACABQEJAADAgoAEAABgQUACAACwICABAABYEJAAAAAsCEgAajWbzaaNGzeWu71fv36aNm2aU2MePnxYvXv3lqenpyIjI79Xfbczb968Kh0fwJ0jIAGoFDab7baP231z+YkTJ2Sz2bR///5Kr2vDhg16/vnnndonMTFRXl5eOnLkiMP3o30fZQW5Z599ttLGB1C56ru6AAB1w7lz5+w/r1u3TnPnznX48uTGjRu7oiz5+fk5vc+XX36pwYMH27/ouao0btzYZa8LgNtjBQlApQgKCrI/fHx8ZLPZ7M8DAgL08ssv66677pKHh4ciIyOVnJxs37dNmzaSpK5du8pms6lfv36SpD179ujHP/6x/P395ePjo759+yojI8Opuqyn2EJDQ/X73/9eTz75pJo0aaJWrVpp5cqV9u02m03p6elasGCBw8rX6dOnNWzYMPn6+srPz09DhgzRiRMnHI715ptvqlOnTvLw8FCLFi00adIk+zEl6ZFHHpHNZrM/t55iKykp0YIFC8p9nW6utG3YsEEPPvigGjVqpIiICKWmptr7nDx5Ug899JCaNm0qLy8vderUSVu2bHHqNQNAQAJQDV599VUtWrRIf/zjH/XZZ58pNjZWDz/8sI4ePSpJ2r17tyRp27ZtOnfunDZs2CBJ+vbbbzVmzBh99NFH+uSTTxQWFqZBgwbp22+//V71LFq0SD169NC+ffv0zDPP6Fe/+pV9tevcuXPq1KmTZsyYoXPnzunZZ59VYWGhYmNj1aRJE3344Yf6+OOP1bhxYw0cOFAFBQWSpOXLl2vixImaMGGCDhw4oE2bNqlt27aSbgQ9SVq9erXOnTtnf+7s63TTb3/7Wz377LPav3+/7rnnHo0cOVJFRUWSpIkTJyo/P187d+7UgQMH9OKLL7JKBdwJV39bLoC6Z/Xq1cbHx8f+PDg42LzwwgsOfXr27GmeeeYZY4wxx48fN5LMvn37bjtucXGxadKkiXn33XftbZLM//t//6/cffr27WumTp1qf966dWvzxBNP2J+XlJSYgIAAs3z5cntbRESESUxMtD9/6623TLt27UxJSYm9LT8/3zRs2NC8//779jn+9re/LbeOsupMTEw0ERER9ucVfZ3++7//2779888/N5LMoUOHjDHGhIeHm3nz5pVbB4CKYQUJQJXKzc3V2bNnde+99zq033vvvTp06NBt983KytL48eMVFhYmHx8feXt768qVKzp16tT3qqlLly72n2+eCjx//ny5/T/99FMdO3ZMTZo0sV835Ofnp+vXr+vLL7/U+fPndfbsWfXv3/+Oa3Lmdbq1/hYtWkiSvf4pU6bod7/7ne69914lJibqs88+u+OagB8yLtIGUGONGTNG33zzjV599VW1bt1aHh4eio6Otp/WulMNGjRweG6z2VRSUlJu/ytXrqh79+56++23S21r3ry53Nyq9/81b63fZrNJkr3+X/7yl4qNjdV7772nf/3rX0pKStKiRYs0efLkaq0RqO1YQQJQpby9vRUcHKyPP/7Yof3jjz9Wx44dJUnu7u6SpOLi4lJ9pkyZokGDBtkvfr548WL1FH6Lbt266ejRowoICFDbtm0dHj4+PmrSpIlCQ0Nv+5H9Bg0alJrfrSryOlVUSEiInn76aW3YsEEzZszQG2+84dT+AAhIAKrBr3/9a7344otat26djhw5opkzZ2r//v2aOnWqJCkgIEANGzZUcnKysrKylJOTI0kKCwvTW2+9pUOHDiktLU2jRo1Sw4YNq73+UaNGyd/fX0OGDNGHH36o48eP6//+7/80ZcoUff3115JufCJt0aJFWrJkiY4ePaqMjAy99tpr9jFuBqjMzExdvny5zON81+tUEdOmTdP777+v48ePKyMjQzt27FCHDh2+3wsA/AARkABUuSlTpig+Pl4zZsxQeHi4kpOTtWnTJoWFhUmS6tevryVLluhPf/qTgoODNWTIEEnSqlWrdPnyZXXr1k0///nPNWXKFAUEBFR7/Y0aNdLOnTvVqlUrPfroo+rQoYPGjRun69evy9vbW9KN04GLFy/W66+/rk6dOumnP/2pw6fPFi1apK1btyokJERdu3Yt8zjf9TpVRHFxsSZOnKgOHTpo4MCBuueee/T6669/vxcA+AGyGWOMq4sAAACoSVhBAgAAsCAgAQAAWBCQAAAALAhIAAAAFgQkAAAACwISAACABQEJAADAgoAEAABgQUACAACwICABAABYEJAAAAAsCEgAAAAW/x9NtN2tmNaIugAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.hist(res['cum_infections'], density=True)\n", - "\n", - "plt.xlabel('Total infections')\n", - "plt.ylabel('Probability density')" - ] - }, - { - "cell_type": "markdown", - "id": "32", - "metadata": {}, - "source": [ - "### Plotting time series\n", - "\n", - "Time series plots can be obtained by accessing the dataframes associated with each seed, and then plotting quantities from those. For convenience, iterating over the `Samples` object will automatically iterate over all of the dataframes associated with each seed. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "33", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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MoFAYND3lwihkDHXXWdMcbExtniAfcyZY8xbbyW6dQAAjKxj/fs9pDkwgUBAklRSUiJJ2rx5s7q6ujR37tzINSeddJImT56smpoaSVJNTY1OOeUUlZWVRa6pqqpSMBjUjh07ItfEvkb4mvBrpLOsLCk/v/9S6pYWux8MAACQsj/6koH19vZq8eLF+vSnP62TTz5ZklRXV6ecnBwVFxc7ri0rK1NdXV3kmtjwEn4+/NyHXRMMBtXe3q68vuuMJYVCIYViJokEg8HB3lpCZWVF58HEii3o6HIlpm0AACSLQffAVFdXa/v27XryySeHsz2Dtnz5cvn9/sijoqIi0U0atMLC/gGmvZ16SAAAhA0qwCxatEirV6/Wiy++qEmTJkXOl5eXq7OzU01NTY7r6+vrVV5eHrmm76qk8PFHXePz+QbsfZGkZcuWKRAIRB779+8fzK0lBZ/PhphYHR12GIkAAwBAnAHGGKNFixbp2Wef1YYNGzR16lTH87NmzZLH49H69esj53bu3Kl9+/apsrJSklRZWalt27apoaEhcs26devk8/k0c+bMyDWxrxG+JvwaA/F6vfL5fI5Hqios7L8KqauLAAMAQFhcc2Cqq6v1xBNP6Ne//rWKiooic1b8fr/y8vLk9/t19dVXa8mSJSopKZHP59MNN9ygyspKzZkzR5I0b948zZw5U1deeaXuvPNO1dXV6ZZbblF1dbW8fx83ue6663Tfffdp6dKl+sY3vqENGzbo6aef1po1a4b59pOT32+XUsfq7paOHCHAAAAgxdkD8+CDDyoQCOi8887ThAkTIo+nnnoqcs2KFSv0xS9+UQsWLNC5556r8vJyPfPMM5Hns7KytHr1amVlZamyslJf/epXddVVV+n222+PXDN16lStWbNG69at02mnnaa7775bjzzyiKqqqobhlpNfQYGUk+M8191t94IhwAAAILmMMSbRjRgJwWBQfr9fgUAg5YaTamqk//kf6fnn7aqjsNtuk777XVuxGgCAdHSsf7+phZSECgttD0zfeTDBIAUdAQCQCDBJKT9/4HICjY2UEwAAQCLAJKX8fNsD03cvmMZGduMFAEAiwCSlcEFHj8d5/sgRAgwAABIBJinl5ERDTKzWVoaQAACQCDBJKTs7OpE3VnMzPTAAAEgEmKQUrkidn+88395uA0x6LnwHAODYEWCSUFaW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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for df in res:\n", - " plt.plot(df['sir.new_infections'], color='b', alpha=0.1)" - ] - }, - { - "cell_type": "markdown", - "id": "34", - "metadata": {}, - "source": [ - "### Other ways to access content\n", - "\n", - "We have seen so far that we can use\n", - "\n", - "- `res.summary` - retrieve dataframe of summary outputs\n", - "- `res[summary_column]` - retrieve a column of the summary dataframe\n", - "- `res[seed]` - retrieve the time series dataframe associated with one of the simulations\n", - "- `for df in res` - iterate over time series dataframes\n", - "\n", - "Sometimes it is useful to have access to both the summary dictionary and the time series dataframe associated with a single sample. These can be accessed using the `get` method, which takes in a seed, and returns both outputs for that seed together:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "35", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(#0. 'p_death': 0.2\n", - " #1. 'cum_infections': 9890.0\n", - " #2. 'cum_deaths': 2580.0,\n", - " n_alive new_deaths sir.n_susceptible sir.n_infected \\\n", - " t \n", - " 1995.0 9813.0 187.0 6638.0 2358.0 \n", - " 1996.0 9329.0 484.0 3023.0 3615.0 \n", - " 1997.0 8389.0 940.0 609.0 2414.0 \n", - " 1998.0 7572.0 817.0 131.0 478.0 \n", - " 1999.0 7422.0 150.0 110.0 21.0 \n", - " 2000.0 7420.0 2.0 110.0 0.0 \n", - " 2001.0 7420.0 0.0 110.0 0.0 \n", - " 2002.0 7420.0 0.0 110.0 0.0 \n", - " 2003.0 7420.0 0.0 110.0 0.0 \n", - " 2004.0 7420.0 0.0 110.0 0.0 \n", - " 2005.0 7420.0 0.0 110.0 0.0 \n", - " 2006.0 7420.0 0.0 110.0 0.0 \n", - " 2007.0 7420.0 0.0 110.0 0.0 \n", - " 2008.0 7420.0 0.0 110.0 0.0 \n", - " 2009.0 7420.0 0.0 110.0 0.0 \n", - " 2010.0 7420.0 0.0 110.0 0.0 \n", - " 2011.0 7420.0 0.0 110.0 0.0 \n", - " 2012.0 7420.0 0.0 110.0 0.0 \n", - " 2013.0 7420.0 0.0 110.0 0.0 \n", - " 2014.0 7420.0 0.0 110.0 0.0 \n", - " 2015.0 7420.0 0.0 110.0 0.0 \n", - " 2016.0 7420.0 0.0 110.0 0.0 \n", - " 2017.0 7420.0 0.0 110.0 0.0 \n", - " 2018.0 7420.0 0.0 110.0 0.0 \n", - " 2019.0 7420.0 0.0 110.0 0.0 \n", - " 2020.0 7420.0 0.0 110.0 0.0 \n", - " 2021.0 7420.0 0.0 110.0 0.0 \n", - " 2022.0 7420.0 0.0 110.0 0.0 \n", - " 2023.0 7420.0 0.0 110.0 0.0 \n", - " 2024.0 7420.0 0.0 110.0 0.0 \n", - " 2025.0 7420.0 0.0 110.0 0.0 \n", - " 2026.0 7420.0 0.0 110.0 0.0 \n", - " 2027.0 7420.0 0.0 110.0 0.0 \n", - " 2028.0 7420.0 0.0 110.0 0.0 \n", - " 2029.0 7420.0 0.0 110.0 0.0 \n", - " 2030.0 7420.0 0.0 110.0 0.0 \n", - " \n", - " sir.n_recovered sir.prevalence sir.new_infections \\\n", - " t \n", - " 1995.0 817.0 0.240293 3362.0 \n", - " 1996.0 2691.0 0.387501 3615.0 \n", - " 1997.0 5366.0 0.287758 2414.0 \n", - " 1998.0 6963.0 0.063127 478.0 \n", - " 1999.0 7291.0 0.002829 21.0 \n", - " 2000.0 7310.0 0.000000 0.0 \n", - " 2001.0 7310.0 0.000000 0.0 \n", - " 2002.0 7310.0 0.000000 0.0 \n", - " 2003.0 7310.0 0.000000 0.0 \n", - " 2004.0 7310.0 0.000000 0.0 \n", - " 2005.0 7310.0 0.000000 0.0 \n", - " 2006.0 7310.0 0.000000 0.0 \n", - " 2007.0 7310.0 0.000000 0.0 \n", - " 2008.0 7310.0 0.000000 0.0 \n", - " 2009.0 7310.0 0.000000 0.0 \n", - " 2010.0 7310.0 0.000000 0.0 \n", - " 2011.0 7310.0 0.000000 0.0 \n", - " 2012.0 7310.0 0.000000 0.0 \n", - " 2013.0 7310.0 0.000000 0.0 \n", - " 2014.0 7310.0 0.000000 0.0 \n", - " 2015.0 7310.0 0.000000 0.0 \n", - " 2016.0 7310.0 0.000000 0.0 \n", - " 2017.0 7310.0 0.000000 0.0 \n", - " 2018.0 7310.0 0.000000 0.0 \n", - " 2019.0 7310.0 0.000000 0.0 \n", - " 2020.0 7310.0 0.000000 0.0 \n", - " 2021.0 7310.0 0.000000 0.0 \n", - " 2022.0 7310.0 0.000000 0.0 \n", - " 2023.0 7310.0 0.000000 0.0 \n", - " 2024.0 7310.0 0.000000 0.0 \n", - " 2025.0 7310.0 0.000000 0.0 \n", - " 2026.0 7310.0 0.000000 0.0 \n", - " 2027.0 7310.0 0.000000 0.0 \n", - " 2028.0 7310.0 0.000000 0.0 \n", - " 2029.0 7310.0 0.000000 0.0 \n", - " 2030.0 7310.0 0.000000 0.0 \n", - " \n", - " sir.cum_infections \n", - " t \n", - " 1995.0 0.0 \n", - " 1996.0 3362.0 \n", - " 1997.0 6977.0 \n", - " 1998.0 9391.0 \n", - " 1999.0 9869.0 \n", - " 2000.0 9890.0 \n", - " 2001.0 9890.0 \n", - " 2002.0 9890.0 \n", - " 2003.0 9890.0 \n", - " 2004.0 9890.0 \n", - " 2005.0 9890.0 \n", - " 2006.0 9890.0 \n", - " 2007.0 9890.0 \n", - " 2008.0 9890.0 \n", - " 2009.0 9890.0 \n", - " 2010.0 9890.0 \n", - " 2011.0 9890.0 \n", - " 2012.0 9890.0 \n", - " 2013.0 9890.0 \n", - " 2014.0 9890.0 \n", - " 2015.0 9890.0 \n", - " 2016.0 9890.0 \n", - " 2017.0 9890.0 \n", - " 2018.0 9890.0 \n", - " 2019.0 9890.0 \n", - " 2020.0 9890.0 \n", - " 2021.0 9890.0 \n", - " 2022.0 9890.0 \n", - " 2023.0 9890.0 \n", - " 2024.0 9890.0 \n", - " 2025.0 9890.0 \n", - " 2026.0 9890.0 \n", - " 2027.0 9890.0 \n", - " 2028.0 9890.0 \n", - " 2029.0 9890.0 \n", - " 2030.0 9890.0 )" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res.get(0) # Retrieve both summary quantities and dataframes" - ] - }, - { - "cell_type": "markdown", - "id": "36", - "metadata": {}, - "source": [ - "In the same way that it is possible to index the `Samples` object directly in order to retrieve columns from the summary dataframe, it is also possible to directly index the `Samples` object to get a column of the time series dataframe. In this case, pass a tuple of items to the `Samples` object, where the first item is the seed, and the second is a column from the time series dataframe. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "37", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "t\n", - "1995.0 2358.0\n", - "1996.0 3615.0\n", - "1997.0 2414.0\n", - "1998.0 478.0\n", - "1999.0 21.0\n", - "2000.0 0.0\n", - "2001.0 0.0\n", - "2002.0 0.0\n", - "2003.0 0.0\n", - "2004.0 0.0\n", - "2005.0 0.0\n", - "2006.0 0.0\n", - "2007.0 0.0\n", - "2008.0 0.0\n", - "2009.0 0.0\n", - "2010.0 0.0\n", - "2011.0 0.0\n", - "2012.0 0.0\n", - "2013.0 0.0\n", - "2014.0 0.0\n", - "2015.0 0.0\n", - "2016.0 0.0\n", - "2017.0 0.0\n", - "2018.0 0.0\n", - "2019.0 0.0\n", - "2020.0 0.0\n", - "2021.0 0.0\n", - "2022.0 0.0\n", - "2023.0 0.0\n", - "2024.0 0.0\n", - "2025.0 0.0\n", - "2026.0 0.0\n", - "2027.0 0.0\n", - "2028.0 0.0\n", - "2029.0 0.0\n", - "2030.0 0.0\n", - "Name: sir.n_infected, dtype: float64" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res[0,'sir.n_infected'] # Equivalent to `res[0]['sir.n_infected']`" - ] - }, - { - "cell_type": "markdown", - "id": "38", - "metadata": {}, - "source": [ - "### Filtering results" - ] - }, - { - "cell_type": "markdown", - "id": "39", - "metadata": {}, - "source": [ - "The `.seeds` attribute contains a listing of seeds, which can be helpful for iteration" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "40", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", - " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", - " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", - " 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,\n", - " 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,\n", - " 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res.seeds" - ] - }, - { - "cell_type": "markdown", - "id": "41", - "metadata": {}, - "source": [ - "The seeds are drawn from the summary dataframe, which defines which seeds are accessible via the `Samples` object. Therefore, you can drop rows from the summary dataframe to filter the results. For example, suppose we only wanted to analyze simulations with over 21000 deaths. We could retrieve a copy of the summary dataframe that only contains matching simulations" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "42", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
cum_infectionscum_deaths
seedp_death
10.29902.02682.0
60.29908.02730.0
140.29903.02721.0
190.29907.02712.0
250.29916.02762.0
260.29910.02667.0
270.29919.02735.0
310.29901.02708.0
320.29903.02733.0
330.29903.02733.0
360.29908.02666.0
390.29920.02815.0
410.29918.02753.0
420.29901.02643.0
450.29906.02673.0
460.29902.02667.0
480.29921.02786.0
510.29918.02712.0
520.29901.02679.0
650.29919.02663.0
680.29904.02760.0
710.29903.02721.0
730.29901.02779.0
780.29907.02685.0
800.29928.02844.0
870.29933.02821.0
880.29901.02683.0
890.29918.02766.0
920.29909.02660.0
940.29912.02719.0
980.29906.02655.0
\n", - "
" - ], - "text/plain": [ - " cum_infections cum_deaths\n", - "seed p_death \n", - "1 0.2 9902.0 2682.0\n", - "6 0.2 9908.0 2730.0\n", - "14 0.2 9903.0 2721.0\n", - "19 0.2 9907.0 2712.0\n", - "25 0.2 9916.0 2762.0\n", - "26 0.2 9910.0 2667.0\n", - "27 0.2 9919.0 2735.0\n", - "31 0.2 9901.0 2708.0\n", - "32 0.2 9903.0 2733.0\n", - "33 0.2 9903.0 2733.0\n", - "36 0.2 9908.0 2666.0\n", - "39 0.2 9920.0 2815.0\n", - "41 0.2 9918.0 2753.0\n", - "42 0.2 9901.0 2643.0\n", - "45 0.2 9906.0 2673.0\n", - "46 0.2 9902.0 2667.0\n", - "48 0.2 9921.0 2786.0\n", - "51 0.2 9918.0 2712.0\n", - "52 0.2 9901.0 2679.0\n", - "65 0.2 9919.0 2663.0\n", - "68 0.2 9904.0 2760.0\n", - "71 0.2 9903.0 2721.0\n", - "73 0.2 9901.0 2779.0\n", - "78 0.2 9907.0 2685.0\n", - "80 0.2 9928.0 2844.0\n", - "87 0.2 9933.0 2821.0\n", - "88 0.2 9901.0 2683.0\n", - "89 0.2 9918.0 2766.0\n", - "92 0.2 9909.0 2660.0\n", - "94 0.2 9912.0 2719.0\n", - "98 0.2 9906.0 2655.0" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res.summary.loc[res['cum_infections']>9900]" - ] - }, - { - "cell_type": "markdown", - "id": "43", - "metadata": {}, - "source": [ - "We can then make a copy of the results and write the reduced summary dataframe back to that object" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "44", - "metadata": {}, - "outputs": [], - "source": [ - "res2 = res.copy()\n", - "res2.summary = res.summary.loc[res['cum_infections']>9900]" - ] - }, - { - "cell_type": "markdown", - "id": "45", - "metadata": {}, - "source": [ - "
\n", - "Unlike sc.dcp(), copying using the .copy() method only deep copies the summary dataframe. It does not duplicate the time series dataframes or the cache. For Samples objects, it is therefore generally preferable to use .copy().\n", - "
\n", - "\n", - "\n", - "Now notice that there are fewer samples, and the seeds have been filtered" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "46", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "100" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(res)" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "47", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "31" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(res2)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "48", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 1, 6, 14, 19, 25, 26, 27, 31, 32, 33, 36, 39, 41, 42, 45, 46, 48,\n", - " 51, 52, 65, 68, 71, 73, 78, 80, 87, 88, 89, 92, 94, 98])" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res2.seeds" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "49", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0, 0.5, 'Probability density')" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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kiJjjJ588km9/PLLstvtztd8fX0VERGhmJgYlzY+f/58JSYmaubMmYqOjlZKSori4uK0Z88eBQcHF+m/fv169e7dW8nJyXrggQc0d+5cde/eXRkZGWratKkkKTExUatWrdKHH36oiIgIff755xo0aJDCwsL00EMPuVQfAAC4NdmMMcaVBdasWaMOHTqoUqVKN73x6OhotW3bVtOmTZMkFRQUKDw8XEOGDNGoUaOK9I+Pj1dOTo6WLVvmbGvfvr2ioqKce3maNm2q+Ph4vfjii84+rVu31v3336/XXnutRHVlZ2fLbrcrKytLgYGBNzPEYkWM+tTt6/S0A+O7ebsEAACuy5W/3yX6Ciw7O9v5c8uWLXXhwgVlZ2cX+yipvLw8bd26VbGxsT8X4+Oj2NhYpaenF7tMenp6of6SFBcXV6h/hw4dtHTpUh05ckTGGK1evVrff/+9fv/731+zltzc3BseBwAAKH9K9BVYjRo1dOzYMQUHB6t69erFHgR99eDo/Pz8Em345MmTys/PV0hISKH2kJAQ7d69u9hlHA5Hsf0dDofz+dSpU/X000+rbt26qlixonx8fDR79mzde++916wlOTmZM9gAALCQEgWgVatWOc/wWr16tUcLullTp07Vhg0btHTpUtWvX19r165VQkKCwsLCiuw9umr06NFKTEx0Ps/OzlZ4eHhplQwAAEpZiQJQp06div35ZgQFBalChQrKzMws1J6ZmanQ0NBilwkNDb1u/wsXLmjMmDFavHixunW7csxK8+bNtX37dk2cOPGaAcjPz09+fn43OyQAAFBOuHwdoNTUVK1bt875fPr06YqKitJjjz2m06dPl3g9vr6+at26tdLS0pxtBQUFSktLu+bZZDExMYX6S9LKlSud/S9duqRLly7Jx6fwsCpUqMCtOQAAgJPLAWjkyJHOg4R37NihxMREde3aVfv37y/0NVJJJCYmavbs2fq///s/7dq1S88884xycnLUv39/SVLfvn01evRoZ/9hw4YpNTVVkyZN0u7du/XSSy9py5YtGjx4sCQpMDBQnTp10siRI/XFF19o//79+t///V/9/e9/1yOPPOLqUAEAwC3K5Zuh7t+/X02aNJEkffTRR3rwwQf1xhtvKCMjQ127dnVpXfHx8Tpx4oTGjRsnh8OhqKgopaamOg90PnToUKG9OR06dNDcuXM1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eVKvvvqqnn76aWebFT9n/ltx8yKVr88Zd87LO++8o6pVq6pq1ar67LPPtHLlSvn6+koqf+8XT7FsAJKkDz74QMYY1alTR35+fpoyZYp69+4tHx8fVapUSYsWLdL333+vmjVrqnLlylq9erXuv//+QrfmuBWV1ry8+OKL6tixo1q2bKm//OUvev755/XWW295aFTuwXumeKU1L4mJiercubOaN2+ugQMHatKkSZo6darzf8ZlkTvnJjs7W926dVOTJk300ksvlf5g3Ki05qW8fc64a1769Omjbdu2ac2aNbrjjjvUq1cvXbx40UujKptu7U/lX3H77bdrzZo1OnfunA4fPqxNmzbp0qVLatiwoSSpdevW2r59u86cOaNjx44pNTVVP/30k/P10NBQ5eXlFTlbJTMzU6Ghoc4+/33209XnV/uUNaUxL8WJjo7Wjz/+WKb/mN3s3JREaGiojh8/Xqjt8uXLOnXq1C37nrlR0dHRunz5sg4cOOCGUXiGu+bm7Nmz6tKli6pVq6bFixerUqVKztes+Dlz1fXmpThl/XPGXfNit9sVGRmpe++9VwsXLtTu3bu1ePFiSeXz/eIRXjv6qAw6deqUsdvt5t133y329e+//974+PiYFStWGGN+Pth34cKFzj67d+8u9iDozMxMZ593333XBAYGmosXL3pwNO7jiXkpzmuvvWZq1Kjh3uI9zNW5+aVfOwh6y5YtzrYVK1aU2YOgi+OJeSnOhx9+aHx8fMypU6dutuRScyNzk5WVZdq3b286depkcnJyiixjxc8ZY359XopT3j5nbuZ36aqLFy+agIAAM2fOHGPMrfF+cQdLB6DU1FTz2Wefmf/85z/m888/Ny1atDDR0dEmLy/PGHPljILVq1ebH374wSxZssTUr1/f9OjRo9A6Bg4caOrVq2dWrVpltmzZYmJiYkxMTIzz9aunG/7+978327dvN6mpqea2224r06cblsa8LF261MyePdvs2LHD7N2717zzzjumcuXKZty4caU6Vle5Y26OHTtmtm3bZmbPnm0kmbVr15pt27aZn376ydmnS5cupmXLlmbjxo1m3bp1JjIyskyfBl8a87J+/Xrz9ttvm+3bt5sffvjBfPjhh+a2224zffv2LfXxuuJm5yYrK8tER0ebZs2amX379hU6nfu/T2u20udMSealPH7O3Oy8/PDDD+aNN94wW7ZsMQcPHjRfffWVefDBB03NmjWdgac8vl88wdIBaP78+aZhw4bG19fXhIaGmoSEBHPmzBnn65MnTzZ169Y1lSpVMvXq1TNjx44tcorghQsXzKBBg0yNGjVM5cqVzSOPPGKOHTtWqM+BAwfM/fffbwICAkxQUJAZMWJEodM0y5rSmJfPPvvMREVFmapVq5oqVaqYFi1amJkzZ5r8/PxSG+eNcMfcJCUlGUlFHlf/d2aMMT/99JPp3bu3qVq1qgkMDDT9+/c3Z8+eLa1huqw05mXr1q0mOjra2O124+/vbxo3bmzeeOONMv8/1pudm6t7xIp77N+/39nPap8zJZmX8vg5c7PzcuTIEXP//feb4OBgU6lSJVO3bl3z2GOPmd27dxfaTnl7v3iCzZgyej4gAACAh1j6IGgAAGBNBCAAAGA5BCAAAGA5BCAAAGA5BCAAAGA5BCAAAGA5BCAAAGA5BCAAAGA5BCAAZZbNZtOSJUuu+Xrnzp01fPhwl9a5e/dutW/fXv7+/oqKirqp+q7npZde8uj6AdwcAhCAX2Wz2a77eOmll6657IEDB2Sz2bR9+3a317Vo0SK9+uqrLi2TlJSkKlWqaM+ePUpLS3NLHcUFteeee85t6wfgfhW9XQCAsu/YsWPOn+fPn69x48Zpz549zraqVat6oyzVrFnT5WV++OEHdevWTfXr1/dART+rWrWq1+YFwK9jDxCAXxUaGup82O122Ww25/Pg4GD99a9/Vd26deXn56eoqCilpqY6l23QoIEkqWXLlrLZbOrcubMkafPmzfrd736noKAg2e12derUSRkZGS7V9d9fgUVEROiNN97Qk08+qWrVqqlevXqaNWuW83WbzaatW7fqlVdeKbTn6vDhw+rVq5eqV6+umjVr6uGHH9aBAwcKbev999/X3XffLT8/P9WuXVuDBw92blOSHnnkEdlsNufz//4KrKCgQK+88so15+nqnrJFixbpvvvuU+XKldWiRQulp6c7+xw8eFAPPvigatSooSpVqujuu+/W8uXLXZozAFcQgADclMmTJ2vSpEmaOHGivvnmG8XFxemhhx7S3r17JUmbNm2SJP373//WsWPHtGjRIknS2bNn1a9fP61bt04bNmxQZGSkunbtqrNnz95UPZMmTVKbNm20bds2DRo0SM8884xzb9WxY8d09913a8SIETp27Jiee+45Xbp0SXFxcapWrZq+/PJLffXVV6pataq6dOmivLw8SdKMGTOUkJCgp59+Wjt27NDSpUvVqFEjSVeCnCTNmTNHx44dcz53dZ6ueuGFF/Tcc89p+/btuuOOO9S7d29dvnxZkpSQkKDc3FytXbtWO3bs0IQJE9jLBNwob9+OHkD5MmfOHGO3253Pw8LCzOuvv16oT9u2bc2gQYOMMcbs37/fSDLbtm277nrz8/NNtWrVzCeffOJsk2QWL158zWU6depkhg0b5nxev35988c//tH5vKCgwAQHB5sZM2Y421q0aGGSkpKczz/44ANz5513moKCAmdbbm6uCQgIMCtWrHCO8YUXXrhmHcXVmZSUZFq0aOF8XtJ5+tvf/uZ8fefOnUaS2bVrlzHGmGbNmpmXXnrpmnUAKDn2AAG4YdnZ2Tp69Kg6duxYqL1jx47atWvXdZfNzMzUU089pcjISNntdgUGBurcuXM6dOjQTdXUvHlz589Xv6o7fvz4Nft//fXX2rdvn6pVq+Y8bqdmzZq6ePGifvjhBx0/flxHjx7Vb3/72xuuyZV5+mX9tWvXliRn/UOHDtVrr72mjh07KikpSd98880N1wRYHQdBA/CKfv366aefftLkyZNVv359+fn5KSYmxvm1042qVKlSoec2m00FBQXX7H/u3Dm1bt1a//jHP4q8dtttt8nHp3T/n/jL+m02myQ56//Tn/6kuLg4ffrpp/r888+VnJysSZMmaciQIaVaI3ArYA8QgBsWGBiosLAwffXVV4Xav/rqKzVp0kSS5OvrK0nKz88v0mfo0KHq2rWr8+DikydPlk7hv9CqVSvt3btXwcHBatSoUaGH3W5XtWrVFBERcd1T2itVqlRkfL9UknkqqfDwcA0cOFCLFi3SiBEjNHv2bJeWB3AFAQjATRk5cqQmTJig+fPna8+ePRo1apS2b9+uYcOGSZKCg4MVEBCg1NRUZWZmKisrS5IUGRmpDz74QLt27dLGjRvVp08fBQQElHr9ffr0UVBQkB5++GF9+eWX2r9/v7744gsNHTpUP/74o6QrZ3RNmjRJU6ZM0d69e5WRkaGpU6c613E1IDkcDp0+fbrY7fzaPJXE8OHDtWLFCu3fv18ZGRlavXq1GjdufHMTAFgUAQjATRk6dKgSExM1YsQINWvWTKmpqVq6dKkiIyMlSRUrVtSUKVP07rvvKiwsTA8//LAk6b333tPp06fVqlUrPf744xo6dKiCg4NLvf7KlStr7dq1qlevnnr06KHGjRtrwIABunjxogIDAyVd+bouJSVF77zzju6++2498MADhc7emjRpklauXKnw8HC1bNmy2O382jyVRH5+vhISEtS4cWN16dJFd9xxh955552bmwDAomzGGOPtIgAAAEoTe4AAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDlEIAAAIDl/D/ASVd0LL3drAAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.hist(res2['cum_infections'], density=True)\n", - "plt.xlabel('Total infections')\n", - "plt.ylabel('Probability density')" - ] - }, - { - "cell_type": "markdown", - "id": "50", - "metadata": {}, - "source": [ - "### Applying functions and transformations" - ] - }, - { - "cell_type": "markdown", - "id": "51", - "metadata": {}, - "source": [ - "Sometimes it might be necessary to calculate quantities that are derived from the time series dataframes. These could be simple scalar values, such as totals or averages that had not been computed ahead of time, or extracting values from each simulation at a particular point in time. As an alternative to writing a loop that iterates over the seeds, the `.apply()` method takes in a function and maps it to every dataframe. This makes it quick to construct lists or arrays with scalar values extracted from the time series. For example, suppose we wanted to extract the peak number of people infected from each simulation:" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "52", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[3615.0,\n", - " 3965.0,\n", - " 3955.0,\n", - " 3922.0,\n", - " 3898.0,\n", - " 3785.0,\n", - " 3905.0,\n", - " 3886.0,\n", - " 3719.0,\n", - " 3982.0,\n", - " 3891.0,\n", - " 3768.0,\n", - " 3931.0,\n", - " 3814.0,\n", - " 4059.0,\n", - " 3879.0,\n", - " 3780.0,\n", - " 3869.0,\n", - " 3780.0,\n", - " 3945.0,\n", - " 3714.0,\n", - " 3862.0,\n", - " 3924.0,\n", - " 3829.0,\n", - " 3895.0,\n", - " 3959.0,\n", - " 3740.0,\n", - " 3940.0,\n", - " 3999.0,\n", - " 4028.0,\n", - " 3855.0,\n", - " 3800.0,\n", - " 3974.0,\n", - " 4179.0,\n", - " 3870.0,\n", - " 3735.0,\n", - " 3987.0,\n", - " 3866.0,\n", - " 4016.0,\n", - " 4041.0,\n", - " 3958.0,\n", - " 3953.0,\n", - " 3912.0,\n", - " 3884.0,\n", - " 3843.0,\n", - " 3921.0,\n", - " 3891.0,\n", - " 3861.0,\n", - " 3974.0,\n", - " 3879.0,\n", - " 3913.0,\n", - " 3810.0,\n", - " 3842.0,\n", - " 3801.0,\n", - " 3638.0,\n", - " 3783.0,\n", - " 4027.0,\n", - " 3763.0,\n", - " 3579.0,\n", - " 3906.0,\n", - " 3740.0,\n", - " 3846.0,\n", - " 4038.0,\n", - " 3730.0,\n", - " 3905.0,\n", - " 3901.0,\n", - " 3795.0,\n", - " 3929.0,\n", - " 3957.0,\n", - " 3789.0,\n", - " 4095.0,\n", - " 3976.0,\n", - " 3962.0,\n", - " 4046.0,\n", - " 3824.0,\n", - " 3952.0,\n", - " 3986.0,\n", - " 3863.0,\n", - " 3881.0,\n", - " 3832.0,\n", - " 4009.0,\n", - " 3945.0,\n", - " 3778.0,\n", - " 3861.0,\n", - " 4036.0,\n", - " 4067.0,\n", - " 3873.0,\n", - " 3968.0,\n", - " 3828.0,\n", - " 3856.0,\n", - " 3921.0,\n", - " 3644.0,\n", - " 3941.0,\n", - " 3952.0,\n", - " 3970.0,\n", - " 3766.0,\n", - " 3792.0,\n", - " 3849.0,\n", - " 3854.0,\n", - " 3909.0]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "peak_infections = lambda df: df['sir.n_infected'].max()\n", - "res.apply(peak_infections)" - ] - }, - { - "cell_type": "markdown", - "id": "53", - "metadata": {}, - "source": [ - "## Options when loading" - ] - }, - { - "cell_type": "markdown", - "id": "54", - "metadata": {}, - "source": [ - "There are two options available when loading that can change how the `Samples` class interacts with the file on disk:\n", - "\n", - "- `memory_buffer` - copy the entire file into memory. This prevents the file from being locked on disk and allows scripts to be re-run and results regenerated while still running the analysis notebook. This defaults to `True` for convenience, but loading the entire file into memory can be problematic if the file is large (e.g., >1GB) in which case setting `memory_buffer=False` may be preferable\n", - "- `preload` - Populate the cache in one step. This facilitates interactive usage of the analysis notebook by making the runtime of analysis functions predictable (since all results will be retrieved from the cache) at the expense of a long initial load time\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "id": "55", - "metadata": {}, - "source": [ - "### Implementation details\n", - "\n", - "If the file is loaded from a memory buffer, the `._zipfile` attribute will be populated. A helper property `.zipfile` is used to access the buffer, so if caching is not used, `.zipfile` returns the actual file on disk rather than the buffer" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "56", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " mode='r'>\n", - " mode='r'>\n" - ] - } - ], - "source": [ - "res = ss.Samples('results/0.2.zip', memory_buffer=True) # Copy the entire file into memory\n", - "print(res._zipfile)\n", - "print(res.zipfile)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "57", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "None\n", - "\n" - ] - } - ], - "source": [ - "res = ss.Samples('results/0.2.zip', memory_buffer=False) # Copy the entire file into memory\n", - "print(res._zipfile)\n", - "print(res.zipfile)" - ] - }, - { - "cell_type": "markdown", - "id": "58", - "metadata": {}, - "source": [ - "The dataframes associated with the individual dataframes are cached on access, so `pd.read_csv()` only needs to be called once. The cache starts out empty:" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "59", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res._cache" - ] - }, - { - "cell_type": "markdown", - "id": "60", - "metadata": {}, - "source": [ - "When a dataframe is accessed, it is automatically stored in the cache:" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "61", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "dict_keys([0])" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res[0]\n", - "res._cache.keys()" - ] - }, - { - "cell_type": "markdown", - "id": "62", - "metadata": {}, - "source": [ - "This means that iterating through the dataframes the first time can be slow (but in general, iterating over all dataframes is avoided in favour of either only using summary outputs, or accessing a subset of the runs)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "63", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Elapsed time: 67.1 ms\n" - ] - } - ], - "source": [ - "with sc.Timer():\n", - " for df in res:\n", - " continue" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "64", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Elapsed time: 2.24 ms\n" - ] - } - ], - "source": [ - "with sc.Timer():\n", - " for df in res:\n", - " continue" - ] - }, - { - "cell_type": "markdown", - "id": "65", - "metadata": {}, - "source": [ - "The `preload` option populates the entire cache in advance. This makes creating the `Samples` object slower, but operating on the dataframes afterwards will be consistently fast. This type of usage can be useful when wanting to load large files in the background and then interactively work with them afterwards. " - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "66", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Elapsed time: 78.9 ms\n" - ] - } - ], - "source": [ - "with sc.Timer():\n", - " res = ss.Samples('results/0.2.zip', preload=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "67", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Elapsed time: 2.62 ms\n" - ] - } - ], - "source": [ - "with sc.Timer():\n", - " for df in res:\n", - " continue" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "68", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Elapsed time: 3.21 ms\n" - ] - } - ], - "source": [ - "with sc.Timer():\n", - " for df in res:\n", - " continue" - ] - }, - { - "cell_type": "markdown", - "id": "69", - "metadata": {}, - "source": [ - "Together, these options provide some flexibility in terms of memory and time demands to suit analyses at various different scales." - ] - }, - { - "cell_type": "markdown", - "id": "70", - "metadata": {}, - "source": [ - "## Running scenarios" - ] - }, - { - "cell_type": "markdown", - "id": "71", - "metadata": {}, - "source": [ - "Suppose we wanted to compare a range of different `p_death` values and `initial` values (initial number of infections). We might define these runs as" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "72", - "metadata": {}, - "outputs": [], - "source": [ - "initials = np.arange(1,4)\n", - "p_deaths = np.arange(0,1,0.25)" - ] - }, - { - "cell_type": "markdown", - "id": "73", - "metadata": {}, - "source": [ - "Recall that our `run_sim()` function had an argument for `p_death`. We can extend this to include the `initial` parameter too. We can actually generalize this further by passing the parameters as keyword arguments to avoid needing to hard-code all of them. Note that we also need to add the `initial` value to the summary outputs:" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "74", - "metadata": {}, - "outputs": [], - "source": [ - "def get_sim(seed, **kwargs):\n", - " ppl = ss.People(10000)\n", - " net = ss.ndict(ss.RandomNet(n_contacts=ss.poisson(5)))\n", - " sir = ss.SIR(pars=kwargs)\n", - " sim = ss.Sim(people=ppl, networks=net, diseases=sir, rand_seed=seed)\n", - " sim.initialize(verbose=0)\n", - " return sim\n", - " \n", - "def run_sim(seed, **kwargs):\n", - " sim = get_sim(seed, **kwargs)\n", - " sim.run(verbose=0)\n", - " df = sim.export_df()\n", - " \n", - " summary = {}\n", - " summary['seed'] = sim.pars['rand_seed']\n", - " summary['p_death']= sim.diseases[0].pars.p_death.mean()\n", - " summary['initial']= sim.diseases[0].pars.initial\n", - " summary['cum_infections'] = sum(sim.results.sir.new_infections)\n", - " summary['cum_deaths'] = sum(sim.results.new_deaths)\n", - " \n", - " return df, summary" - ] - }, - { - "cell_type": "markdown", - "id": "75", - "metadata": {}, - "source": [ - "We can now easily run a set of scenarios with different values of `p_death` and save each one to a separate `Samples` object. Note that when we create the `Samples` objects now, we also want to specify that `'initial'` is one of the identifiers for the scenarios:" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "76", - "metadata": {}, - "outputs": [], - "source": [ - "# Clear the existing results\n", - "for file_path in resultsdir.glob('*'):\n", - " file_path.unlink()" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "77", - "metadata": {}, - "outputs": [], - "source": [ - "# Run the sweep over initial and p_death\n", - "n = 100\n", - "seeds = np.arange(n)\n", - "for initial in initials:\n", - " for p_death in p_deaths:\n", - " outputs = [run_sim(seed, initial=initial, p_death=p_death) for seed in seeds]\n", - " ss.Samples.new(resultsdir, outputs, [\"p_death\",\"initial\"])" - ] - }, - { - "cell_type": "markdown", - "id": "78", - "metadata": {}, - "source": [ - "The results folder now contains a collection of saved `Samples` objects. Notice how the automatically selected file names now contain both the `p_death` value and the `initial` value, because they were both specified as identifiers. We can load one of these objects in to see how these identifiers are stored and accessed inside the `Samples` class:" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "79", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[PosixPath('results/0.75-1.zip'),\n", - " PosixPath('results/0.75-3.zip'),\n", - " PosixPath('results/0.75-2.zip'),\n", - " PosixPath('results/0.0-2.zip'),\n", - " PosixPath('results/0.0-3.zip'),\n", - " PosixPath('results/0.0-1.zip'),\n", - " PosixPath('results/0.25-3.zip'),\n", - " PosixPath('results/0.25-2.zip'),\n", - " PosixPath('results/0.25-1.zip'),\n", - " PosixPath('results/0.5-3.zip'),\n", - " PosixPath('results/0.5-2.zip'),\n", - " PosixPath('results/0.5-1.zip')]" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "list(resultsdir.iterdir())" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "80", - "metadata": {}, - "outputs": [], - "source": [ - "res = ss.Samples('results/0.25-2.zip')" - ] - }, - { - "cell_type": "markdown", - "id": "81", - "metadata": {}, - "source": [ - "The 'id' of a `Samples` object is a dictionary of the identifiers, which makes it easy to access the input parameters associated with a set of scenario runs:" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "82", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'p_death': 0.25, 'initial': 2}" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res.id" - ] - }, - { - "cell_type": "markdown", - "id": "83", - "metadata": {}, - "source": [ - "The 'identifier' is a tuple of these values, which is suitable for use as a dictionary key. This can be useful for accumulating and comparing variables across scenarios" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "84", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(0.25, 2)" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res.identifier" - ] - }, - { - "cell_type": "markdown", - "id": "85", - "metadata": {}, - "source": [ - "### Loading multiple scenarios\n", - "\n", - "We saw above that we now have a directory full of `.zip` files corresponding to the various scenario runs. These can be accessed using the `Dataset` class, which facilitates accessing multiple instances of `Samples`. We can pass the folder containing the results to the `Dataset` constructor to load them all:" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "86", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "results = ss.Dataset(resultsdir)\n", - "results" - ] - }, - { - "cell_type": "markdown", - "id": "87", - "metadata": {}, - "source": [ - "The `.ids` attribute lists all of the values available across scenarios in the results folder:" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "88", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'p_death': [0.0, 0.25, 0.5, 0.75], 'initial': [1, 2, 3]}" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "results.ids" - ] - }, - { - "cell_type": "markdown", - "id": "89", - "metadata": {}, - "source": [ - "The individual results can be accessed by indexing the `Dataset` instance using the values of the identifiers. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "90", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "results[0.25,2]" - ] - }, - { - "cell_type": "markdown", - "id": "91", - "metadata": {}, - "source": [ - "This indexing operation is sensitive to the order in which the identifiers are specified. The `.get()` method allows you to specify them as key-value pairs" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "92", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "results.get(initial=2, p_death=0.25)" - ] - }, - { - "cell_type": "markdown", - "id": "93", - "metadata": {}, - "source": [ - "Iterating over the `Dataset` will iterate over the `Samples` instances contained within it" - ] - }, - { - "cell_type": "code", - "execution_count": 46, - "id": "94", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ] - } - ], - "source": [ - "for res in results:\n", - " print(res)" - ] - }, - { - "cell_type": "markdown", - "id": "95", - "metadata": {}, - "source": [ - "This can be used to extract and compare values across scenarios. For example, we could consider the use case of making a plot that compares total deaths across scenarios:" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "96", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0, 0.5, 'Scenario')" - ] - }, - "execution_count": 47, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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REQvGibjRERERERGwmSciIiIiMhImIwTERERERkJk3EiIiIiIiNhMk5EREREZCRMxomIiIiIjITJOBERERGRkTAZJyIiIiIyEibjRERERERGwmSciIiIiMhImIwTERERERkJk3EiIiIiIiNpN8n4qlWr0L17d5iamqKgoOC5HbegoACCIOD06dPP/FhBQUEYO3bsMz+OsURFReHll19uUh8nJyesXbu21ccVBAHffPNNk/o0h0gkgq2tLYKDg5/5sYiIiKj9aRfJ+IMHDxAREYHAwEBcu3YNCoVCs8/HxwdJSUnGC64ZWpLgN+d8Hz58iODgYMhkMkilUgQEBKC4uLjRPk+ePMFHH30Ee3t7SCQSvP7667h8+XKTjpueng4nJyfN5/DwcKSmpjZpjOzsbMyZM0fzub4kujnj1iUIQpNu8nJycjBp0iQoFApIJBK4ublh3bp1Ou1++eUXrFixAhs3bsTJkydbFCMRERG9eNpFMn7r1i08fvwY48ePh0KhgImJibFDaldCQ0Oxd+9epKSk4PDhw7h58ybGjx/faJ///u//xvr16/E///M/OH78ODp27IgRI0bg4cOHzY5DKpVCJpM1qY+trS06dOjQ6uO2lEqlQteuXfHPf/4T58+fx6JFixAZGYn4+Hitdt26dcOUKVMAADdu3HiuMRIREVHb1y6S8erqagCAqamp3raCIGDTpk3w8/ODRCKBs7MzvvrqK4OPlZWVBaVSCQsLC3h5eeHUqVM6bc6dOwc/Pz9IpVLY2dlh6tSpuH37tmb//v37MXDgQFhZWUEmk8Hf3x/5+fma/T179gQAKJVKCIIAHx8frfFXrlwJe3t7yGQyBAcHo7Ky0uD46yotLUVCQgJWr14NX19fDBgwAFu3bsWxY8eQmZlZb58nT55g7dq1WLx4McaMGQNPT09s374dN2/ebFFpR91ykpqynMbOt3aZSs0s+7hx4yAIguZz3XGzs7PxxhtvoEuXLrC0tMSQIUNafVZ6xowZWLduHYYMGQJnZ2e8++67mD59Onbv3q3T1szMDABQVVWld9yKigqUlZVpbURERPTiahfJeM1sbE1So8+SJUsQEBCAnJwcTJkyBRMnTsSFCxf09isvL4e/vz/69u0LlUqFqKgohIeHa7W5d+8efH19oVQqceLECezfvx/FxcWYMGGCps39+/cRFhaGEydO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PaKmiMk4ERGpwtTUFBkZGcqf79S8eXNMnz4dU6dOhcFgQM+ePVFYWIiEhATo9XqEh4dj3LhxWLFiBWbMmIFXXnkFKSkpiImJqfG53t7e2L59OwYNGgSNRoO5c+c2yIq3h4cHEhMTkZ2dDSsrK9ja2mLChAlYvXo1XnvtNUycOBGZmZmYP38+pk2bpiS3VdXz9vbGhx9+iN27d8PT0xMfffQRkpOT4enpWef+TJo0CT169MDy5csRGhqK3bt3G21RAYB58+Zh4MCBcHNzw7Bhw2BiYoK0tDSkp6fjrbfegpeXF27evIl169Zh0KBBSEhIwMaNGyuNu7i4GPHx8ejYsSMsLS1rXTEHbp3S4uzsDH9/f5iYmGDbtm1wcnLimeX00OHRhkREpBq9Xg+9Xl9t+aJFizB37lxER0fD19cXffv2xc6dO5Wk1M3NDV988QW+/PJLdOzYERs3bkRUVFSNz1y5ciVatGiB7t27Y9CgQQgJCUFAQMBfHsv06dNhamqKtm3bomXLlsjJyUGrVq0QFxeHpKQkdOzYEePGjcOYMWMwZ86cGuv94x//wJAhQxAWFoauXbvi0qVLRqvkddGtWze8//77WLNmDTp27Ijvv//e6LkAEBISgm+//Rbff/89unTpgm7dumHVqlVwd3cHAHTs2BErV67E22+/jXbt2uGTTz6pdPxg9+7dMW7cOISFhaFly5ZYunRpnfrXvHlzLF26FJ07d0aXLl2QnZ2NuLg45UMK0cNCIyKidieIiIiIiB5G/PhJRERERKQSJuNERERERCphMk5EREREpBIm40REREREKmEyTkRERESkEibjREREREQqYTJORERERKQSJuNERERERCphMk5EREREpBIm40REREREKmEyTkRERESkkv8H94nlJCh0988AAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "labels = []\n", - "y = []\n", - "yerr = []\n", - "\n", - "for res in results:\n", - " labels.append(res.id)\n", - " y.append(res['cum_deaths'].median())\n", - "\n", - "plt.barh(np.arange(len(results)),y, tick_label=labels)\n", - "plt.xlabel('Median total deaths');\n", - "plt.ylabel('Scenario')" - ] - }, - { - "cell_type": "markdown", - "id": "97", - "metadata": {}, - "source": [ - "### Filtering scenarios\n", - "\n", - "Often plots need to be generated for a subset of scenarios e.g., for sensitivity analysis or to otherwise compare specific scenarios. `Dataset.filter` returns a new `Dataset` containing a subset of the results:" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "98", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n", - "\n" - ] - } - ], - "source": [ - "for res in results.filter(initial=2):\n", - " print(res)" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "99", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\n" - ] - } - ], - "source": [ - "for res in results.filter(p_death=0.25):\n", - " print(res)" - ] - }, - { - "cell_type": "markdown", - "id": "100", - "metadata": {}, - "source": [ - "This is also a quick and efficient operation, so you can easily embed filtering commands inside the analysis to select subsets of the scenarios for plotting and other output generation. For instance:" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "id": "101", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0, 0.5, 'New deaths')" - ] - }, - "execution_count": 50, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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4uDBgxg3bhzeffddTJkyBbt378bAgQMdCvLKMhqN+Pvvvx16lA1YHZWVlYUmTZqUO96hQwdoNBocOHCgRtelBsLNQ3ZEdWrHjh2iXC4X5XK5GBcXJz777LPi9u3bRYPBYNcuPT1dlMvl4uuvv253/OTJk6JCobA7PmDAABGA+N///lc6ptfrxfDwcPGuu+6Sjo0aNUrs2LFjlf1btWqVCEC8cOGCdKyyGqKytRYWi0W86aab7N5TFEXxq6++EgGI+/fvt+tz6WtWVUMUFxcn9urVy+7Yhg0bHKrzqKzGZOPGjSIA8bXXXrM7fvfdd4uCIIi//fZblde1feYLFy6Ujun1erFr165iaGhoue+zMtnZ2aJKpRITEhLs6rb+7//+TwRgV98zd+5c0c/PT/z111/trvGf//xHlMvlYkZGhnSssLDQro3BYBA7deok3nbbbXbHq6shevTRR+2OjxkzRgwODq72vvr27SvGxsaWO15RfY6jP78XLlwo9zNSVQ0RAPHll1+Wnpf9TERRFJOSksq9t6M1RLZ2jjxK//vkqP3794uCIIgvvvhihefbtm0rDhs2zOnrUsPBDBHd0O644w4kJSVh5MiRSE1Nxfz58xEfH4+bbroJ3333ndRuw4YNsFgsuPfee+3+SzM8PBxt2rTB3r177a7r7++P8ePHS89VKhV69uyJ33//XToWFBSEP/74A0ePHq2TexMEAffccw+2bNmC/Px86fjatWtx0003oW/fvjW67oQJE3D48GGcP39eOrZmzRpERkZiwIABNbrmli1bIJfL8eSTT9odf/rppyGKYpWZCRuFQmGXUVGpVJg8eTKys7ORnJzsUD927doFg8GAadOm2Q33TJ8+vVzbr7/+Gv369UOjRo3sfiYGDx4Ms9mM/fv3S201Go30z9euXUNubi769euHn3/+2aF+2UyZMsXueb9+/fDPP/8gLy+vytf9888/aNSokcPv48jPb22V/kyMRiP++ecfREdHIygoyOnPBQC6dOmCnTt3OvQIDw936trZ2dl44IEHEBUVhWeffbbCNrafA7pxsaiabng9evTAhg0bYDAYkJqaim+++QaLFy/G3XffjZSUFHTo0AHnzp2DKIpo06ZNhddQKpV2z5s1a2b3BxWw/sI8ceKE9Py5557Drl270LNnT0RHR2PIkCF44IEHcOutt7rs3u67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Mqj8iuuI8myLM3LaPRERUUUcGjIbO3as3fM9e/Zg69at6NixI5RK+z/YGzZscF3vvJiUIapiyMxoMeJ49s8ASqbbl6YOUMOQry+3fQdriIiIiOw5FBAFBgbaPR8zZkyddIZKODLL7Mw/p6Ez6RCkDkLroPLDmOoAH1zPzIMh35oh8lOW7GdGREREJRwKiFatWlXX/aAyHJllZptdFhvWAzKh/OinqtwGr7Zp9wyIiIiISnO6hui2225DTk5OueN5eXm47bbbXNEngmOzzGz1Qz3Cyg+XAaW278iz3+C10MQhMyIiotKcDoj27dsHg8FQ7nhRURF+/PFHl3SKqt+643L+n/gz/08oBAW6ht5SYZuy23cwQ0RERFQxhwOiEydO4MSJEwCAM2fOSM9PnDiB48ePY+XKlbjpppucevP3338fN998M7RaLbRaLeLi4rB161bpfFFREaZOnYrg4GD4+/vjrrvuwpUrV+yukZGRgYSEBPj6+iI0NBTPPPMMTCaTXZt9+/ahW7duUKvViI6OxurVq53qpztIGaJKpt3bVqfu2KSjlPkpSwqI8m0ZItYQERERVcThhRm7du0KQRAgCEKFQ2MajQZLly516s2bNWuGN998E23atIEoivj0008xatQoHD9+HB07dsSMGTOwefNmfP311wgMDERiYiLGjh2LAwcOALBuI5KQkIDw8HAcPHgQmZmZmDBhApRKJd544w0AwIULF5CQkIApU6ZgzZo12L17Nx5//HFEREQgPj7eqf7Wp5JZZhVniEqm21c8XAaUbN9hW63aT8kMERERUUUcDoguXLgAURTRqlUrHDlyBCEhIdI5lUqF0NBQyOVyp958xIgRds9ff/11vP/++zh06BCaNWuGlStX4osvvpACsFWrViEmJgaHDh1C7969sWPHDpw5cwa7du1CWFgYunbtirlz5+K5557DK6+8ApVKhRUrViAqKgoLFy4EAMTExOCnn37C4sWLPTsgKh4yU1ZQQ1RoLMTpv08BALqH9yx33qakqLo4Q1Q8ZFZkLoLZYoZc5tz3RUREdKNyeMisRYsWaNmyJSwWC7p3744WLVpIj4iICKeDobLMZjO+/PJLFBQUIC4uDsnJyTAajRg8eLDUpn379mjevDmSkpIAAElJSejcuTPCwsKkNvHx8cjLy8Pp06elNqWvYWtju4anqmrafcpf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s1kMCQkRP/roI1EURXH79u2iTCYTc3NzpTY5OTmiIAjizp07pWMtWrQQFy9eXOf3UFvZ2dkiAPGHH34QRdF6L0qlUvz666+lNmfPnhUBiElJSaIoWoNKmUwmZmVlSW3ef/99UavVinq9XhRFUXz22WfFjh072r3XfffdJ8bHx9f1LTmlru7f9ovw2rVr9XczNVSTz6C0l19+ucKAYPny5WKjRo2kz0QURfG5554T27Vr5/qbqIW6un9bQHT8+PG66rrL1PYzsPn3v/8tDho0SHp+I/8eqEjZ+28ovwdcdf9LliwRmzVrJj2vy++fQ2Z1TK/XAwB8fHykYzKZDGq1Gj/99JPURhAEuwWpfHx8IJPJpDY2b775JoKDg3HLLbfg7bffLjek5Alyc3MBAI0bNwYAJCcnw2g0YvDgwVKb9u3bo3nz5khKSgIAJCUloXPnzggLC5PaxMfHIy8vD6dPn5balL6GrY3tGp6iru7fpmvXroiIiMAdd9yBAwcO1PXt1EhNPgNHJCUloX///lCpVNKx+Ph4pKWl4dq1ay7qfe3V1f3bjBw5EqGhoejbty++++4713TaxVz1GeTm5krXAG7s3wOVXaf0/dt4+u8BV9z/5cuXsWHDBgwYMEA6VpffPwOiOmb7wmfPno1r167BYDDgrbfewh9//IHMzEwAQO/eveHn54fnnnsOhYWFKCgowKxZs2A2m6U2gLW+4Msvv8TevXsxefJkvPHGG3j22WfddWsVslgsmD59Om699VZ06tQJAJCVlQWVSoWgoCC7tmFhYdK4d1ZWll0wYDtvO1dVm7y8POh0urq4HafV5f1HRERgxYoVWL9+PdavX4/IyEgMHDgQP//8cx3flXNq+hk4wpHPyd3q8v79/f2xcOFCfP3119i8eTP69u2L0aNHe1xQ5KrP4ODBg1i7di0mTZokHbuRfw+UVdH9N4TfA7W9//vvvx++vr646aaboNVq8fHHH0vn6vL75273dUypVGLDhg147LHH0LhxY8jlcgwePBjDhg2DWLxIeEhICL7++mv861//wrvvvguZTIb7778f3bp1g0xWErPOnDlT+uebb74ZKpUKkydPxrx58zxmyfepU6fi1KlT5TJb3qIu779du3Zo166d9LxPnz44f/48Fi9ejM8++8zl71dT/Bmou/tv0qSJ3e+BHj164PLly3j77bcxcuRIl79fTbniMzh16hRGjRqFl19+GUOGDHFh7+peXd5/Q/g9UNv7X7x4MV5++WX8+uuvmD17NmbOnInly5e7uJflMUNUD2JjY5GSkoKcnBxkZmZi27Zt+Oeff9CqVSupzZAhQ3D+/HlkZ2fj77//xmeffYY///zTrk1ZvXr1gslkQnp6ej3cRfUSExOxadMm7N27F82aNZOOh4eHw2AwICcnx679lStXEB4eLrUpO+PG9ry6NlqtFhqNxtW347S6vv+K9OzZE7/99puL7qD2avMZOKKmn1N9qev7r0ivXr1uuJ+BM2fO4Pbbb8ekSZPwwgsv2J27kX8P2FR1/xXxpN8Drrj/8PBwtG/fHiNHjsQHH3yA999/XxotqcvvnwFRPQoMDERISAjOnTuHY8eOYdSoUeXaNGnSBEFBQdizZw+ys7Or/K++lJQUyGQyhIaG1mW3qyWKIhITE/HNN99gz549iIqKsjsfGxsLpVKJ3bt3S8fS0tKQkZGBuLg4AEBcXBxOnjyJ7Oxsqc3OnTuh1WrRoUMHqU3pa9ja2K7hLvV1/xVJSUlBRESEi+/Iea74DBwRFxeH/fv3w2g0Ssd27tyJdu3aoVGjRrW/kRqqr/uvyI32M3D69GkMGjQIDz/8MF5//fVy73Mj/x4Aqr//injCz0Bd/TtgsVgAlNTj1un3X+uybBKvX78uHj9+XDx+/LgIQFy0aJF4/Phx8eLFi6IoiuJXX30l7t27Vzx//ry4ceNGsUWLFuLYsWPtrvHJJ5+ISUlJ4m+//SZ+9tlnYuPGjcWZM2dK5w8ePCguXrxYTElJEc+fPy9+/vnnYkhIiDhhwoR6vdeK/Otf/xIDAwPFffv22U0DLSwslNpMmTJFbN68ubhnzx7x2LFjYlxcnBgXFyedt007HzJkiJiSkiJu27ZNDAkJqXDa/TPPPCOePXtWXLZsmUdMt62v+1+8eLG4ceNG8dy5c+LJkyfFp556SpTJZOKuXbvq9X4r4orPQBRF8dy5c+Lx48fFyZMni23btpX+vbLNKsvJyRHDwsLEhx56SDx16pT45Zdfir6+vm6fdl9f97969Wrxiy++EM+ePSuePXtWfP3110WZTCZ+8skn9Xq/FXHFZ3Dy5EkxJCREHD9+vN01srOzpTY38u8BR+7fU38PuOL+N2/eLH7yySfiyZMnxQsXLoibNm0SY2JixFtvvVVqU5ffPwMiF7BNgyz7ePjhh0VRLJk2qFQqxebNm4svvPCC3bRhUbROHQ4LCxOVSqXYpk0bceHChaLFYpHOJycni7169RIDAwNFHx8fMSYmRnzjjTfEoqKi+rzVClV07wDEVatWSW10Op3473//W2zUqJHo6+srjhkzRszMzLS7Tnp6ujhs2DBRo9GITZo0EZ9++mnRaDTatdm7d6/YtWtXUaVSia1atbJ7D3epr/t/6623xNatW4s+Pj5i48aNxYEDB4p79uypr9uskqs+gwEDBlR4nQsXLkhtUlNTxb59+4pqtVq86aabxDfffLOe7rJy9XX/q1evFmNiYkRfX19Rq9WKPXv2tJvG7E6u+AxefvnlCq/RokULu/e6UX8POHL/nvp7wBX3v2fPHjEuLk76O9emTRvxueeeK7fEQF19/0LxjRARERF5LdYQERERkddjQERERERejwEREREReT0GREREROT1GBARERGR12NARERERF6PARERERF5PQZERERE5PUYEBHRDUMURQwePBjx8fHlzi1fvhxBQUH4448/3NAzIvJ0DIiI6IYhCAJWrVqFw4cP44MPPpCOX7hwAc8++yyWLl1qtwO3K5TeaJaIGi4GRER0Q4mMjMSSJUswa9YsXLhwAaIo4rHHHsOQIUNwyy23YNiwYfD390dYWBgeeugh/P3339Jrt23bhr59+yIoKAjBwcG48847cf78eel8eno6BEHA2rVrMWDAAPj4+GDNmjXuuE0icjHuZUZEN6TRo0cjNzcXY8eOxdy5c3H69Gl07NgRjz/+OCZMmACdTofnnnsOJpMJe/bsAQCsX78egiDg5ptvRn5+Pl566SWkp6cjJSUFMpkM6enpiIqKQsuWLbFw4ULccsst8PHxQUREhJvvlohqiwEREd2QsrOz0bFjR1y9ehXr16/HqVOn8OOPP2L79u1Smz/++AORkZFIS0tD27Zty13j77//RkhICE6ePIlOnTpJAdE777yDp556qj5vh4jqGIfMiOiGFBoaismTJyMmJgajR49Gamoq9u7dC39/f+nRvn17AJCGxc6dO4f7778frVq1glarRcuWLQEAGRkZdtfu3r17vd4LEdU9hbs7QERUVxQKBRQK66+5/Px8jBgxAm+99Va5drYhrxEjRqBFixb46KOP0LRpU1gsFnTq1AkGg8GuvZ+fX913nojqFQMiIvIK3bp1w/r169GyZUspSCrtn3/+QVpaGj766CP069cPAPDTTz/VdzeJyE04ZEZEXmHq1Km4evUq7r//fhw9ehTnz5/H9u3bMXHiRJjNZjRq1AjBwcH48MMP8dtvv2HPnj2YOXOmu7tNRPWEAREReYWmTZviwIEDMJvNGDJkCDp37ozp06cjKCgIMpkMMpkMX375JZKTk9GpUyfMmDEDb7/9tru7TUT1hLPMiIiIyOsxQ0RERERejwEREREReT0GREREROT1GBARERGR12NARERERF6PARERERF5PQZERERE5PUYEBEREZHXY0BEREREXo8BEREREXk9BkRERETk9RgQERERkdf7fzk5rofsk9W8AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for res, color in zip(results.filter(initial=2), sc.gridcolors(4)):\n", - " plt.plot(res[0].index, np.median([df['new_deaths'] for df in res], axis=0), color=color, label=f'p_death = {res.id[\"p_death\"]}')\n", - "plt.legend()\n", - "plt.title('Sensitivity to p_death (initial = 2)')\n", - "plt.xlabel('Year')\n", - "plt.ylabel('New deaths')" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "102", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Text(0, 0.5, 'New deaths')" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAHHCAYAAABeLEexAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB2EElEQVR4nO3deXhTVfoH8G+SNlvbdN8pLZvsyKaIsipDWQZFUBZRQfiJC8sojiKjIosK4oKIgoMzgiKODIKoyLAJCCN1gaHsVMCWgnSBbumSpU3O74+QSOhC0ibN0u/nefJA7j055z25afr23nPPkQghBIiIiIiaMKmnAyAiIiLyNCZERERE1OQxISIiIqImjwkRERERNXlMiIiIiKjJY0JERERETR4TIiIiImrymBARERFRk8eEiIiIiJo8JkTk1dasWQOJRIKsrKwblt27dy8kEgn27t3rdDsDBgzAgAEDnH6dt5g0aRJSUlLq9dp58+ZBIpE4VNaZ43E9R2Ns6LFYu3Yt2rVrh8DAQISFhdW7nvpISUnBpEmTGrVNZw0bNgyPPvpoo7bpzGfM1bKysiCRSPDmm296pH1vt23bNgQHB+Py5cueDsXjmBARjh07hvvuuw/JyclQKpVITEzEn/70JyxfvtzTodVoxYoVWLNmjVvbuHTpEubNm4f09HSX1rt161bMmzfPpXW6w2uvvYbNmzd7OgynnT59GpMmTUKrVq3w4YcfYtWqVS5v48CBA5g3bx6Ki4tdXre7/fDDD9ixYwdmz57t6VBczld+tgDgn//8J9q3bw+lUok2bdo4/F37yy+/YPr06ejYsSOCgoLQvHlzjBkzBr/++mu1spMmTYJEIqn2aNeunV25IUOGoHXr1li0aJFL+ubTBDVpP/zwg5DL5aJ169Zi4cKF4sMPPxRz584VgwcPFq1atfJ0eKKqqkrodDphNptt2zp27Cj69+9frazJZBI6nU6YTCan2zEYDMJgMNie//LLLwKAWL16dX3CrtW0adOEO37sjEaj0Ov19XptZWWl0Ol0dtuCgoLExIkTq5Wt6Xg4auLEiSI5OfmG5a4/Fs5YuXKlACDOnDlTr9c74o033hAARGZmZrV9er1eGI1Gt7XdUPfcc48YPHhwo7f78ssvu+Vzf63afrYyMzMFAPHGG2+4tX1HffDBBwKAGD16tFi1apV46KGHBACxePHiG7529OjRIi4uTsyYMUN8+OGHYuHChSI2NlYEBQWJY8eO2ZWdOHGiUCgUYu3atXaPr7/+ulq9K1asEGq1Wmi1Wpf10xcFeCwTI6/w6quvIjQ0FL/88ku1ywv5+fmeCeoaMpkMMpnMobJSqRRKpbJe7cjl8nq9zlsEBgbW+7UBAQEICHDsq8CZ41FfDTkW1s9sY18qs1IoFB5p1xH5+fn49ttv8cEHH3g6lCZLp9PhhRdewPDhw/HFF18AAB599FGYzWYsXLgQU6dORXh4eK2vnzVrFj777DO7n5GxY8eic+fOWLx4MT799FO78gEBAXjwwQdvGNfo0aMxY8YMbNiwAZMnT65n7/yApzMy8qy2bduKAQMGOFx+7dq1onv37kKpVIrw8HAxduxYkZ2dbVemf//+omPHjuLEiRNiwIABQqVSiYSEBPH6669Xq+/dd98VHTp0ECqVSoSFhYkePXqIdevW2favXr3a7q/x5ORkAcDuYT1btGfPHgFA7NmzRwhh+YsxKChIlJeXV2t33LhxIjY2VlRVVdlivr6e6x+rV68Wc+fOFQEBASI/P79anY8++qgIDQ2tdrbFauLEiTXWa1VWViZmzZolmjVrJuRyubjpppvEG2+84dDZmOvPvlz7V/Hf//530bJlSyGXy0XPnj3Fzz//bPfa6/96rylG69mi64+HEEJs3rxZDBs2TMTHxwu5XC5atmwpFixYYHtva4uxNtceCyH+OB7r168Xr7zyikhMTBQKhULceeeddmeCavpsvPzyy7b9W7duFX369BFqtVoEBweLYcOGiePHj1dr/9SpU+L+++8XUVFRQqlUiptuukn87W9/s3uvrn9c+/m8/szauXPnxH333SfCw8OFSqUSvXr1Elu2bLEr42gfhRDi119/FaNGjRKxsbFCoVCIxMREMXbsWFFcXFzn+/rRRx8JACIrK8tuu/WYfv/992Lq1KkiIiJChISEiIceekgUFhbWWWdN9u/fL3r27CkUCoVo2bKl+OCDD2o9Q+TI98m+ffvEfffdJ5KSkoRcLhfNmjUTTz31lKioqLCVqetny5mfBXf79ttvBQDx7bff2m0/cOCAACDWrl1br3q7d+8uunfvbrdt4sSJIigoSFRVVYmSkpIb1tGtWzdx991316t9f8EzRE1ccnIy0tLScPz4cXTq1KnOsq+++ipeeukljBkzBv/3f/+Hy5cvY/ny5ejXrx8OHz5s91d5UVERhgwZglGjRmHMmDH44osvMHv2bHTu3BlDhw4FAHz44YeYOXMm7rvvPvzlL3+BXq/H0aNH8dNPP+GBBx6oMYZ33nkHM2bMQHBwMF544QUAQGxsbI1lx44di/fffx/ffvst7r//ftv2iooKfPPNN5g0aVKNZzvat2+PBQsWYO7cuZg6dSr69u0LALj99tvRp08fLFiwAOvXr8f06dNtrzEajfjiiy8wevToWs9SPfbYY7h06RJ27tyJtWvX2u0TQuDuu+/Gnj17MGXKFHTt2hXbt2/Hs88+i99//x1Lly6tsc4b+eyzz1BaWorHHnsMEokES5YswahRo/Dbb7/VelZp7dq1+L//+z/ceuutmDp1KgCgVatWtbaxZs0aBAcHY9asWQgODsbu3bsxd+5caLVavPHGG/WKuyaLFy+GVCrFX//6V5SUlGDJkiWYMGECfvrpJwCWz8Ynn3yCL7/8EitXrkRwcDC6dOli69PEiRORmpqK119/HRUVFVi5ciX69OmDw4cP2wZ7Hz16FH379kVgYCCmTp2KlJQUnDt3Dt988w1effVVjBo1Cr/++iv+9a9/YenSpYiKigIAREdH1xhzXl4ebr/9dlRUVGDmzJmIjIzExx9/jLvvvhtffPEF7r33Xqf6aDQakZqaCoPBgBkzZiAuLg6///47tmzZguLiYoSGhtb6/h04cACRkZFITk6ucf/06dMRFhaGefPmISMjAytXrsT58+dtNys44tixYxg8eDCio6Mxb948VFVV4eWXX67xZ9TR75MNGzagoqICTzzxBCIjI/Hzzz9j+fLluHjxIjZs2ACg7p8tq/r8LACA2WxGYWGhQ/0PDQ2ts67Dhw8DAHr27Gm3vUePHpBKpTh8+LBDZ3SuJYRAXl4eOnbsWG1fRUUFNBoNKioqEB4ejvHjx+P1119HcHBwtbI9evTwyXGDLuXpjIw8a8eOHUImkwmZTCZ69+4tnnvuObF9+/Zq4yCysrKETCYTr776qt32Y8eOiYCAALvt/fv3FwDEJ598YttmMBhEXFycGD16tG3bPffcIzp27FhnfDWdkahtDNH1Z4jMZrNITEy0a1MIIf79738LAGLfvn12MV9bZ11jiHr37i169eplt23Tpk12bdemtnEOmzdvFgDEK6+8Yrf9vvvuExKJRJw9e7bOems7QxQZGWn3V/5XX30lAIhvvvnGtq2mv95rG0NU0/G49i91q8cee0yo1Wq7cU0NPUPUvn17u7FFy5YtEwDsxk5Y+3L58mXbttLSUhEWFiYeffRRu3Zyc3NFaGio3fZ+/fqJkJAQcf78ebuy156lq2sM0fVniJ566ikBQOzfv98unhYtWoiUlBTbeDdH+3j48GEBQGzYsKHG964uffr0ET169Ki23XpMe/ToYfdzv2TJEgFAfPXVVw63MXLkSKFUKu3ev5MnTwqZTGb3GXPm+6Smz9eiRYuERCKxa+dGY4gc+VmoifX1jjwc+fmXyWQ17ouOjhbjxo2r8/U1Wbt2rQAg/vnPf9ptf/7558Xs2bPF+vXrxb/+9S/bWbQ77rhDVFZWVqvntddeEwBEXl6e0zH4C95l1sT96U9/QlpaGu6++24cOXIES5YsQWpqKhITE/H111/bym3atAlmsxljxozBlStXbI+4uDi0adMGe/bssas3ODjY7i8duVyOW2+9Fb/99pttW1hYGC5evIhffvnFLX2TSCS4//77sXXrVpSVldm2r1+/HomJiejTp0+96n344Yfx008/4dy5c7Zt69atQ1JSEvr371+vOrdu3QqZTIaZM2fabX/mmWcghMB//vOfetU7duxYuzEJ1rNd1x6HhlKpVLb/l5aW4sqVK+jbty8qKipw+vRpl7XzyCOP2I2dcLQvO3fuRHFxMcaPH2/32ZXJZOjVq5fts3v58mXs27cPkydPRvPmze3qqO8t41u3bsWtt95q91kLDg7G1KlTkZWVhZMnTzrVR+sZoO3bt6OiosKpWAoKCuocnzJ16lS7sxtPPPEEAgICsHXrVofqN5lM2L59O0aOHGn3/rVv3x6pqal2ZZ35Prn281VeXo4rV67g9ttvhxDCdsbFEfX9WYiLi8POnTsdetx888111qXT6WodI6dUKqHT6RzuD2C5q3LatGno3bs3Jk6caLdv0aJFWLx4McaMGYNx48ZhzZo1ePXVV/HDDz/Yxi9dy/reXLlyxakY/AkTIsItt9yCTZs2oaioCD///DPmzJmD0tJS3HfffbYv7DNnzkAIgTZt2iA6OtrucerUqWoDsJs1a1btl0h4eDiKiopsz2fPno3g4GDceuutaNOmDaZNm4YffvjBpX0bO3YsdDqdLbkrKyvD1q1bcf/999f7l9zYsWOhUCiwbt06AEBJSQm2bNmCCRMm1LvO8+fPIyEhASEhIXbb27dvb9tfH9f/Yrd+6V17HBrqxIkTuPfeexEaGgqNRoPo6GhbMlxSUuKydurblzNnzgAA7rzzzmqf3R07dtg+u9ZfjDe6dOyM8+fPo23bttW213Zcb9THFi1aYNasWfjHP/6BqKgopKam4v3333f4fRZC1LqvTZs2ds+Dg4MRHx/v8JxTly9fhk6nq1YPgGrvgTPfJ9nZ2Zg0aRIiIiIQHByM6Oho2x8ezny+6vv5USqVGDRokEOPuhJOwJLcGY3GGvfp9Xq75O9GcnNzMXz4cISGhuKLL75w6GaHp59+GlKpFLt27aq2z/rZ8NR8Ud6AY4jIRi6X45ZbbsEtt9yCm266CY888gg2bNiAl19+GWazGRKJBP/5z39q/MG7/pp0bT+c134ht2/fHhkZGdiyZQu2bduGjRs3YsWKFZg7dy7mz5/vkj7ddtttSElJwb///W888MAD+Oabb6DT6TB27Nh61xkeHo4///nPWLduHebOnYsvvvgCBoPB6Wv/jcGR49AQxcXF6N+/PzQaDRYsWIBWrVpBqVTif//7H2bPng2z2eySdoD698Uaw9q1axEXF1dtv6N32DUGR/r41ltvYdKkSfjqq6+wY8cOzJw5E4sWLcKPP/6IZs2a1Vp3ZGSkSxPhhnD0+8RkMuFPf/oTCgsLMXv2bLRr1w5BQUH4/fffMWnSJKc+X/X9/JhMJocnLYyIiKjzLsn4+HiYTCbk5+cjJibGtt1oNKKgoAAJCQkOtVNSUoKhQ4eiuLgY+/fvd/h1KpUKkZGRNY6Jsn42rOPimiLv+SYgr2Id9JeTkwPAMqhWCIEWLVrgpptuclk7QUFBGDt2LMaOHQuj0YhRo0bh1VdfxZw5c2odnOzsXzBjxozBsmXLoNVqsX79eqSkpOC2226r8zU3auPhhx/GPffcg19++QXr1q1Dt27dahzU6Gi9ycnJ2LVrF0pLS+3OElkvOdU2ENZdHH2P9+7di4KCAmzatAn9+vWzbc/MzHRXaE6zDgiPiYnBoEGDai3XsmVLAMDx48frrM+Zz19ycjIyMjKqbW/oce3cuTM6d+6MF198EQcOHMAdd9yBDz74AK+88kqtr2nXrh02btxY6/4zZ85g4MCBtudlZWXIycnBsGHDHIopOjoaKpXKdkbuWte/B45+nxw7dgy//vorPv74Yzz88MO27Tt37qxW1l1nNi5cuIAWLVo4VHbPnj11zrLetWtXAMDBgwft3teDBw/CbDbb9tdFr9djxIgR+PXXX7Fr1y506NDBodiAPy5p13QTQGZmJqKiomq9QaAp4CWzJm7Pnj01/oVkHTdgPdU9atQoyGQyzJ8/v1p5IQQKCgqcbvv618jlcnTo0AFCCFRWVtb6uqCgIKdmCR47diwMBgM+/vhjbNu2DWPGjLnha4KCggCg1naGDh2KqKgovP766/j+++8dPjtUW73Dhg2DyWTCe++9Z7d96dKlkEgktjvzGouj77H1r+5rPxNGoxErVqxwV2hOS01NhUajwWuvvVbj58r61390dDT69euHjz76CNnZ2XZlru3fjT4b1xo2bBh+/vlnpKWl2baVl5dj1apVSElJceqXGQBotVpUVVXZbevcuTOkUikMBkOdr+3duzeKiopqHTOzatUqu/dn5cqVqKqqcvizJ5PJkJqais2bN9u9f6dOncL27dvtyjr6fVLT50sIgWXLllVr35nj4gxXjiG68847ERERgZUrV9ptX7lyJdRqNYYPH27bduXKFZw+fdpurJjJZMLYsWORlpaGDRs2oHfv3jW2o9frUVpaWm37woULIYTAkCFDqu07dOhQrfU1FTxD1MTNmDEDFRUVuPfee9GuXTsYjUYcOHDAdiblkUceAWD5i+6VV17BnDlzkJWVhZEjRyIkJASZmZn48ssvMXXqVPz1r391qu3BgwcjLi4Od9xxB2JjY3Hq1Cm89957GD58eLWxNNfq0aMHVq5ciVdeeQWtW7dGTEwM7rzzzlrLd+/eHa1bt8YLL7wAg8Hg0OWyVq1aISwsDB988AFCQkIQFBSEXr162f5SDAwMxLhx4/Dee+9BJpNh/PjxDvW5R48eAICZM2ciNTUVMpkM48aNw4gRIzBw4EC88MILyMrKws0334wdO3bgq6++wlNPPVXnbe/u0KNHD+zatQtvv/02EhIS0KJFC/Tq1ataudtvvx3h4eGYOHEiZs6cCYlEgrVr17rskpwraDQarFy5Eg899BC6d++OcePGITo6GtnZ2fj2229xxx132BLRd999F3369EH37t0xdepUtGjRAllZWfj2229ty7hYj+ELL7yAcePGITAwECNGjLD9Qr7W888/j3/9618YOnQoZs6ciYiICHz88cfIzMzExo0bIZU69zfp7t27MX36dNx///246aabUFVVhbVr10Imk2H06NF1vnb48OEICAjArl27bNMpXMtoNOKuu+7CmDFjkJGRgRUrVqBPnz64++67HY5v/vz52LZtG/r27Ysnn3wSVVVVWL58OTp27IijR4/ayjn6fdKuXTu0atUKf/3rX/H7779Do9Fg48aNNV76q+1nq6GsY4hcQaVSYeHChZg2bRruv/9+pKamYv/+/fj000/x6quvIiIiwlb2vffew/z58+3OOj3zzDP4+uuvMWLECBQWFlabiNH6h1lubi66deuG8ePH25bq2L59O7Zu3YohQ4bgnnvusXtdfn4+jh49imnTprmknz6r0e5nI6/0n//8R0yePFm0a9dOBAcH25bxmDFjRo23X27cuFH06dNHBAUFiaCgINGuXTsxbdo0kZGRYStjnZjxetffdv33v/9d9OvXT0RGRgqFQiFatWolnn32WbtJxGq6zTs3N1cMHz5chISE1Dkx47VeeOEFAUC0bt26xvfh+lu9hbDcltuhQwcREBBQ4y34P//8swDg1FIIVVVVYsaMGSI6OlpIJBK724RLS0vF008/LRISEkRgYKBo06aNSyZmvB6um7CwptvuT58+Lfr16ydUKtUNJ2b84YcfxG233WabgNM6dcP1x6Kht91ff6u5tY/XHpeabru/tp7U1FQRGhoqlEqlaNWqlZg0aZI4ePCgXbnjx4+Le++9V4SFhQmlUinatm0rXnrpJbsyCxcuFImJiUIqlTo8MaO1vltvvbXWiRlv1MfffvtNTJ48WbRq1UoolUoREREhBg4cKHbt2lXb22nn7rvvFnfddZfdtusnZgwPDxfBwcFiwoQJoqCgwKF6r/X999+LHj162CbprGtiRke+T06ePCkGDRokgoODRVRUlHj00UfFkSNHqh372n62nPlZaCyrVq0Sbdu2FXK5XLRq1UosXbq02s+59T279mfIOqVJbQ+roqIi8eCDD4rWrVsLtVotFAqF6Nixo3jttddqXFpm5cqVXLpDCCERwov+lCPyIUeOHEHXrl3xySef4KGHHvJ0OEQ3tH//fgwYMACnT5+23Q22Zs0aPPLII/jll1+qTRhITUO3bt0wYMCAek8A6y84hoionj788EMEBwdj1KhRng6FyCF9+/bF4MGD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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "for res, color in zip(results.filter(p_death=0.25), sc.gridcolors(3)):\n", - " plt.plot(res[0].index, np.median([df['new_deaths'] for df in res], axis=0), color=color, label=f'initial = {res.id[\"initial\"]}')\n", - "plt.legend()\n", - "plt.title('Sensitivity to initial infections (p_death = 0.25)')\n", - "plt.xlabel('Year')\n", - "plt.ylabel('New deaths')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "103", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:atomica311]", - "language": "python", - "name": "conda-env-atomica311-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.7" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": true, - "sideBar": true, - "skip_h1_title": false, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": { - "height": "calc(100% - 180px)", - "left": "10px", - "top": "150px", - "width": "401.8px" - }, - "toc_section_display": true, - "toc_window_display": true - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/starsim/arrays.py b/starsim/arrays.py index 7c8bc5cf..ae8d469d 100644 --- a/starsim/arrays.py +++ b/starsim/arrays.py @@ -218,6 +218,8 @@ def _convert_key(self, key): return self.auids[key] elif not np.isscalar(key) and len(key) == 0: # Handle [], np.array([]), etc. return uids() + elif isinstance(key, np.ndarray) and ss.options.reticulate: # TODO: fix ss.uids + return key.astype(int) else: errormsg = f'Indexing an Arr ({self.name}) by ({key}) is ambiguous or not supported. Use ss.uids() instead, or index Arr.raw or Arr.values.' raise Exception(errormsg) diff --git a/starsim/calibration.py b/starsim/calibration.py index 8c3a86df..b7340b82 100644 --- a/starsim/calibration.py +++ b/starsim/calibration.py @@ -3,129 +3,40 @@ """ import os import numpy as np +import optuna as op import pandas as pd +import datetime as dt import sciris as sc -import optuna as op -import matplotlib.pyplot as plt import starsim as ss +import matplotlib.pyplot as plt +from scipy.special import gammaln -__all__ = ['Calibration', 'compute_gof'] - - -def compute_gof(actual, predicted, normalize=True, use_frac=False, use_squared=False, - as_scalar='none', eps=1e-9, skestimator=None, estimator=None, **kwargs): - """ - Calculate the goodness of fit. By default use normalized absolute error, but - highly customizable. For example, mean squared error is equivalent to - setting normalize=False, use_squared=True, as_scalar='mean'. - - Args: - actual (arr): array of actual (data) points - predicted (arr): corresponding array of predicted (model) points - normalize (bool): whether to divide the values by the largest value in either series - use_frac (bool): convert to fractional mismatches rather than absolute - use_squared (bool): square the mismatches - as_scalar (str): return as a scalar instead of a time series: choices are sum, mean, median - eps (float): to avoid divide-by-zero - skestimator (str): if provided, use this scikit-learn estimator instead - estimator (func): if provided, use this custom estimator instead - kwargs (dict): passed to the scikit-learn or custom estimator - - Returns: - gofs (arr): array of goodness-of-fit values, or a single value if as_scalar is True - - **Examples**:: - - x1 = np.cumsum(np.random.random(100)) - x2 = np.cumsum(np.random.random(100)) - - e1 = compute_gof(x1, x2) # Default, normalized absolute error - e2 = compute_gof(x1, x2, normalize=False, use_frac=False) # Fractional error - e3 = compute_gof(x1, x2, normalize=False, use_squared=True, as_scalar='mean') # Mean squared error - e4 = compute_gof(x1, x2, skestimator='mean_squared_error') # Scikit-learn's MSE method - e5 = compute_gof(x1, x2, as_scalar='median') # Normalized median absolute error -- highly robust - """ - - # Handle inputs - actual = np.array(sc.dcp(actual), dtype=float) - predicted = np.array(sc.dcp(predicted), dtype=float) - - # Scikit-learn estimator is supplied: use that - if skestimator is not None: # pragma: no cover - try: - import sklearn.metrics as sm - sklearn_gof = getattr(sm, skestimator) # Shortcut to e.g. sklearn.metrics.max_error - except ImportError as E: - errormsg = f'You must have scikit-learn >=0.22.2 installed: {str(E)}' - raise ImportError(errormsg) from E - except AttributeError as E: - errormsg = f'Estimator {skestimator} is not available; see https://scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter for options' - raise AttributeError(errormsg) from E - gof = sklearn_gof(actual, predicted, **kwargs) - return gof - - # Custom estimator is supplied: use that - if estimator is not None: # pragma: no cover - try: - gof = estimator(actual, predicted, **kwargs) - except Exception as E: - errormsg = f'Custom estimator "{estimator}" must be a callable function that accepts actual and predicted arrays, plus optional kwargs' - raise RuntimeError(errormsg) from E - return gof - - # Default case: calculate it manually - else: - # Key step -- calculate the mismatch! - gofs = abs(np.array(actual) - np.array(predicted)) - - if normalize and not use_frac: - actual_max = abs(actual).max() - if actual_max > 0: - gofs /= actual_max - - if use_frac: - if (actual<0).any() or (predicted<0).any(): - print('Warning: Calculating fractional errors for non-positive quantities is ill-advised!') - else: - maxvals = np.maximum(actual, predicted) + eps - gofs /= maxvals - - if use_squared: - gofs = gofs**2 - - if as_scalar == 'sum': - gofs = np.sum(gofs) - elif as_scalar == 'mean': - gofs = np.mean(gofs) - elif as_scalar == 'median': - gofs = np.median(gofs) - - return gofs +__all__ = ['Calibration', 'CalibComponent'] -class Calibration(sc.prettyobj): # pragma: no cover +class Calibration(sc.prettyobj): """ A class to handle calibration of Starsim simulations. Uses the Optuna hyperparameter optimization library (optuna.org). Args: - sim (Sim) : the simulation to calibrate - data (df) : pandas dataframe (or dataframe-compatible dict) of the data to calibrate to - calib_pars (dict) : a dictionary of the parameters to calibrate of the format dict(key1=[best, low, high]) - n_trials (int) : the number of trials per worker - n_workers (int) : the number of parallel workers (default: maximum number of available CPUs) - total_trials (int) : if n_trials is not supplied, calculate by dividing this number by n_workers + sim (Sim) : the base simulation to calibrate + calib_pars (dict) : a dictionary of the parameters to calibrate of the format dict(key1=dict(low=1, high=2, guess=1.5, **kwargs), key2=...), where kwargs can include "suggest_type" to choose the suggest method of the trial (e.g. suggest_float) and args passed to the trial suggest function like "log" and "step" + n_workers (int) : the number of parallel workers (if None, will use all available CPUs) + total_trials (int) : the total number of trials to run, each worker will run approximately n_trials = total_trial / n_workers reseed (bool) : whether to generate new random seeds for each trial - weights (dict) : the relative weights of each data source - fit_args (dict) : a dictionary of options that are passed to sim.compute_fit() to calculate the goodness-of-fit - sep (str) : the separate between different types of results, e.g. 'hiv.deaths' vs 'hiv_deaths' - name (str) : the name of the database (default: 'starsim_calibration') + build_fn (callable): function that takes a sim object and calib_pars dictionary and returns a modified sim + build_kw (dict): a dictionary of options that are passed to build_fn to aid in modifying the base simulation. The API is self.build_fn(sim, calib_pars=calib_pars, **self.build_kw), where sim is a copy of the base simulation to be modified with calib_pars + components (list): CalibComponents independently assess pseudo-likelihood as part of evaluating the quality of input parameters + eval_fn (callable): Function mapping a sim to a float (e.g. negative log likelihood) to be maximized. If None, the default will use CalibComponents. + eval_kwargs (dict): Additional keyword arguments to pass to the eval_fn + label (str) : a label for this calibration object + study_name (str) : name of the optuna study db_name (str) : the name of the database file (default: 'starsim_calibration.db') keep_db (bool) : whether to keep the database after calibration (default: false) storage (str) : the location of the database (default: sqlite) - rand_seed (int) : if provided, use this random seed to initialize Optuna runs (for reproducibility) - label (str) : a label for this calibration object + sampler (BaseSampler): the sampler used by optuna, like optuna.samplers.TPESampler die (bool) : whether to stop if an exception is encountered (default: false) debug (bool) : if True, do not run in parallel verbose (bool) : whether to print details of the calibration @@ -133,20 +44,28 @@ class Calibration(sc.prettyobj): # pragma: no cover Returns: A Calibration object """ - def __init__(self, sim, data, calib_pars, n_trials=None, n_workers=None, total_trials=None, reseed=True, - weights=None, fit_args=None, sep='.', name=None, db_name=None, keep_db=None, storage=None, - rand_seed=None, sampler=None, label=None, die=False, debug=False, verbose=True, save_results=False): + def __init__(self, sim, calib_pars, n_workers=None, total_trials=None, reseed=True, + build_fn=None, build_kw=None, eval_fn=None, eval_kwargs=None, components=None, + label=None, study_name=None, db_name=None, keep_db=None, storage=None, + sampler=None, die=False, debug=False, verbose=True): # Handle run arguments - if n_trials is None: n_trials = 20 - if n_workers is None: n_workers = sc.cpu_count() - if name is None: name = 'starsim_calibration' - if db_name is None: db_name = f'{name}.db' - if keep_db is None: keep_db = False - if storage is None: storage = f'sqlite:///{db_name}' - if total_trials is not None: n_trials = int(np.ceil(total_trials/n_workers)) - kw = dict(n_trials=int(n_trials), n_workers=int(n_workers), debug=debug, name=name, db_name=db_name, - keep_db=keep_db, storage=storage, rand_seed=rand_seed, sampler=sampler) + if total_trials is None: total_trials = 100 + if n_workers is None: n_workers = sc.cpu_count() + if study_name is None: study_name = 'starsim_calibration' + if db_name is None: db_name = f'{study_name}.db' + if keep_db is None: keep_db = False + if storage is None: storage = f'sqlite:///{db_name}' + + self.build_fn = build_fn or self.translate_pars + self.build_kw = build_kw or dict() + self.eval_fn = eval_fn or self._eval_fit + self.eval_kwargs = eval_kwargs or dict() + self.components = components + + n_trials = int(np.ceil(total_trials/n_workers)) + kw = dict(n_trials=n_trials, n_workers=int(n_workers), debug=debug, study_name=study_name, + db_name=db_name, keep_db=keep_db, storage=storage, sampler=sampler) self.run_args = sc.objdict(kw) # Handle other inputs @@ -154,29 +73,14 @@ def __init__(self, sim, data, calib_pars, n_trials=None, n_workers=None, total_t self.sim = sim self.calib_pars = calib_pars self.reseed = reseed - self.sep = sep - self.weights = sc.mergedicts(weights) - self.fit_args = sc.mergedicts(fit_args) self.die = die self.verbose = verbose - self.save_results = save_results self.calibrated = False - self.before_sim = None - self.after_sim = None - - # Load data -- this is expecting a dataframe with a column for 'time' and other columns for to sim results - self.data = ss.validate_sim_data(data, die=True) - - # Temporarily store a filename - self.tmp_filename = 'tmp_calibration_%05i.obj' - - # Initialize sim - if not self.sim.initialized: - self.sim.init() - - # Figure out which sim results to get - self.sim_result_list = self.data.cols + self.before_msim = None + self.after_msim = None + # Temporarily store a filename for storing intermediate results + self.tmp_filename = 'tmp_calibration_%06i.obj' return def run_sim(self, calib_pars=None, label=None): @@ -184,7 +88,7 @@ def run_sim(self, calib_pars=None, label=None): sim = sc.dcp(self.sim) if label: sim.label = label - sim = self.translate_pars(sim, calib_pars=calib_pars) + sim = self.build_fn(sim, calib_pars=calib_pars, **self.build_kw) # Run the sim try: @@ -204,17 +108,15 @@ def translate_pars(sim=None, calib_pars=None): """ Take the nested dict of calibration pars and modify the sim """ if 'rand_seed' in calib_pars: - sim.pars['rand_seed'] = calib_pars['rand_seed'] + sim.pars['rand_seed'] = calib_pars.pop('rand_seed') for parname, spec in calib_pars.items(): - if parname == 'rand_seed': - continue - if 'path' not in spec: raise ValueError(f'Cannot map {parname} because "path" is missing from the parameter configuration.') p = spec['path'] + # TODO: Allow longer paths if len(p) != 3: raise ValueError(f'Cannot map {parname} because "path" must be a tuple of length 3.') @@ -234,14 +136,9 @@ def translate_pars(sim=None, calib_pars=None): return sim - def trial_to_sim_pars(self, pardict=None, trial=None): + def _sample_from_trial(self, pardict=None, trial=None): """ Take in an optuna trial and sample from pars, after extracting them from the structure they're provided in - - Different use cases: - - pardict is self.calib_pars, i.e. {'diseases':{'hiv':{'art_efficacy':[0.96, 0.9, 0.99]}}}, need to sample - - pardict is self.initial_pars, i.e. {'diseases':{'hiv':{'art_efficacy':[0.96, 0.9, 0.99]}}}, pull 1st vals - - pardict is self.best_pars, i.e. {'diseases':{'hiv':{'art_efficacy':0.96786}}}, pull single vals """ pars = sc.dcp(pardict) for parname, spec in pars.items(): @@ -250,84 +147,41 @@ def trial_to_sim_pars(self, pardict=None, trial=None): # Already have a value, likely running initial or final values as part of checking the fit continue - if 'sampler' in spec: - sampler = spec.pop('sampler') - sampler_fn = getattr(trial, sampler) + if 'suggest_type' in spec: + suggest_type = spec.pop('suggest_type') + sampler_fn = getattr(trial, suggest_type) else: sampler_fn = trial.suggest_float - path = spec.pop('path', None) # remove path - guess = spec.pop('guess', None) # remove guess - spec['value'] = sampler_fn(name=parname, **spec) # Sample! + path = spec.pop('path', None) # remove path for the sampler + guess = spec.pop('guess', None) # remove guess for the sampler + spec['value'] = sampler_fn(name=parname, **spec) # suggest values! spec['path'] = path spec['guess'] = guess return pars - @staticmethod - def sim_to_df(sim): # TODO: remove this method - """ Convert a sim to the expected dataframe type """ - df_res = sim.to_df(sep='.') - df_res['t'] = df_res['timevec'] - df_res = df_res.set_index('t') - df_res['time'] = np.floor(np.round(df_res.index, 1)).astype(int) - return df_res + def _eval_fit(self, sim, **kwargs): + """ Evaluate the fit by evaluating the negative log likelihood """ + nll = 0 # Negative log likelihood + for component in sc.tolist(self.components): + nll += component(sim) + return nll def run_trial(self, trial): """ Define the objective for Optuna """ if self.calib_pars is not None: - calib_pars = self.trial_to_sim_pars(self.calib_pars, trial) + pars = self._sample_from_trial(self.calib_pars, trial) else: - calib_pars = None + pars = None if self.reseed: - calib_pars['rand_seed'] = trial.suggest_int('rand_seed', 0, 1_000_000) # Choose a random rand_seed + pars['rand_seed'] = trial.suggest_int('rand_seed', 0, 1_000_000) # Choose a random rand_seed - sim = self.run_sim(calib_pars) - - # Export results # TODO: make more robust - df_res = self.sim_to_df(sim) - sim_results = sc.objdict() - - for skey in self.sim_result_list: - if 'prevalence' in skey or skey.startswith('n_'): - model_output = df_res.groupby(by='time')[skey].mean() - else: - model_output = df_res.groupby(by='time')[skey].sum() - sim_results[skey] = model_output.values - - sim_results['time'] = model_output.index.values - # Store results in temporary files - if self.save_results: - filename = self.tmp_filename % trial.number - sc.save(filename, sim_results) + sim = self.run_sim(pars) # Compute fit - fit = self.compute_fit(df_res=df_res) - return fit - - def compute_fit(self, sim=None, df_res=None): - """ Compute goodness-of-fit """ - fit = 0 - - # TODO: reduce duplication with above - if df_res is None: - df_res = self.sim_to_df(sim) - for skey in self.sim_result_list: - if 'prevalence' in skey or skey.startswith('n_'): - model_output = df_res.groupby(by='time')[skey].mean() - else: - model_output = df_res.groupby(by='time')[skey].sum() - - data = self.data[skey] - combined = pd.merge(data, model_output, how='left', on='time') - combined['diffs'] = combined[skey+'_x'] - combined[skey+'_y'] - gofs = compute_gof(combined.dropna()[skey+'_x'], combined.dropna()[skey+'_y']) - - losses = gofs #* self.weights[skey] - mismatch = losses.sum() - fit += mismatch - + fit = self.eval_fn(sim, **self.eval_kwargs) return fit def worker(self): @@ -336,7 +190,7 @@ def worker(self): op.logging.set_verbosity(op.logging.DEBUG) else: op.logging.set_verbosity(op.logging.ERROR) - study = op.load_study(storage=self.run_args.storage, study_name=self.run_args.name, sampler = self.run_args.sampler) + study = op.load_study(storage=self.run_args.storage, study_name=self.run_args.study_name, sampler=self.run_args.sampler) output = study.optimize(self.run_trial, n_trials=self.run_args.n_trials, callbacks=None) return output @@ -358,8 +212,8 @@ def remove_db(self): if self.verbose: print(f'Removed existing calibration file {self.run_args.db_name}') else: # Delete the study from the database e.g., mysql - op.delete_study(study_name=self.run_args.name, storage=self.run_args.storage) - if self.verbose: print(f'Deleted study {self.run_args.name} in {self.run_args.storage}') + op.delete_study(study_name=self.run_args.study_name, storage=self.run_args.storage) + if self.verbose: print(f'Deleted study {self.run_args.study_name} in {self.run_args.storage}') except Exception as E: if self.verbose: print('Could not delete study, skipping...') @@ -370,23 +224,16 @@ def make_study(self): """ Make a study, deleting one if it already exists """ if not self.run_args.keep_db: self.remove_db() - if self.run_args.rand_seed is not None: - sampler = op.samplers.RandomSampler(self.run_args.rand_seed) - sampler.reseed_rng() - raise NotImplementedError('Implemented but does not work') - else: - sampler = None if self.verbose: print(self.run_args.storage) - output = op.create_study(storage=self.run_args.storage, study_name=self.run_args.name, sampler=sampler) + output = op.create_study(storage=self.run_args.storage, study_name=self.run_args.study_name) return output - def calibrate(self, calib_pars=None, confirm_fit=False, load=False, tidyup=True, **kwargs): + def calibrate(self, calib_pars=None, load=False, tidyup=True, **kwargs): """ Perform calibration. Args: calib_pars (dict): if supplied, overwrite stored calib_pars - confirm_fit (bool): if True, run simulations with parameters from before and after calibration load (bool): whether to load existing trials from the database (if rerunning the same calibration) tidyup (bool): whether to delete temporary files from trial runs verbose (bool): whether to print output from each trial @@ -401,7 +248,7 @@ def calibrate(self, calib_pars=None, confirm_fit=False, load=False, tidyup=True, t0 = sc.tic() self.make_study() self.run_workers() - study = op.load_study(storage=self.run_args.storage, study_name=self.run_args.name, sampler = self.run_args.sampler) + study = op.load_study(storage=self.run_args.storage, study_name=self.run_args.study_name, sampler=self.run_args.sampler) self.best_pars = sc.objdict(study.best_params) self.elapsed = sc.toc(t0, output=True) @@ -436,16 +283,12 @@ def calibrate(self, calib_pars=None, confirm_fit=False, load=False, tidyup=True, if not self.run_args.keep_db: self.remove_db() - # Optionally compute the sims before and after the fit - if confirm_fit: - self.confirm_fit() - return self - def confirm_fit(self): + def check_fit(self, n_runs=5): """ Run before and after simulations to validate the fit """ - if self.verbose: print('\nConfirming fit...') + if self.verbose: print('\nChecking fit...') before_pars = sc.dcp(self.calib_pars) for spec in before_pars.values(): @@ -455,23 +298,26 @@ def confirm_fit(self): for parname, spec in after_pars.items(): spec['value'] = self.best_pars[parname] - self.before_sim = self.run_sim(calib_pars=before_pars, label='Before calibration') - self.after_sim = self.run_sim(calib_pars=after_pars, label='After calibration') - self.before_fit = self.compute_fit(self.before_sim) - self.after_fit = self.compute_fit(self.after_sim) - - # Add the data to the sims - for sim in [self.before_sim, self.after_sim]: - sim.init_data(self.data) - - print(f'Fit with original pars: {self.before_fit:n}') - print(f'Fit with best-fit pars: {self.after_fit:n}') - if self.after_fit <= self.before_fit: + before_sim = self.build_fn(self.sim, calib_pars=before_pars, **self.build_kw) + before_sim.label = 'Before calibration' + self.before_msim = ss.MultiSim(before_sim, n_runs=n_runs) + self.before_msim.run() + self.before_fits = np.array([self.eval_fn(sim, **self.eval_kwargs) for sim in self.before_msim.sims]) + + after_sim = self.build_fn(self.sim, calib_pars=after_pars, **self.build_kw) + after_sim.label = 'Before calibration' + self.after_msim = ss.MultiSim(after_sim, n_runs=n_runs) + self.after_msim.run() + self.after_fits = np.array([self.eval_fn(sim, **self.eval_kwargs) for sim in self.after_msim.sims]) + + print(f'Fit with original pars: {self.before_fits}') + print(f'Fit with best-fit pars: {self.after_fits}') + if self.after_fits.mean() <= self.before_fits.mean(): print('✓ Calibration improved fit') else: print('✗ Calibration did not improve fit, but this sometimes happens stochastically and is not necessarily an error') - return self.before_fit, self.after_fit + return self.before_fits, self.after_fits def parse_study(self, study): """Parse the study into a data frame -- called automatically """ @@ -529,11 +375,24 @@ def plot_sims(self, **kwargs): Args: kwargs (dict): passed to MultiSim.plot() """ - if self.before_sim is None: - self.confirm_fit() - msim = ss.MultiSim([self.before_sim, self.after_sim]) - fig = msim.plot(**kwargs) - return ss.return_fig(fig) + if self.before_msim is None: + self.check_fit() + + # Turn off jupyter mode so we can receive the figure handles + jup = ss.options.jupyter if 'jupyter' in ss.options else sc.isjupyter() + ss.options.jupyter = False + + self.before_msim.reduce() + fig_before = self.before_msim.plot() + fig_before.suptitle('Before calibration') + + self.after_msim.reduce() + fig_after = self.after_msim.plot(fig=fig_before) + fig_after.suptitle('After calibration') + + ss.options.jupyter = jup + + return fig_before, fig_after def plot_trend(self, best_thresh=None, fig_kw=None): """ @@ -570,4 +429,142 @@ def plot_trend(self, best_thresh=None, fig_kw=None): plt.xlabel('Trial number') plt.ylabel('Mismatch') sc.figlayout() - return ss.return_fig(fig) \ No newline at end of file + return fig + + +class CalibComponent(sc.prettyobj): + """ + A class to compare a single channel of observed data with output from a + simulation. The Calibration class can use several CalibComponent objects to + form an overall understanding of how will a given simulation reflects + observed data. + + Args: + name (str) : the name of this component. Importantly, if + extract_fn is None, the code will attempt to use the name, like + "hiv.prevalence" to automatically extract data from the simulation. + data (df) : pandas Series containing calibration data. The index should be the time in either floating point years or datetime. + mode (str/func): To handle misaligned timepoints between observed data and simulation output, it's important to know if the data are incident (like new cases) or prevalent (like the number infected). + If 'prevalent', simulation outputs will be interpolated to observed timepoints. + If 'incident', outputs will be interpolated to cumulative incidence. + """ + def __init__(self, name, expected, extract_fn, conform, nll_fn, weight=1): + self.name = name + self.expected = expected + self.extract_fn = extract_fn + self.weight = weight + + if isinstance(nll_fn, str): + if nll_fn == 'beta': + self.nll_fn = self.nll_beta + elif nll_fn == 'gamma': + self.nll_fn = self.nll_gamma + else: + errormsg = f'The nll_fn (negative log-likelihood function) argument must be "beta" or "gamma", not {conform}.' + raise ValueError(errormsg) + else: + if not callable(conform): + msg = f'The nll_fn (negative log-likelihood function) argument must be a string or a callable function, not {type(nll_fn)}.' + raise Exception(msg) + self.nll_fn = nll_fn + + if isinstance(conform, str): + if conform == 'incident': + self.conform = self.linear_accum + elif conform == 'prevalent': + self.conform = self.linear_interp + else: + errormsg = f'The conform argument must be "prevalent" or "incident", not {conform}.' + raise ValueError(errormsg) + else: + if not callable(conform): + errormsg = f'The conform argument must be a string or a callable function, not {type(conform)}.' + raise TypeError(errormsg) + self.conform = conform + + pass + + @staticmethod + def nll_beta(expected, actual): + """ + For the beta-binomial negative log-likelihood, we begin with a Beta(1,1) prior + and subsequently observe actual['x'] successes (positives) in actual['n'] trials (total observations). + The result is a Beta(actual['x']+1, actual['n']-actual['x']+1) posterior. + We then compare this to the real data, which has expected['x'] successes (positives) in expected['n'] trials (total observations). + To do so, we use a beta-binomial likelihood: + p(x|n, x, a, b) = (n choose x) B(x+a, n-x+b) / B(a, b) + where + x=expected['x'] + n=expected['n'] + a=actual['x']+1 + b=actual['n']-actual['x']+1 + and B is the beta function, B(x, y) = Gamma(x)Gamma(y)/Gamma(x+y) + + We compute the log of p(x|n, x, a, b), noting that gammaln is the log of the gamma function + """ + e_n, e_x = expected['n'], expected['x'] + a_n, a_x = actual['n'], actual['x'] + logL = gammaln(e_n + 1) - gammaln(e_x + 1) - gammaln(e_n - e_x + 1) + logL += gammaln(e_x + a_x + 1) + gammaln(e_n - e_x + a_n - a_x + 1) - gammaln(e_n + a_n + 2) + logL += gammaln(a_n + 2) - gammaln(a_x + 1) - gammaln(a_n - a_x + 1) + return -logL + + @staticmethod + def nll_gamma(expected, actual): + """ + Also called negative binomial, but parameterized differently + The gamma-poisson likelihood is a Poisson likelihood with a gamma-distributed rate parameter + """ + e_n, e_x = expected['n'], expected['x'] + a_n, a_x = actual['n'], actual['x'] + logL = gammaln(e_x + a_x + 1) - gammaln(e_x + 1) - gammaln(e_x + 1) + logL += (e_x + 1) * np.log(e_n) + logL += (a_x + 1) * np.log(a_n) + logL -= (e_x + a_x + 1) * np.log(e_n + a_n) + return -logL + + @staticmethod + def linear_interp(expected, actual): + """ + Simply interpolate + Use for prevalent data like prevalence + """ + t = expected.index + conformed = pd.DataFrame(index=expected.index) + for k in actual: + conformed[k] = np.interp(x=t, xp=actual.index, fp=actual[k]) + + return conformed + + @staticmethod + def linear_accum(expected, actual): + """ + Interpolate in the accumulation, then difference. + Use for incident data like incidence or new_deaths + """ + t = expected.index + t_step = np.diff(t) + assert np.all(t_step == t_step[0]) + ti = np.append(t, t[-1] + t_step) # Add one more because later we'll diff + + sim_t = np.array([sc.datetoyear(t) for t in actual.index if isinstance(t, dt.date)]) + + sdi = np.interp(x=ti, xp=sim_t, fp=actual.cumsum()) + df = pd.Series(sdi.diff(), index=t) + return df + + def eval(self, sim): + """ Compute and return the negative log likelihood """ + actual = self.extract_fn(sim) # Extract + actual = self.conform(self.expected, actual) # Conform + self.nll = self.nll_fn(self.expected, actual) # Negative log likelihood + return self.weight * np.sum(self.nll) + + def __call__(self, sim): + return self.eval(sim) + + def __repr__(self): + return f'Calibration component with name {self.name}' + + def plot(self): + NotImplementedError \ No newline at end of file diff --git a/starsim/demographics.py b/starsim/demographics.py index fd981c0e..475f2188 100644 --- a/starsim/demographics.py +++ b/starsim/demographics.py @@ -93,7 +93,7 @@ def get_births(self): scaled_birth_prob = this_birth_rate * p.rate_units * p.rel_birth * factor scaled_birth_prob = np.clip(scaled_birth_prob, a_min=0, a_max=1) - n_new = int(sc.randround(sim.people.alive.count() * scaled_birth_prob)) + n_new = np.random.binomial(n=sim.people.alive.count(), p=scaled_birth_prob) # Not CRN safe, see issue #404 return n_new def step(self): diff --git a/starsim/distributions.py b/starsim/distributions.py index 759fd0e4..de8ce76a 100644 --- a/starsim/distributions.py +++ b/starsim/distributions.py @@ -688,11 +688,10 @@ def plot_hist(self, n=1000, bins=None, fig_kw=None, hist_kw=None): #%% Specific distributions # Add common distributions so they can be imported directly; assigned to a variable since used in help messages -dist_list = ['random', 'uniform', 'normal', 'lognorm_ex', 'lognorm_im', 'expon', - 'poisson', 'weibull', 'gamma', 'constant', 'randint', 'rand_raw', 'bernoulli', - 'choice', 'histogram'] +dist_list = ['random', 'uniform', 'normal', 'lognorm_ex', 'lognorm_im', 'expon', 'poisson', 'nbinom', + 'weibull', 'gamma', 'constant', 'randint', 'rand_raw', 'bernoulli', 'choice', 'histogram'] __all__ += dist_list -__all__ += ['multi_random'] # Not a dist in the same sense as the others +__all__ += ['multi_random'] # Not a dist in the same sense as the others (e.g. same tests would fail) class random(Dist): @@ -879,6 +878,20 @@ def preprocess_timepar(self, key, timepar): return timepar.values # Also use this for the rest of the loop +class nbinom(Dist): + """ + Negative binomial distribution + + Args: + n (float): the number of successes, > 1 (default 1.0) + p (float): the probability of success in [0,1], (default 0.5) + + """ + def __init__(self, n=1, p=0.5, **kwargs): + super().__init__(distname='negative_binomial', dist=sps.nbinom, n=n, p=p, **kwargs) + return + + class randint(Dist): """ Random integer distribution, on the interval [low, high) diff --git a/starsim/modules.py b/starsim/modules.py index 6ecd6e0a..642103df 100644 --- a/starsim/modules.py +++ b/starsim/modules.py @@ -228,13 +228,12 @@ def init_time(self, force=False): def match_time_inds(self, inds=None): """ Find the nearest matching sim time indices for the current module """ - if inds is None: inds = Ellipsis self_tvec = self.t.abstvec sim_tvec = self.sim.t.abstvec if len(self_tvec) == len(sim_tvec): # Shortcut to avoid doing matching - return inds + return Ellipsis if inds is None else inds else: - out = sc.findnearest(sim_tvec, [inds]) + out = sc.findnearest(sim_tvec, self_tvec) return out def start_step(self): diff --git a/starsim/networks.py b/starsim/networks.py index 5d4649aa..e957c6c9 100644 --- a/starsim/networks.py +++ b/starsim/networks.py @@ -480,6 +480,9 @@ def get_edges(self): self.append(edges) return + def step(self): + pass + class RandomNet(DynamicNetwork): """ Random connectivity between agents """ diff --git a/starsim/parameters.py b/starsim/parameters.py index cdf9ad8b..56db3515 100644 --- a/starsim/parameters.py +++ b/starsim/parameters.py @@ -173,6 +173,10 @@ def _update_dist(self, key, old, new): dist = ss.make_dist(new) self[key] = dist + # It's a function, treat it like a number + elif sc.isfunc(new): + old.set(new) + # Give up else: errormsg = f'Updating dist from {type(old)} to {type(new)} is not supported' diff --git a/starsim/results.py b/starsim/results.py index 23c4e2bf..540e0402 100644 --- a/starsim/results.py +++ b/starsim/results.py @@ -52,7 +52,7 @@ def __repr__(self): out = f'{cls_name}({self.key}):\narray{arrstr}' return out - def __str__(self): + def __str__(self, label=True): cls_name = self.__class__.__name__ try: minval = self.values.min() @@ -61,9 +61,17 @@ def __str__(self): valstr = f'min={minval:n}, mean={meanval:n}, max={maxval:n}' except: valstr = f'{self.values}' - out = f'{cls_name}({self.key}: {valstr})' + labelstr = f'{self.key}: ' if label else '' + out = f'{cls_name}({labelstr}{valstr})' return out + def disp(self, label=True, output=False): + string = self.__str__(label=label) + if not output: + print(string) + else: + return string + def __getitem__(self, key): """ Allow e.g. result['low'] """ if isinstance(key, str): @@ -188,23 +196,43 @@ def __init__(self, module, *args, strict=True, **kwargs): super().__init__(type=Result, strict=strict, *args, **kwargs) return - def __repr__(self, indent=2, **kwargs): # kwargs are not used, but are needed for disp() to work - string = f'Results({self._module})\n' + def __repr__(self, indent=4, head_col=None, key_col=None, **kwargs): # kwargs are not used, but are needed for disp() to work + + def format_head(string): + string = sc.colorize(head_col, string, output=True) if head_col else string + return string + + def format_key(k): + keystr = sc.colorize(key_col, k, output=True) if key_col else k + return keystr + + # Make the heading + string = format_head(f'Results({self._module})') + '\n' + + # Loop over the other items for i,k,v in self.enumitems(): - if k != 'timevec': + if k == 'timevec': + entry = f'array(start={v[0]}, stop={v[-1]})' + elif isinstance(v, Result): + entry = v.disp(label=False, output=True) + else: entry = f'{v}' - if '\n' in entry: # Check if the string is multi-line - lines = entry.splitlines() - entry = f'{i}. {lines[0]}\n' - entry += '\n'.join(' '*indent + f'{i}.' + line for line in lines[1:]) - string += entry + '\n' - else: - string += f'{i}. {v}\n' + + if '\n' in entry: # Check if the string is multi-line + lines = entry.splitlines() + entry = f'{i}. {format_key(k)}: {lines[0]}\n' + entry += '\n'.join(' '*indent + f'{i}.' + line for line in lines[1:]) + string += entry + '\n' + else: + string += f'{i}. {format_key(k)}: {entry}\n' string = string.rstrip() return string + def __str__(self, indent=4, head_col='cyan', key_col='green'): + return self.__repr__(indent=indent, head_col=head_col, key_col=key_col) + def disp(self, *args, **kwargs): - print(super().__repr__(*args, **kwargs)) + print(super().__str__(*args, **kwargs)) return def append(self, arg, key=None): @@ -217,13 +245,10 @@ def append(self, arg, key=None): result = arg if not isinstance(result, Result): - warnmsg = f'You are adding a result of type {type(result)} to Results, which is inadvisable.' + warnmsg = f'You are adding a result of type {type(result)} to Results, which is inadvisable; if you intended to add it, use results[key] = value instead' ss.warn(warnmsg) if result.module != self._module: - if result.module: - warnmsg = f'You are adding a result from module {result.module} to module {self._module}; check that this is intentional.' - ss.warn(warnmsg) result.module = self._module super().append(result, key=key) diff --git a/starsim/run.py b/starsim/run.py index 718f705b..ce080445 100644 --- a/starsim/run.py +++ b/starsim/run.py @@ -26,7 +26,6 @@ class MultiSim: def __init__(self, sims=None, base_sim=None, label=None, n_runs=4, initialize=False, inplace=True, debug=False, **kwargs): # Handle inputs - super().__init__(**kwargs) if base_sim is None: if isinstance(sims, ss.Sim): base_sim = sims diff --git a/starsim/settings.py b/starsim/settings.py index 70bb3efa..01914bd1 100644 --- a/starsim/settings.py +++ b/starsim/settings.py @@ -68,6 +68,9 @@ def get_orig_options(): optdesc.license = 'Whether to print the license on import' options.license = sc.parse_env('STARSIM_LICENSE', False, 'bool') + optdesc.show_type = 'Whether to show the type of different numbers (e.g. np.float64(1.3) instead of 1.3)' + options.show_type = sc.parse_env('STARSIM_SHOW_TYPE', False, 'bool') + optdesc.warnings = 'How warnings are handled: options are "warn" (default), "print", and "error"' options.warnings = sc.parse_env('STARSIM_WARNINGS', 'warn', 'str') @@ -83,6 +86,9 @@ def get_orig_options(): optdesc.jupyter = 'Set whether to use Jupyter settings: -1=auto, 0=False, 1=True' options.jupyter = sc.parse_env('STARSIM_JUPYTER', -1, 'int') + optdesc.reticulate = 'Set whether to use Reticulate (R) settings' + options.reticulate = sc.parse_env('STARSIM_RETICULATE', False, 'bool') + optdesc.precision = 'Set arithmetic precision' options.precision = sc.parse_env('STARSIM_PRECISION', 64, 'int') @@ -172,7 +178,9 @@ def set(self, key=None, value=None, use=False, **kwargs): # Handle special cases if key == 'precision': - self.reset_precision() + self.set_precision() + elif key == 'show_type': + self.set_show_type() return @@ -215,6 +223,7 @@ def changed(self, key): return None def set_precision(self): + """ Change the arithmetic precision used by Starsim/NumPy """ if self.precision == 32: dtypes.int = np.int32 dtypes.float = np.float32 @@ -226,6 +235,15 @@ def set_precision(self): raise ValueError(errormsg) return + def set_show_type(self): + """ Set NumPy to show numbers as just e.g. 1.3 (Starsim default) or np.float64(1.3) (NumPy default) """ + if self.show_type: + np.set_printoptions(legacy=False) + elif sc.compareversions(np, '>=2.0'): # Numpy crashes with this option otherwise + np.set_printoptions(legacy='1.25') + return + # Create the options on module load options = Options() +options.set_show_type() diff --git a/starsim/sim.py b/starsim/sim.py index 125bb130..6b90119c 100644 --- a/starsim/sim.py +++ b/starsim/sim.py @@ -125,15 +125,20 @@ def modules(self): self.analyzers(), ) - def init(self, **kwargs): - """ Perform all initializations for the sim """ + def init(self, force=False, **kwargs): + """ + Perform all initializations for the sim + Args: + force (bool): whether to overwrite sim attributes even if they already exist + kwargs (dict): passed to ss.People() + """ # Validation and initialization -- this is "pre" ss.set_seed(self.pars.rand_seed) # Reset the seed before the population is created -- shouldn't matter if only using Dist objects self.pars.validate() # Validate parameters self.init_time() # Initialize time self.init_people(**kwargs) # Initialize the people - self.init_sim_attrs() + self.init_sim_attrs(force=force) self.init_mods_pre() # Final initializations -- this is "post" @@ -183,11 +188,17 @@ def init_people(self, verbose=None, **kwargs): self.people.link_sim(self) return self.people - def init_sim_attrs(self): + def init_sim_attrs(self, force=False): """ Move initialized modules to the sim """ keys = ['label', 'demographics', 'networks', 'diseases', 'interventions', 'analyzers', 'connectors'] for key in keys: - setattr(self, key, self.pars.pop(key)) + orig = getattr(self, key, None) + if not force and orig is not None: + if key != 'label': # Don't worry about overwriting the label + warnmsg = f'Skipping key "{key}" in parameters since already present in sim and force=False' + ss.warn(warnmsg) + else: + setattr(self, key, self.pars.pop(key)) return def init_mods_pre(self): @@ -488,15 +499,6 @@ def save(self, filename=None, shrink=None, **kwargs): sc.save(filename=filename, obj=sim) return filename - @staticmethod - def load(filename, *args, **kwargs): - """ Load from disk from a gzipped pickle """ - sim = sc.load(filename, *args, **kwargs) - if not isinstance(sim, Sim): # pragma: no cover - errormsg = f'Cannot load object of {type(sim)} as a Sim object' - raise TypeError(errormsg) - return sim - def to_json(self, filename=None, keys=None, tostring=False, indent=2, verbose=False, **kwargs): """ Export results and parameters as JSON. @@ -588,9 +590,9 @@ def plot(self, key=None, fig=None, style='fancy', show_data=True, show_skipped=F if key is not None: if isinstance(key, str): - flat = {k:v for k,v in flat.items() if (key in k)} + flat = {k:v for k,v in flat.items() if (key.lower() in k)} else: - flat = {k:flat[k] for k in key} + flat = {k.lower():flat[k.lower()] for k in key} # Get the figure if fig is None: diff --git a/starsim/utils.py b/starsim/utils.py index fc5972eb..0d916af9 100644 --- a/starsim/utils.py +++ b/starsim/utils.py @@ -13,7 +13,7 @@ # What functions are externally visible __all__ = ['ndict', 'warn', 'find_contacts', 'set_seed', 'check_requires', 'standardize_netkey', - 'standardize_data', 'validate_sim_data', 'return_fig'] + 'standardize_data', 'validate_sim_data', 'load', 'save', 'return_fig'] class ndict(sc.objdict): @@ -383,6 +383,39 @@ def combine_rands(a, b): #%% Other helper functions +def load(filename, **kwargs): + """ + Alias to Sciris sc.loadany() + + Since Starsim uses Sciris for saving objects, they can be loaded back using + this function. This can also be used to load other objects of known type + (e.g. JSON), although this usage is discouraged. + + Args: + filename (str/path): the name of the file to load + kwargs (dict): passed to sc.loadany() + + Returns: + The loaded object + """ + return sc.loadany(filename, **kwargs) + + +def save(filename, obj, **kwargs): + """ + Alias to Sciris sc.save() + + While some Starsim objects have their own save methods, this function can be + used to save any arbitrary object. It can then be loaded with ss.load(). + + Args: + filename (str/path): the name of the file to save + obj (any): the object to save + kwargs (dict): passed to sc.save() + """ + return sc.save(filename=filename, obj=obj, **kwargs) + + class shrink: """ Define a class to indicate an object has been shrunken """ def __repr__(self): @@ -393,7 +426,8 @@ def __repr__(self): def return_fig(fig, **kwargs): """ Do postprocessing on the figure: by default, don't return if in Jupyter, but show instead """ is_jupyter = [False, True, sc.isjupyter()][ss.options.jupyter] - if is_jupyter: + is_reticulate = ss.options.reticulate + if is_jupyter or is_reticulate: print(fig) plt.show() return None diff --git a/starsim/version.py b/starsim/version.py index 99811c16..18730c29 100644 --- a/starsim/version.py +++ b/starsim/version.py @@ -4,6 +4,6 @@ __all__ = ['__version__', '__versiondate__', '__license__'] -__version__ = '2.1.1' -__versiondate__ = '2024-11-8' +__version__ = '2.2.0' +__versiondate__ = '2024-11-18' __license__ = f'Starsim {__version__} ({__versiondate__}) — © 2023-2024 by IDM' diff --git a/tests/archive/samples-documentation.ipynb b/tests/archive/samples-documentation.ipynb new file mode 100644 index 00000000..3b46c4fa --- /dev/null +++ b/tests/archive/samples-documentation.ipynb @@ -0,0 +1,1168 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "# Managing samples" + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": {}, + "source": [ + "As STIsim models are usually stochastic, for a single scenario it is often desirable to run the model multiple times with different random seeds. The role of the `Samples` class is to facilitate working with large numbers of simulations and scenarios, to ease:\n", + "\n", + "- Loading large result sets\n", + "- Filtering/selecting simulation runs\n", + "- Plotting individual simulations and aggregate results\n", + "- Slicing result sets to compare scenarios\n", + "\n", + "Essentially, if we think of the processed results of a model run as being\n", + "\n", + "- A collection of scalar outputs (e.g., cumulative infections, total deaths)\n", + "- A dataframe of time-varying outputs (e.g., new diagnoses per day, number of people on treatment each day)\n", + "\n", + "then the classes `Dataset` and `Samples` manage collections of these results. In particular, the `Samples` class manages different random samples of the same parameters, and the `Dataset` class manages a collection of `Samples`. \n", + "\n", + "
\n", + "These classes are particularly designed to facilitate working with tens of thousands of simulation runs, where other approaches such as those based on the `MultiSim` class may not be feasible.\n", + "
\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "import starsim as ss\n", + "import numpy as np\n", + "import pandas as pd\n", + "from pathlib import Path\n", + "import matplotlib.pyplot as plt\n", + "import sciris as sc" + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "## Obtaining simulation output" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "To demonstrate usage of this class, we will first consider constructing the kinds of output that the `Samples` class stores. We begin by running a basic simulation using the SIR model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "ppl = ss.People(5000)\n", + "net = ss.ndict(ss.RandomNet(n_contacts=ss.poisson(5)))\n", + "sir = ss.SIR()\n", + "sim = ss.Sim(people=ppl, networks=net, diseases=sir, rand_seed=0)\n", + "sim.run();" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "### Dataframe output\n", + "\n", + "A `Sim` instance is (in general) too large and complex to efficiently store on disk - the file size and loading time make it prohibitive to work with tens of thousands of simulations. Therefore, rather than storing entire `Sim` instances, we instead store dataframes containing just the simulation results and any other pre-processed calculated quantities. There are broadly speaking two types of outputs\n", + "\n", + "- Scalar outputs at each timepoint (e.g., daily new cases)\n", + "- Scalar outputs for each simulation (e.g., total number of deaths)\n", + "\n", + "These outputs can each be produced from a `Sim` - the former has a tabular structure, and the latter has a dictionary structure (which can later be assembled into a table where the rows correspond to each simulation). The `export_df` method is a quick way to obtain a dataframe with the appropriate structure retaining all results from the `Sim`.\n", + "\n", + "\n", + "
\n", + "In real-world use, it is often helpful to write your own function to extract a dataframe of simulation outputs, because typically some of the outputs need to be extracted from custom Analyzers.\n", + "
\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "sim.to_df()" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "### Scalar/summary outputs\n", + "\n", + "We can also consider extracting a summary dictionary of scalar values. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "summary = {}\n", + "summary['seed'] = sim.pars['rand_seed']\n", + "summary['p_death'] = sim.diseases[0].pars.p_death.pars.p\n", + "summary['cum_infections'] = sum(sim.results.sir.new_infections)\n", + "summary['cum_deaths'] = sum(sim.results.new_deaths)\n", + "summary" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "
\n", + "Notice how in the example above, the summary contains both simulation inputs (seed, probability of death) as well as simulation outputs (total infections, total deaths). The simulation summary should contain sufficient information about the simulation inputs to identify the simulation. The seed should generally be present. The other inputs normally correspond to variables that scenarios are being run over. In this example, we will run scenarios comparing simulations with different probabilities of death. Therefore, we need to include the death probability in the simulation summary. \n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "### Running the model\n", + "\n", + "For usage at scale, the steps of creating a simulation, running it and producing these outputs are usually encapsulated in functions" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "def get_sim(seed, p_death):\n", + " ppl = ss.People(5000)\n", + " net = ss.RandomNet(n_contacts=ss.poisson(5))\n", + " sir = ss.SIR(p_death=p_death)\n", + " sim = ss.Sim(people=ppl, networks=net, diseases=sir, rand_seed=seed)\n", + " sim.init(verbose=0)\n", + " return sim\n", + " \n", + "def run_sim(seed, p_death):\n", + " sim = get_sim(seed, p_death)\n", + " sim.run(verbose=0)\n", + " df = sim.to_df()\n", + " \n", + " summary = {}\n", + " summary['seed'] = sim.pars['rand_seed']\n", + " summary['p_death']= sim.diseases[0].pars.p_death.pars.p\n", + " summary['cum_infections'] = sum(sim.results.sir.new_infections)\n", + " summary['cum_deaths'] = sum(sim.results.new_deaths)\n", + " \n", + " return df, summary" + ] + }, + { + "cell_type": "markdown", + "id": "14", + "metadata": {}, + "source": [ + "
\n", + "The functions above could be combined into a single function. However, in real world usage it is often convenient to be able to construct a simulation independently of running it (e.g., for diagnostic purposes or to allow running the sim in a range of different ways). The suggested structure above, with a get_sim() function and a run_sim() function are recommended as standard practice.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "Now running a simulation for a given beta/seed value and returning the processed outputs can be done in a single step" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "# Scalar output\n", + "df, summary = run_sim(0, 0.2);\n", + "summary" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "We can produce all of the samples associated with a scenario by iterating over the input seed values. This is being done in a basic loop here, but could be done in more sophistical ways to leverage parallel computing (e.g., with `sc.parallelize` for single host parallelization, or with `celery` for distributed computation). " + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "# Run a collection of sims\n", + "n = 20\n", + "seeds = np.arange(n)\n", + "outputs = [run_sim(seed, 0.2) for seed in seeds]" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Saving and loading the samples" + ] + }, + { + "cell_type": "markdown", + "id": "20", + "metadata": {}, + "source": [ + "We have now produced simulation outputs (dataframes and summary statistics) for 20 simulation runs. The `outputs` here are a list of tuples, containing the dataframe and dictionary outputs for each sample. This list can be passed to the `cvv.Samples` class to produce a single compressed file on disk" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21", + "metadata": {}, + "outputs": [], + "source": [ + "resultsdir = Path('results')\n", + "resultsdir.mkdir(exist_ok=True, parents=True)\n", + "ss.Samples.new(resultsdir, outputs, identifiers=[\"p_death\"])\n", + "list(resultsdir.iterdir())" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "Notice that a list of `identifiers` should be passed to the `Samples` constructor. This is a list of keys in the simulation summary dictionaries that identifies the scenario. These would be model inputs rather than model outputs, and they should be the same for all of the outputs passed into the `Samples` object. If no file name is explicitly provided, the file will automatically be assigned a name based on the identifiers.\n", + "\n", + "
\n", + "The Samples file internally contains metadata recording the identifiers. When Samples are accessed using the Dataset class, they can be accessed via the internal metadata. Therefore for a typical workflow, the file name largely doesn't matter, and it usually doesn't need to be manually specified.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "The saved file can be loaded and accessed via the `Samples` class. **Importantly, individual files can be extracted from a `.zip` file without decompressing the entire archive**. This means that loading the summary dataframe and using it to selectively load the full outputs for individual runs can be done efficiently. For example, loading retrieving a single result from a `Samples` file would take a similar amount of time regardless of whether the file contained 10 samples or 100000 samples. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "# Load the samples\n", + "res = ss.Samples('results/0.2.zip')\n", + "res.summary" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "When the `Samples` file was created, a dictionary of scalars was provided for each result. These are automatically used to populate a 'summary' dataframe, where each identifier (and the seed) are used as the index, and the remaining keys appear as columns, as shown above. As a shortcut, columns of the summary dataframe can be accessed by indexing the `Samples` object directly, without having to access the `.summary` attribute e.g.," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "res['cum_infections']" + ] + }, + { + "cell_type": "markdown", + "id": "27", + "metadata": {}, + "source": [ + "Each simulation is uniquely identified by its seed, and the time series dataframe for each simulation can be accessed by indexing the `Samples` object with the seed:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], + "source": [ + "res[0]" + ] + }, + { + "cell_type": "markdown", + "id": "29", + "metadata": {}, + "source": [ + "The dataframes in the `Samples` object are cached, so that the dataframes don't all need to be loaded in order to start working with the file. The first time a dataframe is accessed, it will be loaded from disk. Subsequent requests for the dataframe will return a cached version instead. The cached dataframe is copied each time it is retrieved, to prevent accidentally modifying the original data. " + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "## Common analysis operations\n", + "\n", + "Here are some examples of common analyses that can be performed using functionality in the `Samples` class\n", + "\n", + "### Plotting summary quantities\n", + "\n", + "Often it's useful to be able plot distributions of summary quantities, such as the total infections. This can be performed by directly indexing the `Samples` object and then using the appropriate plotting command:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(res['cum_infections'], density=True)\n", + "\n", + "plt.xlabel('Total infections')\n", + "plt.ylabel('Probability density')" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "### Plotting time series\n", + "\n", + "Time series plots can be obtained by accessing the dataframes associated with each seed, and then plotting quantities from those. For convenience, iterating over the `Samples` object will automatically iterate over all of the dataframes associated with each seed. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "for df in res:\n", + " plt.plot(df['sir.new_infections'], color='b', alpha=0.1)" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "### Other ways to access content\n", + "\n", + "We have seen so far that we can use\n", + "\n", + "- `res.summary` - retrieve dataframe of summary outputs\n", + "- `res[summary_column]` - retrieve a column of the summary dataframe\n", + "- `res[seed]` - retrieve the time series dataframe associated with one of the simulations\n", + "- `for df in res` - iterate over time series dataframes\n", + "\n", + "Sometimes it is useful to have access to both the summary dictionary and the time series dataframe associated with a single sample. These can be accessed using the `get` method, which takes in a seed, and returns both outputs for that seed together:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "res.get(0) # Retrieve both summary quantities and dataframes" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "In the same way that it is possible to index the `Samples` object directly in order to retrieve columns from the summary dataframe, it is also possible to directly index the `Samples` object to get a column of the time series dataframe. In this case, pass a tuple of items to the `Samples` object, where the first item is the seed, and the second is a column from the time series dataframe. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "res[0,'sir.n_infected'] # Equivalent to `res[0]['sir.n_infected']`" + ] + }, + { + "cell_type": "markdown", + "id": "38", + "metadata": {}, + "source": [ + "### Filtering results" + ] + }, + { + "cell_type": "markdown", + "id": "39", + "metadata": {}, + "source": [ + "The `.seeds` attribute contains a listing of seeds, which can be helpful for iteration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40", + "metadata": {}, + "outputs": [], + "source": [ + "res.seeds" + ] + }, + { + "cell_type": "markdown", + "id": "41", + "metadata": {}, + "source": [ + "The seeds are drawn from the summary dataframe, which defines which seeds are accessible via the `Samples` object. Therefore, you can drop rows from the summary dataframe to filter the results. For example, suppose we only wanted to analyze simulations with over 4900 deaths. We could retrieve a copy of the summary dataframe that only contains matching simulations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "res.summary.loc[res['cum_infections']>4900]" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ + "We can then make a copy of the results and write the reduced summary dataframe back to that object" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "res2 = res.copy()\n", + "res2.summary = res.summary.loc[res['cum_infections']>4900]" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "
\n", + "Unlike sc.dcp(), copying using the .copy() method only deep copies the summary dataframe. It does not duplicate the time series dataframes or the cache. For Samples objects, it is therefore generally preferable to use .copy().\n", + "
\n", + "\n", + "\n", + "Now notice that there are fewer samples, and the seeds have been filtered" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "len(res)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "len(res2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "48", + "metadata": {}, + "outputs": [], + "source": [ + "res2.seeds" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "plt.hist(res2['cum_infections'], density=True)\n", + "plt.xlabel('Total infections')\n", + "plt.ylabel('Probability density')" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "### Applying functions and transformations" + ] + }, + { + "cell_type": "markdown", + "id": "51", + "metadata": {}, + "source": [ + "Sometimes it might be necessary to calculate quantities that are derived from the time series dataframes. These could be simple scalar values, such as totals or averages that had not been computed ahead of time, or extracting values from each simulation at a particular point in time. As an alternative to writing a loop that iterates over the seeds, the `.apply()` method takes in a function and maps it to every dataframe. This makes it quick to construct lists or arrays with scalar values extracted from the time series. For example, suppose we wanted to extract the peak number of people infected from each simulation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "peak_infections = lambda df: df['sir.n_infected'].max()\n", + "res.apply(peak_infections)" + ] + }, + { + "cell_type": "markdown", + "id": "53", + "metadata": {}, + "source": [ + "## Options when loading" + ] + }, + { + "cell_type": "markdown", + "id": "54", + "metadata": {}, + "source": [ + "There are two options available when loading that can change how the `Samples` class interacts with the file on disk:\n", + "\n", + "- `memory_buffer` - copy the entire file into memory. This prevents the file from being locked on disk and allows scripts to be re-run and results regenerated while still running the analysis notebook. This defaults to `True` for convenience, but loading the entire file into memory can be problematic if the file is large (e.g., >1GB) in which case setting `memory_buffer=False` may be preferable\n", + "- `preload` - Populate the cache in one step. This facilitates interactive usage of the analysis notebook by making the runtime of analysis functions predictable (since all results will be retrieved from the cache) at the expense of a long initial load time\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "### Implementation details\n", + "\n", + "If the file is loaded from a memory buffer, the `._zipfile` attribute will be populated. A helper property `.zipfile` is used to access the buffer, so if caching is not used, `.zipfile` returns the actual file on disk rather than the buffer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "res = ss.Samples('results/0.2.zip', memory_buffer=True) # Copy the entire file into memory\n", + "print(res._zipfile)\n", + "print(res.zipfile)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "res = ss.Samples('results/0.2.zip', memory_buffer=False) # Copy the entire file into memory\n", + "print(res._zipfile)\n", + "print(res.zipfile)" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "The dataframes associated with the individual dataframes are cached on access, so `pd.read_csv()` only needs to be called once. The cache starts out empty:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "res._cache" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "When a dataframe is accessed, it is automatically stored in the cache:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "res[0]\n", + "res._cache.keys()" + ] + }, + { + "cell_type": "markdown", + "id": "62", + "metadata": {}, + "source": [ + "This means that iterating through the dataframes the first time can be slow (but in general, iterating over all dataframes is avoided in favour of either only using summary outputs, or accessing a subset of the runs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "with sc.Timer():\n", + " for df in res:\n", + " continue" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64", + "metadata": {}, + "outputs": [], + "source": [ + "with sc.Timer():\n", + " for df in res:\n", + " continue" + ] + }, + { + "cell_type": "markdown", + "id": "65", + "metadata": {}, + "source": [ + "The `preload` option populates the entire cache in advance. This makes creating the `Samples` object slower, but operating on the dataframes afterwards will be consistently fast. This type of usage can be useful when wanting to load large files in the background and then interactively work with them afterwards. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66", + "metadata": {}, + "outputs": [], + "source": [ + "with sc.Timer():\n", + " res = ss.Samples('results/0.2.zip', preload=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67", + "metadata": {}, + "outputs": [], + "source": [ + "with sc.Timer():\n", + " for df in res:\n", + " continue" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68", + "metadata": {}, + "outputs": [], + "source": [ + "with sc.Timer():\n", + " for df in res:\n", + " continue" + ] + }, + { + "cell_type": "markdown", + "id": "69", + "metadata": {}, + "source": [ + "Together, these options provide some flexibility in terms of memory and time demands to suit analyses at various different scales." + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "## Running scenarios" + ] + }, + { + "cell_type": "markdown", + "id": "71", + "metadata": {}, + "source": [ + "Suppose we wanted to compare a range of different `p_death` values and `initial` values (initial number of infections). We might define these runs as" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "72", + "metadata": {}, + "outputs": [], + "source": [ + "initials = np.arange(1,4)\n", + "p_deaths = np.arange(0,1,0.25)" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "Recall that our `run_sim()` function had an argument for `p_death`. We can extend this to include the `initial` parameter too. We can actually generalize this further by passing the parameters as keyword arguments to avoid needing to hard-code all of them. Note that we also need to add the `initial` value to the summary outputs:" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "74", + "metadata": {}, + "outputs": [], + "source": [ + "def get_sim(seed, **kwargs):\n", + " ppl = ss.People(5000)\n", + " net = ss.RandomNet(n_contacts=ss.poisson(5))\n", + " sir = ss.SIR(pars=kwargs)\n", + " sim = ss.Sim(people=ppl, networks=net, diseases=sir, rand_seed=seed)\n", + " sim.init(verbose=0)\n", + " return sim\n", + " \n", + "def run_sim(seed, **kwargs):\n", + " sim = get_sim(seed, **kwargs)\n", + " sim.run(verbose=0)\n", + " df = sim.to_df()\n", + " \n", + " summary = {}\n", + " summary['seed'] = sim.pars['rand_seed']\n", + " summary['p_death']= sim.diseases[0].pars.p_death.pars.p\n", + " summary['initial']= sim.diseases[0].pars.init_prev\n", + " summary['cum_infections'] = sum(sim.results.sir.new_infections)\n", + " summary['cum_deaths'] = sum(sim.results.new_deaths)\n", + " \n", + " return df, summary" + ] + }, + { + "cell_type": "markdown", + "id": "75", + "metadata": {}, + "source": [ + "We can now easily run a set of scenarios with different values of `p_death` and save each one to a separate `Samples` object. Note that when we create the `Samples` objects now, we also want to specify that `'initial'` is one of the identifiers for the scenarios:" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "76", + "metadata": {}, + "outputs": [], + "source": [ + "# Clear the existing results\n", + "for file_path in resultsdir.glob('*'):\n", + " file_path.unlink()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77", + "metadata": {}, + "outputs": [], + "source": [ + "# Run the sweep over initial and p_death\n", + "n = 20\n", + "seeds = np.arange(n)\n", + "for init_prev in initials:\n", + " for p_death in p_deaths:\n", + " outputs = [run_sim(seed, init_prev=init_prev, p_death=p_death) for seed in seeds]\n", + " ss.Samples.new(resultsdir, outputs, [\"p_death\", \"init_prev\"])" + ] + }, + { + "cell_type": "markdown", + "id": "78", + "metadata": {}, + "source": [ + "The results folder now contains a collection of saved `Samples` objects. Notice how the automatically selected file names now contain both the `p_death` value and the `initial` value, because they were both specified as identifiers. We can load one of these objects in to see how these identifiers are stored and accessed inside the `Samples` class:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79", + "metadata": {}, + "outputs": [], + "source": [ + "list(resultsdir.iterdir())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80", + "metadata": {}, + "outputs": [], + "source": [ + "res = ss.Samples('results/0.25-2.zip')" + ] + }, + { + "cell_type": "markdown", + "id": "81", + "metadata": {}, + "source": [ + "The 'id' of a `Samples` object is a dictionary of the identifiers, which makes it easy to access the input parameters associated with a set of scenario runs:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82", + "metadata": {}, + "outputs": [], + "source": [ + "res.id" + ] + }, + { + "cell_type": "markdown", + "id": "83", + "metadata": {}, + "source": [ + "The 'identifier' is a tuple of these values, which is suitable for use as a dictionary key. This can be useful for accumulating and comparing variables across scenarios" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84", + "metadata": {}, + "outputs": [], + "source": [ + "res.identifier" + ] + }, + { + "cell_type": "markdown", + "id": "85", + "metadata": {}, + "source": [ + "### Loading multiple scenarios\n", + "\n", + "We saw above that we now have a directory full of `.zip` files corresponding to the various scenario runs. These can be accessed using the `Dataset` class, which facilitates accessing multiple instances of `Samples`. We can pass the folder containing the results to the `Dataset` constructor to load them all:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86", + "metadata": {}, + "outputs": [], + "source": [ + "results = ss.Dataset(resultsdir)\n", + "results" + ] + }, + { + "cell_type": "markdown", + "id": "87", + "metadata": {}, + "source": [ + "The `.ids` attribute lists all of the values available across scenarios in the results folder:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88", + "metadata": {}, + "outputs": [], + "source": [ + "results.ids" + ] + }, + { + "cell_type": "markdown", + "id": "89", + "metadata": {}, + "source": [ + "The individual results can be accessed by indexing the `Dataset` instance using the values of the identifiers. For example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90", + "metadata": {}, + "outputs": [], + "source": [ + "results[0.25,2]" + ] + }, + { + "cell_type": "markdown", + "id": "91", + "metadata": {}, + "source": [ + "This indexing operation is sensitive to the order in which the identifiers are specified. The `.get()` method allows you to specify them as key-value pairs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92", + "metadata": {}, + "outputs": [], + "source": [ + "results.get(initial=2, p_death=0.25)" + ] + }, + { + "cell_type": "markdown", + "id": "93", + "metadata": {}, + "source": [ + "Iterating over the `Dataset` will iterate over the `Samples` instances contained within it" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": {}, + "outputs": [], + "source": [ + "for res in results:\n", + " print(res)" + ] + }, + { + "cell_type": "markdown", + "id": "95", + "metadata": {}, + "source": [ + "This can be used to extract and compare values across scenarios. For example, we could consider the use case of making a plot that compares total deaths across scenarios:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96", + "metadata": {}, + "outputs": [], + "source": [ + "labels = []\n", + "y = []\n", + "yerr = []\n", + "\n", + "for res in results:\n", + " labels.append(res.id)\n", + " y.append(res['cum_deaths'].median())\n", + "\n", + "plt.barh(np.arange(len(results)),y, tick_label=labels)\n", + "plt.xlabel('Median total deaths');\n", + "plt.ylabel('Scenario')" + ] + }, + { + "cell_type": "markdown", + "id": "97", + "metadata": {}, + "source": [ + "### Filtering scenarios\n", + "\n", + "Often plots need to be generated for a subset of scenarios e.g., for sensitivity analysis or to otherwise compare specific scenarios. `Dataset.filter` returns a new `Dataset` containing a subset of the results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98", + "metadata": {}, + "outputs": [], + "source": [ + "for res in results.filter(initial=2):\n", + " print(res)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99", + "metadata": {}, + "outputs": [], + "source": [ + "for res in results.filter(p_death=0.25):\n", + " print(res)" + ] + }, + { + "cell_type": "markdown", + "id": "100", + "metadata": {}, + "source": [ + "This is also a quick and efficient operation, so you can easily embed filtering commands inside the analysis to select subsets of the scenarios for plotting and other output generation. For instance:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "101", + "metadata": {}, + "outputs": [], + "source": [ + "for res, color in zip(results.filter(initial=2), sc.gridcolors(4)):\n", + " plt.plot(res[0].index, np.median([df['new_deaths'] for df in res], axis=0), color=color, label=f'p_death = {res.id[\"p_death\"]}')\n", + "plt.legend()\n", + "plt.title('Sensitivity to p_death (initial = 2)')\n", + "plt.xlabel('Year')\n", + "plt.ylabel('New deaths')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "102", + "metadata": {}, + "outputs": [], + "source": [ + "for res, color in zip(results.filter(p_death=0.25), sc.gridcolors(3)):\n", + " plt.plot(res[0].index, np.median([df['new_deaths'] for df in res], axis=0), color=color, label=f'initial = {res.id[\"initial\"]}')\n", + "plt.legend()\n", + "plt.title('Sensitivity to initial infections (p_death = 0.25)')\n", + "plt.xlabel('Year')\n", + "plt.ylabel('New deaths')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "103", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.2" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "401.8px" + }, + "toc_section_display": true, + "toc_window_display": true + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/baseline.json b/tests/baseline.json index 0e7720a9..5b9e462f 100644 --- a/tests/baseline.json +++ b/tests/baseline.json @@ -1,26 +1,28 @@ { "summary": { - "timevec": 2020.0, - "births_new": 46.84158415841584, - "births_cumulative": 2267.7425742574255, - "births_cbr": 19.93833106141367, - "deaths_new": 9.673267326732674, - "deaths_cumulative": 468.5742574257426, - "deaths_cmr": 4.118825151847284, - "sir_n_susceptible": 2440.029702970297, - "sir_n_infected": 3630.3960396039606, - "sir_n_recovered": 5676.693069306931, - "sir_prevalence": 0.32402568798505815, - "sir_new_infections": 122.34653465346534, - "sir_cum_infections": 12357.0, - "sis_n_susceptible": 4784.306930693069, - "sis_n_infected": 6973.504950495049, - "sis_prevalence": 0.5720906510271361, - "sis_new_infections": 193.5742574257426, - "sis_cum_infections": 19551.0, - "sis_rel_sus": 0.5019197711850157, - "n_alive": 11747.118811881188, - "new_deaths": 10.693069306930694, - "cum_deaths": 1072.0 + "births_new": 48.257425742574256, + "births_cumulative": 2343.3069306930693, + "births_cbr": 20.43178207644599, + "deaths_new": 9.712871287128714, + "deaths_cumulative": 470.58415841584156, + "deaths_cmr": 4.112394571867341, + "randomnet_n_edges": 58901.33663366337, + "mfnet_n_edges": 4004.732673267327, + "maternalnet_n_edges": 0.0, + "sir_n_susceptible": 2464.970297029703, + "sir_n_infected": 3658.227722772277, + "sir_n_recovered": 5694.504950495049, + "sir_prevalence": 0.32462009407521675, + "sir_new_infections": 122.81188118811882, + "sir_cum_infections": 12404.0, + "sis_n_susceptible": 4828.3267326732675, + "sis_n_infected": 7000.19801980198, + "sis_prevalence": 0.5702209995778549, + "sis_new_infections": 195.01980198019803, + "sis_cum_infections": 19697.0, + "sis_rel_sus": 0.5033450153204474, + "n_alive": 11817.70297029703, + "new_deaths": 10.821782178217822, + "cum_deaths": 1084.0 } } \ No newline at end of file diff --git a/tests/benchmark.json b/tests/benchmark.json index 00f78bb2..38baff8b 100644 --- a/tests/benchmark.json +++ b/tests/benchmark.json @@ -1,12 +1,12 @@ { "time": { - "initialize": 0.055, - "run": 1.013 + "initialize": 0.053, + "run": 0.914 }, "parameters": { "n_agents": 10000, "dur": 20, "dt": 0.2 }, - "cpu_performance": 0.9665005580733697 + "cpu_performance": 0.8734404449460742 } \ No newline at end of file diff --git a/tests/devtests/devtest_axbo.py b/tests/devtests/devtest_axbo.py new file mode 100644 index 00000000..98f36ac1 --- /dev/null +++ b/tests/devtests/devtest_axbo.py @@ -0,0 +1,146 @@ +""" +Test calibration +""" + +#%% Imports and settings +import sciris as sc +import starsim as ss +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +from ax.plot.contour import plot_contour +from ax.plot.trace import optimization_trace_single_method +from ax.service.managed_loop import optimize +from ax.utils.notebook.plotting import init_notebook_plotting, render + +do_plot = 1 +do_save = 0 +n_agents = 2e3 + +#%% Helper functions + +def make_sim(): + sir = ss.SIR( + beta = ss.beta(0.9), + dur_inf = ss.lognorm_ex(mean=ss.dur(6)), + init_prev = ss.bernoulli(0.01), + ) + + #deaths = ss.Deaths(death_rate=15) + #births = ss.Births(birth_rate=15) + + random = ss.RandomNet(n_contacts=ss.poisson(4)) + + sim = ss.Sim( + dt = 1, + unit = 'day', + n_agents = n_agents, + #total_pop = 9980999, + start = sc.date('2024-01-01'), + stop = sc.date('2024-01-31'), + diseases = sir, + networks = random, + #demographics = [deaths, births], + ) + + return sim + + +def build_sim(sim, calib_pars, **kwargs): + """ Modify the base simulation by applying calib_pars """ + + for k, v in calib_pars.items(): + if k == 'beta': + sim.diseases.sir.pars['beta'] = ss.beta(v) + elif k == 'dur_inf': + sim.diseases.sir.pars['dur_inf'] = ss.lognorm_ex(mean=ss.dur(v)), #ss.dur(v) + elif k == 'n_contacts': + sim.networks.randomnet.pars.n_contacts = v # Typically a Poisson distribution, but this should set the distribution parameter value appropriately + else: + sim.pars[k] = v # Assume sim pars + + return sim + +def eval_sim(pars): + sim = make_sim() + sim.init() + sim = build_sim(sim, pars) + sim.run() + #print('pars:', pars, ' --> Final prevalence:', sim.results.sir.prevalence[-1]) + fig = sim.plot() + fig.suptitle(pars) + fig.subplots_adjust(top=0.9) + plt.show() + + return dict( + prevalence_error = ((sim.results.sir.prevalence[-1] - 0.10)**2, None), + prevalence = (sim.results.sir.prevalence[-1], None), + ) + + +#%% Define the tests +def test_calibration(do_plot=False): + sc.heading('Testing calibration') + + # Define the calibration parameters + calib_pars = [ + dict(name='beta', type='range', bounds=[0.01, 1.0], value_type='float', log_scale=True), + dict(name='dur_inf', type='range', bounds=[1, 60], value_type='float', log_scale=False), + #dict(name='init_prev', type='range', bounds=[0.01, 0.30], value_type='float', log_scale=False), + dict(name='n_contacts', type='range', bounds=[2, 10], value_type='int', log_scale=False), + ] + + best_pars, values, exp, model = optimize( + experiment_name = 'starsim', + parameters = calib_pars, + evaluation_function = eval_sim, + objective_name = 'prevalence_error', + minimize = True, + parameter_constraints = None, + outcome_constraints = None, + total_trials = 10, + arms_per_trial = 3, + ) + + return best_pars, values, exp, model + + +#%% Run as a script +if __name__ == '__main__': + + T = sc.timer() + do_plot = True + + best_pars, values, exp, model = test_calibration(do_plot=do_plot) + + print('best_pars:', best_pars) + print('values:', values) + print('exp:', exp) + print('model:', model) + + render(plot_contour(model=model, param_x='beta', param_y='init_prev', metric_name='prevalence')) + + # `plot_single_method` expects a 2-d array of means, because it expects to average means from multiple + # optimization runs, so we wrap out best objectives array in another array. + + for trial in exp.trials.values(): + print(trial) + print(dir(trial)) + print(f"Trial {trial.index} with parameters {trial.arm.parameters} " + f"has objective {trial.objective_mean}.") + + best_objectives = np.array( + [[trial.objective_mean for trial in exp.trials.values()]] + ) + best_objective_plot = optimization_trace_single_method( + y = np.minimum.accumulate(best_objectives, axis=1), + optimum = 0.10, #hartmann6.fmin, + title = "Model performance vs. # of iterations", + ylabel = "Prevalence", + ) + render(best_objective_plot) + + plt.show() + + T.toc() diff --git a/tests/devtests/devtest_axbo_service.py b/tests/devtests/devtest_axbo_service.py new file mode 100644 index 00000000..8d4a7879 --- /dev/null +++ b/tests/devtests/devtest_axbo_service.py @@ -0,0 +1,153 @@ +""" +Test calibration +""" + +#%% Imports and settings +import sciris as sc +import starsim as ss +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +#from ax.plot.contour import plot_contour +#from ax.plot.trace import optimization_trace_single_method +#from ax.service.managed_loop import optimize +#from ax.utils.notebook.plotting import init_notebook_plotting, render + +from ax.service.ax_client import AxClient, ObjectiveProperties +from ax.utils.notebook.plotting import init_notebook_plotting, render + +from ax.modelbridge.cross_validation import cross_validate +from ax.plot.contour import interact_contour +from ax.plot.diagnostic import interact_cross_validation +from ax.plot.scatter import interact_fitted, plot_objective_vs_constraints, tile_fitted +from ax.plot.slice import plot_slice +from ax.service.utils.report_utils import exp_to_df + +do_plot = 1 +do_save = 0 +n_agents = [2e3, 25_000][1] + +ax_client = AxClient(enforce_sequential_optimization=False) + +#%% Helper functions + +def make_sim(calib_pars): + sir = ss.SIR( + beta = ss.beta( calib_pars.get('beta', 0.9) ), + dur_inf = ss.lognorm_ex(mean=ss.dur( calib_pars.get('dur_inf', 6))), + init_prev = ss.bernoulli(0.01), + ) + + #deaths = ss.Deaths(death_rate=15) + #births = ss.Births(birth_rate=15) + + random = ss.RandomNet(n_contacts=ss.poisson(calib_pars.get('n_contacts', 4))) + + sim = ss.Sim( + dt = 1, + unit = 'day', + n_agents = n_agents, + #total_pop = 9980999, + start = sc.date('2024-01-01'), + stop = sc.date('2024-01-31'), + diseases = sir, + networks = random, + #demographics = [deaths, births], + rand_seed = np.random.randint(1e6), + ) + + return sim + + +def eval_sim(pars): + sim = make_sim(pars) + sim.run() + + if False: + fig = sim.plot() + fig.suptitle(pars) + fig.subplots_adjust(top=0.9) + plt.show() + + return dict( + prevalence_error = (np.abs(sim.results.sir.prevalence[-1] - 0.20), None), + #prevalence = (sim.results.sir.prevalence[-1], None), + ) + +#%% Define the tests +def test_calibration(do_plot=False): + sc.heading('Testing calibration') + + # Define the calibration parameters + calib_pars = [ + dict(name='beta', type='range', bounds=[0.005, 0.1], value_type='float', log_scale=True), + #dict(name='dur_inf', type='range', bounds=[1, 120], value_type='float', log_scale=False), + dict(name='dur_inf', type='fixed', value=60, value_type='float'), + #dict(name='init_prev', type='range', bounds=[0.01, 0.30], value_type='float', log_scale=False), + dict(name='n_contacts', type='range', bounds=[1, 10], value_type='int', log_scale=False), + ] + + ax_client.create_experiment( + name = 'starsim test', + parameters = calib_pars, + objectives={'prevalence_error': ObjectiveProperties(minimize=True)}, + parameter_constraints = None, + outcome_constraints = None, + choose_generation_strategy_kwargs={"max_parallelism_override": 25}, + ) + + print('Max parallelism:', ax_client.get_max_parallelism()) # Seems to require manual specification of generation_strategy + + for i in range(5): + print('THINKING...') + trial_index_to_param, idk = ax_client.get_next_trials(max_trials=1_000) + + print('STEP', i, len(trial_index_to_param)) + + # Does NOT work to complete_trial in the parallel loop + results = sc.parallelize(eval_sim, iterkwargs=dict(pars=list(trial_index_to_param.values())), serial=False) + for trial_index, result in zip(trial_index_to_param.keys(), results): + ax_client.complete_trial(trial_index=trial_index, raw_data=result) + + print(exp_to_df(ax_client.experiment)) + + + best_pars, values = ax_client.get_best_parameters() + + return best_pars, values#, exp, model + + +#%% Run as a script +if __name__ == '__main__': + + + T = sc.timer() + do_plot = True + + best_pars, values = test_calibration(do_plot=do_plot) + + sim = make_sim(best_pars) + sim.run() + sim.plot() + + print('best_pars:', best_pars) + print('values:', values) + + #render(ax_client.get_contour_plot(param_x='beta', param_y='dur_inf', metric_name='prevalence_error')) + render(ax_client.get_optimization_trace(objective_optimum=0)) + + model = ax_client.generation_strategy.model + render(interact_contour(model=model, metric_name='prevalence_error')) + + cv_results = cross_validate(model) + render(interact_cross_validation(cv_results)) + + render(plot_slice(model, 'beta', 'prevalence_error')) + render(plot_slice(model, 'n_contacts', 'prevalence_error')) + + render(interact_fitted(model, rel=False)) + + plt.show() + + T.toc() diff --git a/tests/test_calibration.py b/tests/test_calibration.py index 2d89ebba..ea339d94 100644 --- a/tests/test_calibration.py +++ b/tests/test_calibration.py @@ -5,7 +5,9 @@ #%% Imports and settings import sciris as sc import starsim as ss +import pandas as pd +debug = False # If true, will run in serial do_plot = 1 do_save = 0 n_agents = 2e3 @@ -14,116 +16,107 @@ #%% Helper functions def make_sim(): - hiv = ss.HIV( - beta = {'random': [0.01]*2, 'maternal': [1, 0]}, - init_prev = 0.15, + sir = ss.SIR( + beta = ss.beta(0.075), + init_prev = ss.bernoulli(0.02), ) - pregnancy = ss.Pregnancy(fertility_rate=20) - death = ss.Deaths(death_rate=10) - random = ss.RandomNet(n_contacts=4) - maternal = ss.MaternalNet() + random = ss.RandomNet(n_contacts=ss.poisson(4)) sim = ss.Sim( - dt = 1, n_agents = n_agents, - total_pop = 9980999, - start = 1990, + start = sc.date('2020-01-01'), dur = 40, - diseases = [hiv], - networks = [random, maternal], - demographics = [pregnancy, death], + dt = 1, + unit = 'day', + diseases = sir, + networks = random, ) return sim -def make_data(): - """ Define the calibration target data """ - target_data = [ - ['time', 'n_alive', 'hiv.prevalence', 'hiv.n_infected', 'hiv.new_infections', 'hiv.new_deaths'], - [ 1990, 10432409, 0.0699742, 730000 , 210000, 25000], - [ 1991, 10681008, 0.0851979, 910000 , 220000, 33000], - [ 1992, 10900511, 0.1009127, 1100000, 220000, 43000], - [ 1993, 11092775, 0.1081785, 1200000, 210000, 53000], - [ 1994, 11261752, 0.1154349, 1300000, 200000, 63000], - [ 1995, 11410721, 0.1226916, 1400000, 180000, 74000], - [ 1996, 11541215, 0.1299689, 1500000, 160000, 84000], - [ 1997, 11653254, 0.1287194, 1500000, 150000, 94000], - [ 1998, 11747079, 0.1362040, 1600000, 140000, 100000], - [ 1999, 11822722, 0.1353326, 1600000, 130000, 110000], - [ 2000, 11881482, 0.1346633, 1600000, 120000, 120000], - [ 2001, 11923906, 0.1341842, 1600000, 110000, 130000], - [ 2002, 11954293, 0.1254779, 1500000, 100000, 130000], - [ 2003, 11982219, 0.1251854, 1500000, 94000 , 130000], - [ 2004, 12019911, 0.1164734, 1400000, 89000 , 120000], - [ 2005, 12076697, 0.1159257, 1400000, 83000 , 120000], - [ 2006, 12155496, 0.1069475, 1300000, 78000 , 110000], - [ 2007, 12255920, 0.1060711, 1300000, 74000 , 93000], - [ 2008, 12379553, 0.1050118, 1300000, 69000 , 80000], - [ 2009, 12526964, 0.0957933, 1200000, 65000 , 68000], - [ 2010, 12697728, 0.0945050, 1200000, 62000 , 54000], - [ 2011, 12894323, 0.0930642, 1200000, 56000 , 42000], - [ 2012, 13115149, 0.0914972, 1200000, 49000 , 34000], - [ 2013, 13350378, 0.0973755, 1300000, 47000 , 28000], - [ 2014, 13586710, 0.0956817, 1300000, 45000 , 25000], - [ 2015, 13814642, 0.0941030, 1300000, 44000 , 24000], - [ 2016, 14030338, 0.0926563, 1300000, 43000 , 23000], - [ 2017, 14236599, 0.0913139, 1300000, 34000 , 23000], - [ 2018, 14438812, 0.0900351, 1300000, 27000 , 22000], - [ 2019, 14645473, 0.0920401, 1347971, 23000 , None], - [ 2020, 14862927, 0.0874659, 1300000, 20000 , None], - [ 2021, 15085870, 0.0861733, 1300000, 19000 , None], - [ 2022, 15312158, 0.0848998, 1300000, 17000 , None], - ] - df = sc.dataframe(target_data[1:], columns=target_data[0]) - return df +def build_sim(sim, calib_pars, **kwargs): + """ Modify the base simulation by applying calib_pars """ + + sir = sim.pars.diseases # There is only one disease in this simulation and it is a SIR + net = sim.pars.networks # There is only one network in this simulation and it is a RandomNet + + # Capture any parameters that need special handling here + for k, pars in calib_pars.items(): + if k == 'rand_seed': + sim.pars.rand_seed = v + continue + + v = pars['value'] + if k == 'beta': + sir.pars.beta = ss.beta(v) + elif k == 'init_prev': + sir.pars.init_prev = ss.bernoulli(v) + elif k == 'n_contacts': + net.pars.n_contacts = ss.poisson(v) + else: + raise NotImplementedError(f'Parameter {k} not recognized') + + return sim -#%% Define the tests +#%% Define the tests def test_calibration(do_plot=False): sc.heading('Testing calibration') # Define the calibration parameters calib_pars = dict( - init_prev = dict(low=0.01, high=0.30, guess=0.15, path=('diseases', 'hiv', 'init_prev')), - n_contacts = dict(low=2, high=10, guess=4, path=('networks', 'randomnet', 'n_contacts')), + beta = dict(low=0.01, high=0.30, guess=0.15, suggest_type='suggest_float', log=True), # Log scale and no "path", will be handled by build_sim (ablve) + init_prev = dict(low=0.01, high=0.05, guess=0.15, path=('diseases', 'hiv', 'init_prev')), # Default type is suggest_float, no need to re-specify + n_contacts = dict(low=2, high=10, guess=3, suggest_type='suggest_int', path=('networks', 'randomnet', 'n_contacts')), # Suggest int just for demo ) # Make the sim and data sim = make_sim() - data = make_data() - # Define weights for the data - weights = { - 'n_alive': 1.0, - 'hiv.prevalence': 1.0, - 'hiv.n_infected': 1.0, - 'hiv.new_infections': 1.0, - 'hiv.new_deaths': 1.0, - } + infectious = ss.CalibComponent( + name = 'Infectious', + + # "expected" actually from a simulation with pars + # beta=0.075, init_prev=0.02, n_contacts=4 + expected = pd.DataFrame({ + 'n': [200, 197, 195], # Number of individuals sampled + 'x': [30, 30, 10], # Number of individuals found to be infectious + }, index=pd.Index([ss.date(d) for d in ['2020-01-12', '2020-01-25', '2020-02-02']], name='t')), # On these dates + + extract_fn = lambda sim: pd.DataFrame({ + 'n': sim.results.n_alive, + 'x': sim.results.sir.n_infected, + }, index=pd.Index(sim.results.timevec, name='t')), + + conform = 'prevalent', + nll_fn = 'beta', + + weight = 1, + ) # Make the calibration calib = ss.Calibration( calib_pars = calib_pars, sim = sim, - data = data, - weights = weights, - total_trials = 8, - n_workers = 2, + build_fn = build_sim, # Use default builder, Calibration.translate_pars + components = infectious, + total_trials = 20, + n_workers = None, # None indicates to use all available CPUs die = True, - debug = False, + debug = debug, ) # Perform the calibration sc.printcyan('\nPeforming calibration...') - calib.calibrate(confirm_fit=False) - - # Confirm - sc.printcyan('\nConfirming fit...') - calib.confirm_fit() - print(f'Fit with original pars: {calib.before_fit:n}') - print(f'Fit with best-fit pars: {calib.after_fit:n}') - if calib.after_fit <= calib.before_fit: + calib.calibrate() + + # Check + sc.printcyan('\nChecking fit...') + calib.check_fit() + print(f'Fit with original pars: {calib.before_fits}') + print(f'Fit with best-fit pars: {calib.after_fits}') + if calib.after_fits.mean() <= calib.before_fits.mean(): print('✓ Calibration improved fit') else: print('✗ Calibration did not improve fit, but this sometimes happens stochastically and is not necessarily an error') @@ -138,9 +131,24 @@ def test_calibration(do_plot=False): #%% Run as a script if __name__ == '__main__': + # Useful for generating fake "expected" data + if False: + sim = make_sim() + pars = { + 'beta' : dict(value=0.075), + 'init_prev' : dict(value=0.02), + 'n_contacts': dict(value=4), + } + sim = build_sim(sim, pars) + ms = ss.MultiSim(sim, n_runs=25) + ms.run().plot() + T = sc.timer() do_plot = True sim, calib = test_calibration(do_plot=do_plot) T.toc() + + import matplotlib.pyplot as plt + plt.show() \ No newline at end of file diff --git a/tests/test_time.py b/tests/test_time.py index 18fd6d3c..7f6bb7ab 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -145,7 +145,7 @@ def test_multi_timestep(do_plot=False): def test_mixed_timesteps(): - sc.heading('Test behavior of different commbinations of timesteps') + sc.heading('Test behavior of different combinations of timesteps') siskw = dict(dur_inf=ss.dur(50, 'day'), beta=ss.beta(0.01, 'day'), waning=ss.rate(0.005, 'day')) kw = dict(n_agents=1000, start='2001-01-01', stop='2001-07-01', networks='random', copy_inputs=False, verbose=0) @@ -168,7 +168,7 @@ def test_mixed_timesteps(): msim = ss.parallel(sim1, sim2, sim3, sim4) - # Check that al results are close + # Check that all results are close threshold = 0.01 summary = msim.summarize() for key,res in summary.items():