Skip to content

Commit 61d0bba

Browse files
tbhalletttdm32
andauthored
Simplified births and deaths equality (#1628)
* simplified births elaborated to have a a mode whereby births equal deaths * refactoring and editing * typo fix! * isort * linting * update argument in _choose_women_to_make_pregnant * move call to sim object into apply() method * add simple test * move record of data back to __init__ * strengthen logic for chekcing death dates --------- Co-authored-by: tdm32 <[email protected]> Co-authored-by: Tara <[email protected]>
1 parent dd0d816 commit 61d0bba

File tree

2 files changed

+75
-25
lines changed

2 files changed

+75
-25
lines changed

src/tlo/methods/simplified_births.py

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import json
77
from pathlib import Path
8-
from typing import Optional
8+
from typing import List, Optional
99

1010
import pandas as pd
1111

@@ -85,8 +85,10 @@ class SimplifiedBirths(Module):
8585
categories=['none', 'non_exclusive', 'exclusive']),
8686
}
8787

88-
def __init__(self, name=None):
88+
def __init__(self, name=None, force_one_birth_for_one_death: bool = False):
8989
super().__init__(name)
90+
# Whether to use the _really_ simplified mode, whereby number of births is just equal to number of deaths
91+
self.force_one_birth_for_one_death = force_one_birth_for_one_death
9092
self.asfr = dict()
9193

9294
# Define defaults for properties:
@@ -162,8 +164,10 @@ def __init__(self, module):
162164
super().__init__(module, frequency=DateOffset(months=self.months_between_polls))
163165
self.asfr = get_medium_variant_asfr_from_wpp_resourcefile(
164166
dat=self.module.parameters['age_specific_fertility_rates'], months_exposure=self.months_between_polls)
167+
self.date_of_last_poll = self.sim.date
165168

166169
def apply(self, population):
170+
167171
# Set new pregnancies:
168172
self.set_new_pregnancies()
169173

@@ -174,26 +178,54 @@ def apply(self, population):
174178
self.update_breastfed_status()
175179

176180
def set_new_pregnancies(self):
177-
"""Making women pregnant. Rate of doing so is based on age-specific fertility rates under assumption that every
178-
pregnancy results in a birth."""
181+
"""Making women pregnant."""
179182

180-
df = self.sim.population.props # get the population dataframe
183+
def _make_pregnant(ids: List[int], df: pd.DataFrame, months_between_pregnancy_and_delivery: int) -> None:
184+
"""Enact the change to make the women pregnant"""
185+
# updating properties for women who will get pregnant
186+
df.loc[ids, 'is_pregnant'] = True
187+
df.loc[ids, 'date_of_last_pregnancy'] = self.sim.date
188+
df.loc[ids, 'si_date_of_last_delivery'] = \
189+
self.sim.date + pd.DateOffset(months=months_between_pregnancy_and_delivery)
190+
191+
def _choose_women_to_make_pregnant(one_birth_for_one_death: bool, df: pd.DataFrame) -> List[int]:
192+
"""Choose women to make pregnant, depending on mode."""
193+
194+
if one_birth_for_one_death:
195+
# Rate of pregnancy matches number of deaths: simple assumption and induces stable population size
196+
# Non-pregnant women aged [15, 35) are selected randomly for pregnancy:
197+
198+
eligible_for_pregnancy = df.loc[
199+
(df.sex == 'F') & df.is_alive & ~df.is_pregnant & df.age_years.between(15, 35, inclusive='left')
200+
]
201+
202+
num_of_deaths_since_last_poll = len(
203+
df.loc[df.date_of_death.between(self.date_of_last_poll, self.sim.date, inclusive='left')])
181204

182-
# find probability of becoming pregnant (using asfr for the year, limiting to alive, non-pregnant females)
183-
prob_preg = df.loc[
184-
(df.sex == 'F') & df.is_alive & ~df.is_pregnant
185-
]['age_range'].map(self.asfr[self.sim.date.year]).fillna(0)
186-
187-
# determine which woman will get pregnant
188-
pregnant_women_ids = prob_preg.index[
189-
(self.module.rng.random_sample(size=len(prob_preg)) < prob_preg)
190-
]
191-
192-
# updating properties for women who will get pregnant
193-
df.loc[pregnant_women_ids, 'is_pregnant'] = True
194-
df.loc[pregnant_women_ids, 'date_of_last_pregnancy'] = self.sim.date
195-
df.loc[pregnant_women_ids, 'si_date_of_last_delivery'] = \
196-
self.sim.date + pd.DateOffset(months=self.module.parameters['months_between_pregnancy_and_delivery'])
205+
return self.module.rng.choice(
206+
eligible_for_pregnancy.index, size=num_of_deaths_since_last_poll, replace=False, p=None)
207+
208+
else:
209+
# Rate of pregnancy is based on age-specific fertility rates under
210+
# assumption that every pregnancy results in a birth.
211+
212+
# find probability of becoming pregnant
213+
# (using asfr for the year, limiting to alive, non-pregnant females)
214+
prob_preg = df.loc[
215+
(df.sex == 'F') & df.is_alive & ~df.is_pregnant
216+
]['age_range'].map(self.asfr[self.sim.date.year]).fillna(0)
217+
218+
# determine which woman will get pregnant:
219+
return prob_preg.index[
220+
(self.module.rng.random_sample(size=len(prob_preg)) < prob_preg)
221+
]
222+
223+
df = self.sim.population.props # get the population dataframe
224+
women_to_make_pregnant = _choose_women_to_make_pregnant(self.module.force_one_birth_for_one_death, df)
225+
_make_pregnant(ids=women_to_make_pregnant,
226+
df=df,
227+
months_between_pregnancy_and_delivery=self.module.parameters['months_between_pregnancy_and_delivery'])
228+
self.date_of_last_poll = self.sim.date
197229

198230
def do_deliveries(self):
199231
"""Checks to see if the date-of-delivery for pregnant women has been reached and implement births where

tests/test_simplified_births.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ def check_dtypes(simulation):
2020
assert (df.dtypes == orig.dtypes).all()
2121

2222

23-
def get_sim(seed, popsize=1000):
23+
def get_sim(seed, popsize=1000, **kwargs):
2424
sim = Simulation(start_date=Date(2010, 1, 1), seed=seed, resourcefilepath=resourcefilepath)
2525

2626
# Register the appropriate modules
27-
sim.register(demography.Demography(),
28-
simplified_births.SimplifiedBirths()
29-
)
27+
sim.register(
28+
demography.Demography(),
29+
simplified_births.SimplifiedBirths(**kwargs)
30+
)
3031

3132
# Make the population
3233
sim.make_initial_population(n=popsize)
@@ -235,10 +236,11 @@ def apply(self, population):
235236

236237

237238
@pytest.mark.slow
238-
def test_other_modules_running_with_simplified_births_module():
239+
def test_other_modules_running_with_simplified_births_module(seed):
239240
"""Run a "full simulation" using the simplified_births module and other disease modules"""
240241
sim = Simulation(
241242
start_date=Date(2010, 1, 1),
243+
seed=seed,
242244
log_config={
243245
'custom_levels': {
244246
'*': logging.WARNING,
@@ -254,3 +256,19 @@ def test_other_modules_running_with_simplified_births_module():
254256
sim.simulate(end_date=Date(2011, 12, 31))
255257
check_property_integrity(sim)
256258
check_dtypes(sim)
259+
260+
def test_using_option_force_one_birth_for_one_death(seed):
261+
"""Test that when we have the option `force_one_birth_for_one_death` we see the behaviour expected."""
262+
263+
sim = get_sim(seed=seed, popsize=1000, force_one_birth_for_one_death=True)
264+
265+
# Make births happen same month as pregnancy, so that at the end of each month, pregnancies == births
266+
sim.modules['SimplifiedBirths'].parameters['months_between_pregnancy_and_delivery'] = 0
267+
268+
# Check that in a simulation, the number of births that occurs does match the number of deaths
269+
sim.simulate(end_date=Date(2015, 1, 1))
270+
df = sim.population.props
271+
num_births = len(df.loc[df.date_of_birth.notnull() & (df.mother_id >= 0)])
272+
num_deaths = len(df.loc[df.date_of_death.notnull()])
273+
274+
assert num_births == num_deaths

0 commit comments

Comments
 (0)