Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
dbff470
Investigate analysis of events at sim level
marghe-molaro Apr 3, 2024
bf64628
Merge branch 'master' into molaro/harvest-training-data
marghe-molaro Sep 17, 2024
05098f7
Final data-printing set-up
marghe-molaro Sep 30, 2024
16c071c
Print event chains
marghe-molaro Oct 2, 2024
ba81487
Add chains in mode 2 too and clean up in simuation
marghe-molaro Oct 2, 2024
0474624
Merged with master, and moved all logging into event module to keep t…
marghe-molaro Oct 2, 2024
b1c907c
Fix issue with tests by ensuring standard Polling and infection is ma…
marghe-molaro Oct 7, 2024
cfb4264
Switch iloc for loc
marghe-molaro Oct 7, 2024
e0327de
Change syntax of if statement
marghe-molaro Oct 7, 2024
fceee02
Change syntax of if statement and print string of event
marghe-molaro Oct 9, 2024
eaeae62
Focus on rti and print footprint
marghe-molaro Oct 10, 2024
c7bd9d0
Only store change in individual properties, not entire property row. …
marghe-molaro Oct 11, 2024
769aaec
Style fixes
marghe-molaro Oct 11, 2024
757cee3
Include printing of individual properties at the beginning and at bir…
marghe-molaro Oct 13, 2024
22a5e44
Log everything to simulation, as events logger doesn't seem to be vis…
marghe-molaro Oct 16, 2024
7faa817
Consider all modules included as of interest
marghe-molaro Oct 18, 2024
7232f97
Remove pop-wide HSI warning and make epi default even when printing c…
marghe-molaro Oct 18, 2024
98a8832
Merge branch 'master' into molaro/harvest-training-data
marghe-molaro Oct 18, 2024
a6def2d
Style fix
marghe-molaro Oct 18, 2024
ecea532
Remove data generation test, which wasn't really a test
marghe-molaro Oct 18, 2024
ae7a44c
Change dict of properties to string in logging, and add analysis files
marghe-molaro Oct 23, 2024
16299a2
Include debugging option, final set-up of scenario to print data, ana…
marghe-molaro Nov 25, 2024
0dd862f
Change label of person when iterating
marghe-molaro Nov 26, 2024
0e7dc99
Merge branch 'master' into molaro/harvest-training-data
marghe-molaro Dec 9, 2024
84f8263
Correctly retrieve event name
marghe-molaro Dec 13, 2024
a490d19
Modify scenario file such that can exclude specific services, and cor…
marghe-molaro Jan 20, 2025
08a5d9a
Change seed in scenario file
marghe-molaro Apr 12, 2025
3dda343
latest scenario
marghe-molaro Apr 14, 2025
d9e3f66
Latest scenario version
marghe-molaro Apr 29, 2025
ddf6f68
Latest version of scenario file
marghe-molaro Apr 29, 2025
0e38408
Ensure changes to mni dataframe are captured as well
marghe-molaro Oct 9, 2025
9b8f01f
Tidy up
marghe-molaro Oct 9, 2025
3b81de6
All fixes made
marghe-molaro Oct 9, 2025
bc61e1e
Cleaned and [skip ci]
marghe-molaro Nov 17, 2025
e084e39
Start logging data in EAV format
marghe-molaro Nov 20, 2025
ac617e8
Log event chains via EAV approach
marghe-molaro Nov 21, 2025
5234550
No need to store EventDate since this is already stored in logger by …
marghe-molaro Nov 21, 2025
b3e6ccc
Merge master
marghe-molaro Nov 21, 2025
2f20cb3
Check if PregnancySupervisor is included before considering in chain …
marghe-molaro Nov 21, 2025
1b83823
Remove old util fnc
marghe-molaro Nov 22, 2025
f4cf120
Overwrite any changes to hiv and tb file
marghe-molaro Nov 22, 2025
29dd543
Overwrite any changes to demography file
marghe-molaro Nov 22, 2025
33f1143
Remove outdated test related to RTI data harvesting
marghe-molaro Nov 22, 2025
af477c2
Add a very simple synchronous notification dispatcher
tamuri Nov 24, 2025
01e35d0
Fix comment
tamuri Nov 24, 2025
9f23fcb
Fix formatting
tamuri Nov 24, 2025
5ff53bb
Remove unnecessary list wrap
tamuri Nov 24, 2025
02278b3
Merge branch 'tamuri/signal' into molaro/harvest-training-data-includ…
marghe-molaro Nov 24, 2025
16f5e67
Use broadcaster to collect events. Need to expand to include HSI events
marghe-molaro Nov 24, 2025
ebe0ebc
Use broadcasting in HSI events too
marghe-molaro Nov 25, 2025
e617aa9
Clear listeners in the global notifier instance at the start of simul…
tamuri Nov 25, 2025
4fe8e1f
Correct log name in analysis file
marghe-molaro Nov 25, 2025
474a1e5
Merge branch 'tamuri/signal' into molaro/harvest-training-data-includ…
marghe-molaro Nov 25, 2025
c1e6096
Summarise checks on whether to collect event changes
marghe-molaro Nov 25, 2025
5e07204
Use module names rather than obj for ease of use
marghe-molaro Nov 25, 2025
2ce9bbd
Fix parameters initialisation
marghe-molaro Nov 25, 2025
a786b2e
Fix to type of parameter
marghe-molaro Nov 25, 2025
7af8c70
Give option to overwrite module parameters
marghe-molaro Nov 25, 2025
d8e6922
Correct use of parameters
marghe-molaro Nov 25, 2025
fd761f7
Exit as soon as condition is not met
marghe-molaro Nov 25, 2025
edd9e0b
Allow option to overwrite parameter file
marghe-molaro Nov 25, 2025
24eacdb
No need for else if exiting function
marghe-molaro Nov 26, 2025
017c6d2
Turn off ruff warnings. Far too frequent changes.
tamuri Nov 26, 2025
2942701
Include test for event chains collection
marghe-molaro Nov 26, 2025
68b1961
Scenario file
marghe-molaro Nov 26, 2025
ca72352
Merge branch 'tamuri/quieter-ruff' into molaro/harvest-training-data-…
marghe-molaro Nov 26, 2025
ba78a01
Style fixes
marghe-molaro Nov 26, 2025
44253a2
Final style fixes
marghe-molaro Nov 26, 2025
add05e9
Track PR specific test file
marghe-molaro Nov 26, 2025
0fe1d80
Rename module and keys, and assume that if module is included then wi…
marghe-molaro Nov 27, 2025
65522a1
Revert changes in rti module
marghe-molaro Nov 27, 2025
c10e6aa
Revert changes in rti module
marghe-molaro Nov 27, 2025
ab975c2
change function names on listener's end
marghe-molaro Nov 27, 2025
450f06c
Restructure data passed by dispatcher
marghe-molaro Nov 27, 2025
9e36395
Style fixes
marghe-molaro Nov 27, 2025
07beb70
Remove individual history tracker from the fullmodule
marghe-molaro Nov 27, 2025
3644668
Fix analysis scripts
marghe-molaro Nov 27, 2025
23f45e1
Fix retreival of class name
marghe-molaro Nov 27, 2025
e6f35cc
Add resource file
marghe-molaro Nov 27, 2025
b4301ce
Fix EventName error and logging of EAV dataframe
marghe-molaro Nov 28, 2025
8177340
Adjust utily functions based on new logging
marghe-molaro Nov 28, 2025
2215645
Rename E column
marghe-molaro Nov 28, 2025
d869f17
Rename column name
marghe-molaro Nov 28, 2025
3d11545
Check changes in df
marghe-molaro Nov 28, 2025
c146326
Ensure order of events on same date is preserved
marghe-molaro Nov 28, 2025
a148696
Merge branch 'master' into molaro/harvest-training-data-including-mni
tamuri Dec 2, 2025
d48376f
Rename module to allow bundling other useful things in there
tamuri Dec 2, 2025
af4ace9
Clean up variable names
tamuri Dec 2, 2025
fcf803a
Fix variable names
tamuri Dec 2, 2025
f615f14
Move individual-history-specific utils into its module
tamuri Dec 2, 2025
21482a6
Don't send data if there isn't any - default is `None`
tamuri Dec 3, 2025
87f4710
Rename HSI event notifications
tamuri Dec 3, 2025
950c2f7
Merge branch 'master' into molaro/harvest-training-data-including-mni
tamuri Dec 3, 2025
d44285e
Simplify handling of mni differences for individual
marghe-molaro Dec 4, 2025
d01fb3e
Fix issue with nan changes being saved. This was down to EAV conversi…
marghe-molaro Dec 5, 2025
1e0c55a
Unify approach taken to copy pop dataframe and mni, and remove all ev…
marghe-molaro Dec 5, 2025
43f9c71
Merge branch 'master' into molaro/harvest-training-data-including-mni
tbhallett Dec 17, 2025
1269bda
Add tracking property for individual
marghe-molaro Dec 18, 2025
601eb11
Merge branch 'molaro/harvest-training-data-including-mni' of https://…
marghe-molaro Dec 18, 2025
b8b858e
Track consumable access
marghe-molaro Dec 19, 2025
71a7776
Additionally log equipment and beddays
marghe-molaro Dec 19, 2025
2feff77
Log consumable access as part of HSI
marghe-molaro Dec 19, 2025
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
Git LFS file not shown
82 changes: 82 additions & 0 deletions src/scripts/track_individual_histories/analysis_extract_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import argparse
from pathlib import Path

import pandas as pd

from tlo.analysis.utils import extract_individual_histories


def print_filtered_df(df):
"""
Prints rows of the DataFrame excluding event_name 'Initialise' and 'Birth'.
"""
pd.set_option('display.max_colwidth', None)
filtered = df # [~df['event_name'].isin(['StartOfSimulation', 'Birth'])]

dict_cols = ["Info"]
max_items = 2
# Step 2: Truncate dictionary columns for display
if dict_cols is not None:
for col in dict_cols:
def truncate_dict(d):
if isinstance(d, dict):
items = list(d.items())[:max_items] # keep only first `max_items`
return dict(items)
return d
filtered[col] = filtered[col].apply(truncate_dict)
print(filtered)


def apply(results_folder: Path, output_folder: Path, resourcefilepath: Path = None, ):
"""Extract event chains
"""
pd.set_option('display.max_rows', None)
pd.set_option('display.max_colwidth', None)

individual_individual_histories = extract_individual_histories(results_folder)

if __name__ == "__main__":
rfp = Path('resources')

parser = argparse.ArgumentParser(
description="Produce plots to show the impact each set of treatments",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--output-path",
help=(
"Directory to write outputs to. If not specified (set to None) outputs "
"will be written to value of --results-path argument."
),
type=Path,
default=None,
required=False,
)
parser.add_argument(
"--resources-path",
help="Directory containing resource files",
type=Path,
default=Path('resources'),
required=False,
)
parser.add_argument(
"--results-path",
type=Path,
help=(
"Directory containing results from running "
"src/scripts/analysis_data_generation/scenario_track_individual_histories.py "
),
default=None,
required=False
)
args = parser.parse_args()
assert args.results_path is not None
results_path = args.results_path

output_path = results_path if args.output_path is None else args.output_path

apply(
results_folder=results_path,
output_folder=output_path,
resourcefilepath=args.resources_path
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""This Scenario file run the model to track individual histories

Run on the batch system using:
```
tlo batch-submit
src/scripts/analysis_data_generation/scenario_track_individual_histories.py
```

or locally using:
```
tlo scenario-run src/scripts/analysis_data_generation/scenario_track_individual_histories.py
```

"""
from pathlib import Path
from typing import Dict

import pandas as pd

from tlo import Date, logging
from tlo.analysis.utils import get_parameters_for_status_quo, mix_scenarios
from tlo.methods import individual_history_tracker
from tlo.methods.fullmodel import fullmodel
from tlo.scenario import BaseScenario


class TrackIndividualHistories(BaseScenario):
def __init__(self):
super().__init__()
self.seed = 42
self.start_date = Date(2010, 1, 1)
self.end_date = self.start_date + pd.DateOffset(years=5)
self.pop_size = 100
self._scenarios = self._get_scenarios()
self.number_of_draws = len(self._scenarios)
self.runs_per_draw = 1
self.generate_event_chains = True

def log_configuration(self):
return {
'filename': 'track_individual_histories',
'directory': Path('./outputs'), # <- (specified only for local running)
'custom_levels': {
'*': logging.WARNING,
'tlo.methods.demography': logging.INFO,
'tlo.methods.events': logging.INFO,
'tlo.methods.demography.detail': logging.WARNING,
'tlo.methods.healthburden': logging.INFO,
'tlo.methods.healthsystem.summary': logging.INFO,
'tlo.methods.individual_history': logging.INFO
}
}

def modules(self):
return (
fullmodel() + [individual_history_tracker.IndividualHistoryTracker()]
)

def draw_parameters(self, draw_number, rng):
if draw_number < self.number_of_draws:
return list(self._scenarios.values())[draw_number]
else:
return

def _get_scenarios(self) -> Dict[str, Dict]:

return {
"Baseline":
mix_scenarios(
self._baseline(),
{
}
),

}

def _baseline(self) -> Dict:
#Return the Dict with values for the parameter changes that define the baseline scenario.
return mix_scenarios(
get_parameters_for_status_quo(),
{
"HealthSystem": {
"mode_appt_constraints": 1, # <-- Mode 1 prior to change to preserve calibration
}
},
)

if __name__ == '__main__':
from tlo.cli import scenario_run

scenario_run([__file__])
124 changes: 124 additions & 0 deletions src/tlo/analysis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,130 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series:
_concat.columns.names = ['draw', 'run'] # name the levels of the columns multi-index
return _concat

def check_info_value_changes(df):

problems = {} # store issues

# iterate group-by-group
for E, g in df.groupby("entity"):
prev_info = {}

for _, row in g.iterrows():
current_info = row["Info"]

for key, value in current_info.items():
if key in prev_info and key != 'footprint' and key != 'level':
# compare with previous value
if prev_info[key] == value and key not in problems.keys():
problems[key] = value

# update latest value
prev_info = row["Info"]

return problems

def remove_events_for_individual_after_death(df):
rows_to_drop = []

# Group by entity
for entity, g in df.groupby("entity"):
died = False

for idx, row in g.iterrows():
current_info = row["Info"]

if not died:
# Check if this row marks death
if isinstance(current_info, dict) and current_info.get("is_alive") is False:
died = True
else:
# Already dead → mark this row for removal
rows_to_drop.append(idx)

# Drop all marked rows
return df.drop(index=rows_to_drop)

def reconstruct_individual_histories(df):

# Collapse into 'entity', 'date', 'event_name', 'Info' format where 'Info' is dict listing attributes
# (e.g. {a1:v1, a2:v2, a3:v3, ...} )
df_collapsed = (
df.groupby(['entity', 'date', 'event_name'], sort=False)
.apply(lambda g: dict(zip(g['attribute'], g['value'])))
.reset_index(name='Info')
)

df_final = (
df_collapsed
.sort_values(by=['entity', 'date'])
.reset_index(drop=True)
)

df_final = remove_events_for_individual_after_death(df_final)

problems = check_info_value_changes(df_final)
if len(problems)>0:
print("Values didn't change but were still detected")
print(problems)



return df_final


def extract_individual_histories(results_folder: Path,
) -> dict:
"""Utility function to collect chains of events. Individuals across runs of the same draw
will be combined into unique df.
Returns dictionary where keys are draws, and each draw is associated with a dataframe of
format 'entity', 'date', 'event_name', 'Info' where 'Info' is a dictionary that combines
A&Vs for a particular individual + date + event name combination.
"""
module = 'tlo.methods.individual_history'
key = 'individual_histories'

# get number of draws and numbers of runs
info = get_scenario_info(results_folder)

# Collect results from each draw/run. Individuals across runs of the same draw will be combined into unique df.
res = dict()

for draw in range(info['number_of_draws']):

# All individuals in same draw will be combined across runs, so their ID will be offset.
dfs_from_runs = []
ID_offset = 0

for run in range(info['runs_per_draw']):

try:
df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key]
df_single_run= reconstruct_individual_histories(df)

# Offset person ID to account for the fact that we are collecting chains across runs
df_single_run['entity'] = df_single_run['entity'] + ID_offset

# Calculate ID offset for next run
ID_offset = (max(df_single_run['entity']) + 1)

# The E has now become an ID for the individual in the draw overall, so rename column as such
df_single_run = df_single_run.rename(columns={'entity': 'person_ID_in_draw'})

# Append these chains to list
dfs_from_runs.append(df_single_run)

except KeyError:
# Some logs could not be found - probably because this run failed.
# Simply to not append anything to the df collecting chains.
print("Run failed")

# Combine all dfs into a single DataFrame
res[draw] = pd.concat(dfs_from_runs, ignore_index=True)

res[0].to_csv('individual_histories.csv')

return res


def compute_summary_statistics(
results: pd.DataFrame,
Expand Down
13 changes: 12 additions & 1 deletion src/tlo/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
if TYPE_CHECKING:
from tlo import Simulation

from tlo.notify import notifier


class Priority(Enum):
"""Enumeration for the Priority, which is used in sorting the events in the simulation queue."""
Expand All @@ -22,7 +24,6 @@ def __lt__(self, other):
return self.value < other.value
return NotImplemented


class Event:
"""Base event class, from which all others inherit.

Expand Down Expand Up @@ -63,9 +64,19 @@ def apply(self, target):

def run(self):
"""Make the event happen."""

# Dispatch notification that event is about to run
notifier.dispatch("event.pre-run", data={"target": self.target,
"module" : self.module.name,
"event_name": self.__class__.__name__})

self.apply(self.target)
self.post_apply_hook()

# Dispatch notification that event has just ran
notifier.dispatch("event.post-run", data={"target": self.target,
"event_name": self.__class__.__name__})


class RegularEvent(Event):
"""An event that automatically reschedules itself at a fixed frequency."""
Expand Down
Loading
Loading