Scenarios#

You should now be able to run a single sim, using default or custom parameters and plot the results.

In this tutorial, you will learn how to create your own intervention scenarios. The code used to run scenarios is housed in the scenarios.py script, which you can reference as we go. To write custom scenarios, we define a new function - ‘Scenario’.

Intervention scenarios are flexible, and can be complicated. We’ll give you a taste here, but you should take a look at the full documentation here: https://docs.idmod.org/projects/fpsim/en/latest/fpsim.scenarios.html#fpsim.scenarios.Scenario

An interactive version of this notebook is available on Google Colab or Binder.

Let’s start with a basic scenario in which we increase the method efficacy of the existing injectables to 99%. We’ll label the scenario s1, to keep it short & sweet.

First, we want injectables efficacy to increase, and second, we want that change to start in 2020. We can imagine using this kind of scenario when a user wants to improve an existing method, but does not think this will impact behavior.

[2]:
import fpsim as fp
s1 = fp.make_scen(eff={'Injectables':0.99}, year=2020)

If we want to instead change behavior around a method, we can adjust the initiation, switching, and/or discontinuation. In s2, we’ll double the initiation rate of injectables.

[3]:
s2 = fp.make_scen(method='Injectables', init_factor=2, year=2020)

Users may also want to specify a specific value, instead of using a factor. In this case, we would input init_value = [your value] instead of a factor. Important not to confuse value and factor, since value represents the annual probability, between 0 and 1. An init_value of 0.5 would be a massive 50% initation probability, which is not feasible to see in real life, but an init_factor would cut initiation in half.

[5]:
s2_value = fp.make_scen(method='Injectables', init_value=0.07, year=2020) # in this case, we would need robust user insights data to tell us that we should anticipate ~7% uptake

Scenarios can be easily combined, so that you can layer multiple interventions in one scenario. Here, we can boost the efficacy of injectables and double the uptake.

[6]:
s3 = s1 + s2

Note that by default, the intervention conditions are applied to all annual matrices, unless we specify otherwise.

However, let’s say we think that the newly improved injectables will especially appeal to the 35+ audience when it hits the market in 2027. We would specify the age matrix using ‘ages’.

[7]:
s4 = fp.make_scen(method='Injectables', init_factor=2.0, ages='>35', year=2027)

We can also combine different changes in a single scenario by using a dictionary. In this case, let’s imagine that our newly improved 2027 injectables will appeal to the 35+ audience and will draw not only new users, but current condom users. In this case, we need two lines of changes, one line to affect injectables, and one to affect condom switching behavior.

[15]:
s5 = fp.make_scen(
    year = 2027,
    probs = [
        dict(method='Injectables', init_factor=2.0, ages='>35'),
        dict(source='Condoms', dest='Injectables', value=0.20) #assume 20% likelihood of condom users to switch to injectables
    ]
)

If you want to write up several scenarios that share multiple arguments, it’s easy to use kwargs to make life easier (and more legible). In this example, we’ll take a look at some counterfactual scenarios in which we see big increases in injectable uptake for the under 20 population. We set the pars first as global - so they’ll be part of all scenarios. We then set kwargs as a dictionary that specific scenarios will pull.

[32]:
    n_agents   = 10_000
    start_year = 1980
    repeats    = 3
    year       = 2012
    youth_ages = ['<18', '18-20']

    pars = fp.pars(location='senegal', n_agents=n_agents, start_year=start_year, end_year=2020) #set pars to include all shared arguments

    method = 'Injectables'
    kw = dict(method=method, ages=youth_ages) #set kwargs to take on the method and the age group
    d_kw = dict(dest=method, ages=youth_ages) #switching scenarios takes a slightly different set of kwargs, which we define here
    f1 = fp.make_scen(
            label = '2x uptake',
            year  = year,
            probs = [
                dict(init_factor = 2.0, **kw),
                    ]
                )
    f2 = fp.make_scen(
            label = '5x uptake',
            year  = year,
            probs = [
                dict(init_factor = 5.0, **kw),
                    ]
                )

    f3 = fp.make_scen(
            label = '10x uptake',
            year  = year,
            probs = [
                dict(init_factor = 10.0, **kw),
                    ]
                )
    f_switch = fp.make_scen(
            label = '20 percent switching',
            year  = year,
            probs = [
                dict(source = 'Injectables', value = 0.20, **d_kw) #note the use of **d_kw here
                    ]
               )

    f4 = f2 + f_switch # We will come back to this later.

Finally, let’s add in a scenario where we invent a new method. For this one, we’ll say we’re introducing a new injectable that has a different iTPP than the existing injectable, and we anticipate that both injectables will co-exist on the same market. We’ll have to add in a new method to our fp.pars(). We’ll also have to re-do our kwargs here, to call a new method, which we have labeled ‘new injectables’. In the probs line of the scenario, we will call a new dict to copy the row and column from a switching matrix and paste it as our 11th method. Everything we call after that will replace the copied data. In this case, we’re copying over from injectables, since the new method should be fairly similar.

[29]:
pars.add_method(name='new injectables', eff=0.983)

method = 'new injectables'
kw = dict(method=method, ages=youth_ages)
f_new = fp.make_scen(
        label = 'introduce new method',
        year  = 2015, #replacing the intervention year, giving the new method a bit of a lag
        probs = [
            dict(copy_from='Injectables', **kw),
            dict(init_value=0.05, **kw),
                ]
            )

So, now that we’ve defined the scenarios we want to explore, we need to run them together so that we can compare them. Using the ‘f’ scenarios from above, we first call on Scenarios to run the sims with the pars and repeats we defined above. We’re then going to add in the new scens (including the baseline) one by one. Note: You can label scenarios here and they’ll override what you used as a label when defining the scenarios. This can be useful if you’re combining two different scenarios and you want to re-label the combination.

[30]:

scens = fp.Scenarios(pars=pars, repeats=repeats) scens.add_scen(label='Baseline') scens.add_scen(f1) scens.add_scen(f2) scens.add_scen(f3) scens.add_scen(f4, label='5x uptake plus switching') #combining f2 and f_switch and re-labeling here scens.add_scen(f_new)
[31]:
#Note: Running the scenarios can take quite a while, depending on how large your starting population is (n_agents) and how many times you'd like to repeat the sim with a new seed (repeats).
#On a local machine, with five scenarios plus a baseline, n_agents = 10,000 and repeats = 3, this run took the author 3m 2.6s. YMMV!
scens.run()

Congratulations on making it this far! In the next tutorial, we’ll learn how to plot the scenarios.