Sampling form non-uniform distributions

If we want to represent regions of our parameter space to different degrees, we can use non-uniform distributions for sampling. In such cases, we can build our sampler by inheriting from SamplerBase and implementing our custom sampling logic there.

Note: this can also introduce bias to the sensitivity of the parameters, thus it should be handled with care.

Example of a custom sampling scenario

This example demonstrates how to sample three parameters: alpha, beta, and gamma. The alpha and beta parameters are uniformly distributed, while gamma follows a standard normal distribution, which is scaled to fit between 0 and 5.

Asssumptions

  1. The sampled parameters are provided in sampling_config if the model evaluation can be handled by EMSA (i.e. we sample “basic” parameters),

or

  1. The SamplerBase.lhs_bounds member variable contains the sampling boundaries in the correct format. (for an example of the 2. option, see ContactSampler or VaccinatedSampler in emsa_examples)

We also assume that gamma was sampled in between 0 and 1. Then the following steps will provide us the desired result:

Steps

  1. Use SamplerBase.get_lhs_table() to generate a Latin Hypercube Sampling (LHS) table with uniformly distributed values for all parameters.

  2. Create a function that converts uniformly distributed values on [0, 1] into normally distributed values on the interval [0, 5].

  3. Apply this function to the gamma values in the LHS table.

Implementation

from scipy.stats import norm
from emsa.sensitivity import SamplerBase
import numpy as np


def uniform_to_normal_on_interval(data, std=1):
    # Convert uniform [0, 1] to standard normal using the inverse CDF (Probit function)
    normal_values = norm.ppf(data)

    # Apply the logistic transformation to map to (0, 5)
    transformed_values = 5 / (1 + np.exp(-std * normal_values))

    return transformed_values


class CustomSampler(SamplerBase):
    def __init__(self, sim_object, variable_params=None):
        super().__init__(sim_object, variable_params)

    def run(self):
        lhs_table = self.get_lhs_table()
        # Note: if gamma was age dependent, we can use a slice object to perform the following
        gamma_idx = 2
        # Apply the sampling transformation
        transformed_gamma = uniform_to_normal_on_interval(lhs_table[:, gamma_idx])
        # Overwrite old values with the transformed ones
        lhs_table[:, gamma_idx] = transformed_gamma
        # Calculate targets, etc.
        self.get_sim_output(lhs_table)