Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions resources/ResourceFile_Stunting/Cover Sheet.csv

This file was deleted.

3 changes: 0 additions & 3 deletions resources/ResourceFile_Stunting/Parameter_values.csv

This file was deleted.

3 changes: 3 additions & 0 deletions resources/ResourceFile_Stunting/parameter_values.csv
Git LFS file not shown
57 changes: 40 additions & 17 deletions src/tlo/methods/stunting.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,25 @@ class Stunting(Module, GenericFirstAppointmentsMixin):
'prob_stunting_diagnosed_at_generic_appt': Parameter(
Types.REAL,
'Probability of a stunted or severely stunted person being checked and correctly diagnosed'),

# Age and classification thresholds
'max_age_for_stunting_years': Parameter(
Types.REAL,
'Maximum age (in years) for which stunting assessment and interventions apply'),
'haz_threshold_stunted': Parameter(
Types.REAL,
'HAZ score threshold below which a child is considered stunted'),
'haz_threshold_severe_stunted': Parameter(
Types.REAL,
'HAZ score threshold below which a child is considered severely stunted'),

# Event timing
'main_polling_frequency_months': Parameter(
Types.INT,
'Frequency in months for the main stunting polling event'),
'followup_appointment_months': Parameter(
Types.INT,
'Interval in months for scheduling follow-up appointments after stunting treatment'),
}

PROPERTIES = {
Expand All @@ -149,7 +168,7 @@ def __init__(self, name=None):

def read_parameters(self, resourcefilepath: Optional[Path]=None):
self.load_parameters_from_dataframe(
read_csv_files(resourcefilepath / 'ResourceFile_Stunting', files='Parameter_values')
read_csv_files(resourcefilepath / 'ResourceFile_Stunting', files='parameter_values')
)

def initialise_population(self, population):
Expand All @@ -167,11 +186,13 @@ def get_probs_stunting(_agegp):
mean, stdev = p[f'prev_HAZ_distribution_age_{_agegp[0]}_{_agegp[1]}mo']
haz_distribution = norm(loc=mean, scale=stdev)

# Compute proportion "stunted" (HAZ <-2)
p_stunted = haz_distribution.cdf(-2.0)
# Compute proportion "stunted" (HAZ < threshold)
p_stunted = haz_distribution.cdf(p['haz_threshold_stunted'])

# Compute proportion "severely stunted" given "stunted" (HAZ <-3 given HAZ <-2)
p_severely_stunted_given_stunted = haz_distribution.cdf(-3.0) / haz_distribution.cdf(-2.0)
# Compute proportion "severely stunted" given "stunted"
# (HAZ < severe threshold given HAZ < stunted threshold)
p_severely_stunted_given_stunted = (haz_distribution.cdf(p['haz_threshold_severe_stunted']) /
haz_distribution.cdf(p['haz_threshold_stunted']))

# Return results needed as named tuple:
result = namedtuple('probs', ['prob_stunting', 'prob_severe_given_stunting'])
Expand Down Expand Up @@ -304,7 +325,7 @@ def do_at_generic_first_appt(

# Schedule the HSI for provision of treatment based on the probability of
# stunting diagnosis, provided the necessary symptoms are there.
if individual_properties["age_years"] <= 5 and is_stunted:
if individual_properties["age_years"] <= self.parameters["max_age_for_stunting_years"] and is_stunted:
# Schedule the HSI for provision of treatment based on the probability of
# stunting diagnosis
if p_stunting_diagnosed > self.rng.random_sample():
Expand Down Expand Up @@ -346,7 +367,7 @@ def make_lm_prob_becomes_stunted(self):
).when(
"4 <= age_exact_years < 5", p["base_inc_rate_stunting_by_agegp"][5]
).when(
"age_exact_years >= 5", 0.0
f"age_exact_years >= {p['max_age_for_stunting_years']}", 0.0
),
Predictor('li_wealth',
conditions_are_mutually_exclusive=True).when(1, 1.0)
Expand Down Expand Up @@ -407,7 +428,7 @@ def make_lm_prob_progression_to_severe_stunting(self):
p['r_progression_severe_stunting_by_agegp'][5]
)
.when(
'age_exact_years >= 5.0', 1.0
f'age_exact_years >= {p["max_age_for_stunting_years"]}', 1.0
),
Predictor('un_ever_wasted',
conditions_are_exhaustive=True,
Expand Down Expand Up @@ -440,7 +461,7 @@ class StuntingPollingEvent(RegularEvent, PopulationScopeEventMixin):
"""Regular event that controls the onset of stunting, progression to severe stunting and natural recovery."""

def __init__(self, module):
super().__init__(module, frequency=DateOffset(months=1))
super().__init__(module, frequency=DateOffset(months=module.parameters['main_polling_frequency_months']))
assert isinstance(module, Stunting)

def apply(self, population):
Expand All @@ -455,7 +476,7 @@ def apply(self, population):

# Onset of Stunting
eligible_for_stunting = (df.is_alive &
(df.age_exact_years < 5.0) &
(df.age_exact_years < self.module.parameters['max_age_for_stunting_years']) &
(df.un_HAZ_category == 'HAZ>=-2'))
idx_will_be_stunted = self.apply_model(
model=models.lm_prob_becomes_stunted,
Expand All @@ -466,7 +487,7 @@ def apply(self, population):

# Recovery from Stunting
eligible_for_recovery = (df.is_alive &
(df.age_exact_years < 5.0) &
(df.age_exact_years < self.module.parameters['max_age_for_stunting_years']) &
(df.un_HAZ_category != 'HAZ>=-2') &
~df.index.isin(idx_will_be_stunted))
idx_will_recover = self.apply_model(
Expand All @@ -478,7 +499,7 @@ def apply(self, population):

# Progression to Severe Stunting
eligible_for_progression = (df.is_alive &
(df.age_exact_years < 5.0) &
(df.age_exact_years < self.module.parameters['max_age_for_stunting_years']) &
(df.un_HAZ_category == '-3<=HAZ<-2') & ~df.index.isin(idx_will_be_stunted)
& ~df.index.isin(idx_will_recover))
idx_will_progress = self.apply_model(
Expand Down Expand Up @@ -523,8 +544,9 @@ def apply(self, population):
"""Log the current distribution of stunting classification by age"""
df = population.props

subset = df.loc[df.is_alive & (df.age_years < 5)].copy()
subset["age_years"] = pd.Categorical(subset["age_years"], categories=range(5))
max_age = int(self.module.parameters['max_age_for_stunting_years'])
subset = df.loc[df.is_alive & (df.age_years < max_age)].copy()
subset["age_years"] = pd.Categorical(subset["age_years"], categories=range(max_age))
d_to_log = subset.groupby(
by=['age_years', 'un_HAZ_category']).size().sort_index().to_dict()

Expand Down Expand Up @@ -560,8 +582,9 @@ def apply(self, person_id, squeeze_factor):
if not person.is_alive:
return

# Only do anything if child is under 5 and remains stunted
if (person.age_years < 5) and (person.un_HAZ_category in ['HAZ<-3', '-3<=HAZ<-2']):
# Only do anything if child is under max age and remains stunted
if (person.age_years < self.module.parameters['max_age_for_stunting_years']) and \
(person.un_HAZ_category in ['HAZ<-3', '-3<=HAZ<-2']):

# Provide supplementary feeding if consumable available, otherwise provide 'education only' (which has a
# different probability of success).
Expand All @@ -581,7 +604,7 @@ def apply(self, person_id, squeeze_factor):
self.sim.modules['HealthSystem'].schedule_hsi_event(
hsi_event=self,
priority=2, # <-- lower priority that for wasting and most other HSI
topen=self.sim.date + pd.DateOffset(months=6)
topen=self.sim.date + pd.DateOffset(months=self.module.parameters['followup_appointment_months'])
)


Expand Down