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

Adding a new method#

This tutorial shows how to use the ``add_method`` intervention to introduce a new contraceptive method into an FPsim run at a specified year. You can use it in several ways:

  • Brand new method: Define a full fp.Method and add it, copying switching behavior from an existing method.

  • Quick clone: Clone an existing method (e.g. implants) with minimal config; useful for generic “copy” scenarios.

  • Clone + override: Clone a method and override specific attributes (name, efficacy, duration, etc.) via method_pars.

  • Market splitting: Introduce a new method that takes a fraction of another method’s market share using split_shares.

You always specify when the method becomes available (year) and which existing method to copy switching behavior from (copy_from). Optionally you can pass a custom method, method_pars, and/or split_shares.

Setup#

Import FPsim, Starsim, and set common simulation parameters. We’ll use a small population and short run for quick demos.

[1]:
import fpsim as fp
import starsim as ss
import numpy as np
import matplotlib.pyplot as plt

pars = dict(
    n_agents=2000,
    start=2000,
    stop=2020,
    location='kenya',
    verbose=0,
    analyzers=fp.method_mix_over_time(),
)
intro_year = 2010  # Year when new methods will be introduced
/home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

1. Basic introduction (new method + market share)#

The simplest use case: define a new method with fp.Method, introduce it at a given year, copy switching behavior from an existing method, and optionally take part of that method’s market share with split_shares.

[2]:
# Define the new method
new_injectable = fp.Method(
    name='new_inj',
    label='New Injectable',
    efficacy=0.995,
    modern=True,
    dur_use=ss.lognorm_ex(mean=3, std=1.5),
)

# Intervention: introduce in intro_year, copy from injectables, take 50% of their share
intv_basic = fp.add_method(
    year=intro_year,
    method=new_injectable,
    copy_from='inj',
    split_shares=0.50,
    verbose=True,
)

sim_baseline = fp.Sim(pars=pars, label='Baseline').run()
sim_basic = fp.Sim(pars=pars, interventions=[intv_basic], label='With New Injectable').run()

print(f"Baseline mCPR: {sim_baseline.results.contraception.mcpr[-1]*100:.1f}%")
print(f"With new method mCPR: {sim_basic.results.contraception.mcpr[-1]*100:.1f}%")
Loading data from files in /home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/fpsim/locations/kenya/data...
Applying calibration parameters for kenya...
Loading data from files in /home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/fpsim/locations/kenya/data...
Applying calibration parameters for kenya...
Registered new method "new_inj" (idx=10), will activate in year 2010
Activating new contraceptive method "new_inj" in year 2010.0
add_method finalized: "new_inj" has 44 users (5.10% of method mix)
Baseline mCPR: 58.5%
With new method mCPR: 59.7%

2. Quick clone (copy existing method)#

Add a copy of an existing method without defining a full Method: omit method and use copy_from. The new method is named {source}_copy unless you override it in method_pars.

[3]:
intv_clone = fp.add_method(
    year=intro_year,
    copy_from='impl',
    verbose=True,
)

sim_clone = fp.Sim(pars=pars, interventions=[intv_clone], label='With Implant Clone').run()
Loading data from files in /home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/fpsim/locations/kenya/data...
Applying calibration parameters for kenya...
Registered new method "impl_copy" (idx=10), will activate in year 2010
Activating new contraceptive method "impl_copy" in year 2010.0
add_method finalized: "impl_copy" has 119 users (14.90% of method mix)

3. Clone + override with improved contraceptive technology#

Clone an existing method and override specific attributes via method_pars. In this next example we introduce an improved injectable with higher efficacy and a 10% longer relative duration of use.

[4]:
intv_override = fp.add_method(
    year=intro_year,
    copy_from='inj',
    method_pars={
        'name': 'inj_improved',
        'label': 'Improved Injectable',
        'efficacy': 0.998,
        'rel_dur_use': 1.1
    },
)

sim_override = fp.Sim(pars=pars, interventions=[intv_override], label='With Improved Injectable').run()
Loading data from files in /home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/fpsim/locations/kenya/data...
Applying calibration parameters for kenya...
Registered new method "inj_improved" (idx=10), will activate in year 2010
Activating new contraceptive method "inj_improved" in year 2010.0
add_method finalized: "inj_improved" has 87 users (9.80% of method mix)

4. Market splitting (split_shares)#

Model a new product that takes a fixed fraction of an existing method’s users (e.g. self-injectable DMPA-SC taking share from clinic injectables). Use split_shares between 0 and 1; the new method gets that fraction of users who would have chosen copy_from.

[5]:
dmpasc_pars = dict(
    name='dmpasc',
    label='DMPA-SC (Self-Injectable)',
    efficacy=0.997,
    dur_use=ss.lognorm_ex(mean=0.25, std=0.1),
)

intv_split = fp.add_method(
    year=intro_year,
    method_pars=dmpasc_pars,
    copy_from='inj',
    split_shares=0.40,
    verbose=True,
)

sim_split = fp.Sim(pars=pars, interventions=[intv_split], label='With DMPA-SC').run()

# Method-specific usage at end
cm = sim_split.connectors.contraception
fp_mod = sim_split.connectors.fp
inj_usage = fp_mod.method_mix[cm.methods['inj'].idx, -1] * 100
dmpasc_usage = fp_mod.method_mix[cm.methods['dmpasc'].idx, -1] * 100
print(f"Injectable: {inj_usage:.1f}%  |  DMPA-SC: {dmpasc_usage:.1f}%")
Loading data from files in /home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/fpsim/locations/kenya/data...
Applying calibration parameters for kenya...
Registered new method "dmpasc" (idx=10), will activate in year 2010
Activating new contraceptive method "dmpasc" in year 2010.0
add_method finalized: "dmpasc" has 49 users (5.75% of method mix)
Injectable: 6.8%  |  DMPA-SC: 5.8%

We can visualize how the market share evolved over time using the method_mix_over_time analyzer. The plot below shows the share of injectables and DMPA-SC over time, highlighting how DMPA-SC captured a portion of the injectable market when it was introduced in 2010.

[6]:
# Plot the method mix over time, focusing on injectables and DMPA-SC
# The plot shows share (%) and only the two methods involved in the market split
fig = sim_split.analyzers[0].plot(methods=['inj', 'dmpasc'], share=True)
plt.show()
../_images/tutorials_T5_new_method_13_0.png

Alternative: Adding methods from simulation start#

The examples above use the add_method intervention to introduce methods partway through a simulation. If you need a method to be available from the very beginning of the simulation, use the manual method creation approach below.

First, make a copy of the default list of contraceptive methods:

[7]:
import sciris as sc

my_methods = fp.make_methods()

Define a new method with its attributes:

  1. A simple name (used inside the code)

  2. Efficacy (as a decimal)

  3. Whether it is a modern method

  4. Duration of use (in months or as a distribution)

  5. A label (used in plots)

[8]:
new_method = fp.Method(name='new', efficacy=0.96, modern=True, dur_use=15, label='New method')

Add the method to the list:

[9]:
my_methods += new_method

Create a method choice module that includes your new method:

[10]:
method_choice = fp.RandomChoice(methods=my_methods)

Now run simulations comparing baseline (without the new method) to a scenario with the new method:

[11]:
pars = dict(
    n_agents   = 10_000,
    location   = 'senegal',
    start      = 2000,
    stop       = 2012,
    exposure_factor = 1.0  # Overall scale factor on probability of becoming pregnant
)

s1 = fp.Sim(pars=pars, label='Baseline')
s2 = fp.Sim(pars=pars, contraception_module=method_choice, label='New Method')
simlist = sc.autolist([s1, s2])
msim = ss.MultiSim(sims=simlist)
msim.run(parallel=False)
Loading data from files in /home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/fpsim/locations/senegal/data...
Applying calibration parameters for senegal...
Loading data from files in /home/docs/checkouts/readthedocs.org/user_builds/institute-for-disease-modeling-fpsim/envs/latest/lib/python3.11/site-packages/fpsim/locations/senegal/data...
Applying calibration parameters for senegal...
Initializing sim "Baseline" with 10000 agents
  Running "Baseline": 2000.01.01 ( 0/145) (0.00 s)  ———————————————————— 1%
  Running "Baseline": 2001.01.01 (12/145) (0.24 s)  •——————————————————— 9%
  Running "Baseline": 2002.01.01 (24/145) (0.54 s)  •••————————————————— 17%
  Running "Baseline": 2003.01.01 (36/145) (0.85 s)  •••••——————————————— 26%
  Running "Baseline": 2004.01.01 (48/145) (1.17 s)  ••••••—————————————— 34%
  Running "Baseline": 2005.01.01 (60/145) (1.50 s)  ••••••••———————————— 42%
  Running "Baseline": 2006.01.01 (72/145) (2.01 s)  ••••••••••—————————— 50%
  Running "Baseline": 2007.01.01 (84/145) (2.34 s)  •••••••••••————————— 59%
  Running "Baseline": 2008.01.01 (96/145) (2.68 s)  •••••••••••••——————— 67%
  Running "Baseline": 2009.01.01 (108/145) (3.03 s)  •••••••••••••••————— 75%
  Running "Baseline": 2010.01.01 (120/145) (3.37 s)  ••••••••••••••••———— 83%
  Running "Baseline": 2011.01.01 (132/145) (3.73 s)  ••••••••••••••••••—— 92%
  Running "Baseline": 2012.01.01 (144/145) (4.09 s)  •••••••••••••••••••• 100%

Initializing sim "New Method" with 10000 agents
  Running "New Method": 2000.01.01 ( 0/145) (0.00 s)  ———————————————————— 1%
  Running "New Method": 2001.01.01 (12/145) (0.14 s)  •——————————————————— 9%
  Running "New Method": 2002.01.01 (24/145) (0.34 s)  •••————————————————— 17%
  Running "New Method": 2003.01.01 (36/145) (0.53 s)  •••••——————————————— 26%
  Running "New Method": 2004.01.01 (48/145) (0.73 s)  ••••••—————————————— 34%
  Running "New Method": 2005.01.01 (60/145) (0.93 s)  ••••••••———————————— 42%
  Running "New Method": 2006.01.01 (72/145) (1.13 s)  ••••••••••—————————— 50%
  Running "New Method": 2007.01.01 (84/145) (1.33 s)  •••••••••••————————— 59%
  Running "New Method": 2008.01.01 (96/145) (1.54 s)  •••••••••••••——————— 67%
  Running "New Method": 2009.01.01 (108/145) (1.75 s)  •••••••••••••••————— 75%
  Running "New Method": 2010.01.01 (120/145) (1.96 s)  ••••••••••••••••———— 83%
  Running "New Method": 2011.01.01 (132/145) (2.17 s)  ••••••••••••••••••—— 92%
  Running "New Method": 2012.01.01 (144/145) (2.38 s)  •••••••••••••••••••• 100%

[11]:
MultiSim("Baseline"; n_sims: 2; base: Sim(Baseline; n=10000; 2000—2012; demographics=deaths; connectors=contraception, edu, fp))

Compare the contraceptive prevalence rate between the two scenarios:

[12]:
msim.plot(key='cpr');
Figure(1600x1000)
../_images/tutorials_T5_new_method_26_1.png
../_images/tutorials_T5_new_method_26_2.png