Skip to content

Getting started with pyrenew

pyrenew is a flexible tool for simulating and making statistical inferences from epidemiologic models, with an emphasis on renewal models. Built on numpyro, pyrenew provides core components for model building and pre-defined models for processing various observational processes.

Prerequisites

The following tutorials assume that you are familiar with both infectious disease dynamics and Python programming.

  • You must have a Python3 installation (use tools like pyenv or compile and install from the release page).
  • You should be able to install packages, load modules in python, and manage virtual environments (we recommend uv).
  • You should understand the concept of a class and metaclass in python.
  • You should be familiar with Bayesian inference and have a working understanding of MCMC methods used to fit Bayesian models to data (some resources are available here, and here).

Installing pyrenew

You can install pyrenew using either uv or pip. To install pyrenew using uv, run the following command from within the directory containing the pyrenew project:

uv sync

To install pyrenew using pip, run the following command:

pip install git+https://github.com/CDCgov/PyRenew@main

The fundamentals

The pyrenew package leverages numpyro’s flexibility to build models via composition. It’s core components are the metaclasses RandomVariable and Model (in Python, a metaclass is a class whose instances are also classes, where a class is a template for making objects). Within the pyrenew package, a RandomVariable is a quantity that models can estimate and sample from, including deterministic quantities. The benefit of this design is that the definition of the sample() function can be arbitrary, allowing the user to either sample from a distribution using numpyro.sample(), compute fixed quantities (like a mechanistic equation), or return a fixed value (like a pre-computed PMF.) For instance, when estimating a PMF, the RandomVariable sampling function may roughly be defined as:

# define a new class called MyRandVar that inherits from the RandomVariable class
class MyRandVar(RandomVariable):
    #define a method called sample that returns an object of type ArrayLike
    def sample(...) -> ArrayLike:
        # calls sample function from NumPyro package
        return numpyro.sample(...)

We can instead use a fixed quantity for that variable (like a pre-computed PMF), where the RandomVariable’s sample function could be defined as:

# instead define MyRandVar to still inherit from the RandVariable class
class MyRandVar(RandomVariable):
    #define sample method that still returns an ArrayLike object
    def sample(...) -> ArrayLike:
        #sampling method is a pre-computed PMF, a JAX NumPy array with explicit elements
        return jax.numpy.array([0.2, 0.7, 0.1])

Thus, when a Model samples from MyRandVar, it could be either adding random variables to be estimated (first example) or just retrieving some quantity needed for other calculations (second example).

The Model metaclass provides basic functionality for estimating and simulation. Like RandomVariable, the Model metaclass has a sample() method that defines the model structure. Ultimately, models can be nested (or inherited), providing a straightforward way to add layers of complexity. Currently the Model metaclass contains of two model classes

  • RtInfectionsRenewalModel - a basic renewal model consisting of infections and reproduction numbers.
  • HospitalAdmissionsModel- a model which extends a basic renewal model to account for latent and observed hospital admissions.