Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ combine-as-imports = true
convention = "google"

[tool.ruff.lint.pylint]
max-args = 7
max-args = 8

[tool.ruff.format]
quote-style = "double"
Expand Down
4 changes: 4 additions & 0 deletions src/nhp/model/aae.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class AaEModel(Model):
run_params: The parameters to use for each model run. Generated automatically if left as
None. Defaults to None.
save_full_model_results: Whether to save the full model results or not. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.
"""

def __init__(
Expand All @@ -35,6 +36,7 @@ def __init__(
hsa: Any = None,
run_params: dict | None = None,
save_full_model_results: bool = False,
aggregation_columns: list[str] | None = None,
) -> None:
"""Initialise the A&E Model.

Expand All @@ -44,6 +46,7 @@ def __init__(
hsa: Health Status Adjustment object. Defaults to None.
run_params: The run parameters to use. Defaults to None.
save_full_model_results: Whether to save full model results. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.
"""
# call the parent init function
super().__init__(
Expand All @@ -54,6 +57,7 @@ def __init__(
hsa,
run_params,
save_full_model_results,
aggregation_columns,
)
Comment thread
tomjemmett marked this conversation as resolved.

def _get_data(self, data_loader: Data) -> pd.DataFrame:
Expand Down
4 changes: 4 additions & 0 deletions src/nhp/model/inpatients.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class InpatientsModel(Model):
run_params: The parameters to use for each model run. Generated automatically if left as
None. Defaults to None.
save_full_model_results: Whether to save the full model results or not. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.
"""

def __init__(
Expand All @@ -36,6 +37,7 @@ def __init__(
hsa: Any = None,
run_params: dict | None = None,
save_full_model_results: bool = False,
aggregation_columns: list[str] | None = None,
) -> None:
"""Initialise the Inpatients Model.

Expand All @@ -45,6 +47,7 @@ def __init__(
hsa: Health Status Adjustment object. Defaults to None.
run_params: The run parameters to use. Defaults to None.
save_full_model_results: Whether to save full model results. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.
"""
# call the parent init function
super().__init__(
Expand All @@ -55,6 +58,7 @@ def __init__(
hsa,
run_params,
save_full_model_results,
aggregation_columns,
)
Comment thread
tomjemmett marked this conversation as resolved.

def _get_data(self, data_loader: Data) -> pd.DataFrame:
Expand Down
11 changes: 8 additions & 3 deletions src/nhp/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Model:
run_params: The parameters to use for each model run. Generated automatically if left as
None. Defaults to None.
save_full_model_results: Whether to save the full model results or not. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.

Attributes:
model_type: A string describing the type of model, must be one of "aae", "ip", or "op".
Expand All @@ -67,6 +68,7 @@ def __init__(
hsa: Any | None = None,
run_params: dict | None = None,
save_full_model_results: bool = False,
aggregation_columns: List[str] | None = None,
) -> None:
"""Initialise the Model.

Expand All @@ -78,6 +80,8 @@ def __init__(
hsa: Health Status Adjustment object. Defaults to None.
run_params: The run parameters to use. Defaults to None.
save_full_model_results: Whether to save full model results. Defaults to False.
aggregation_columns: The columns to use for aggregation. If left None (Default), then
["pod", "sitetret"] is used.
"""
valid_model_types = ["aae", "ip", "op"]
assert model_type in valid_model_types, "Model type must be one of 'aae', 'ip', or 'op'"
Expand Down Expand Up @@ -107,6 +111,8 @@ def __init__(
self.run_params = run_params or self.generate_run_params(params)
self.save_full_model_results = save_full_model_results

self._aggregation_columns = aggregation_columns or ["pod", "sitetret"]

@property
def measures(self) -> List[str]:
"""The names of the measure columns.
Expand Down Expand Up @@ -408,8 +414,7 @@ def path_fn(f):

return mr.get_aggregate_results()

@staticmethod
def get_agg(results: pd.DataFrame, *args: str) -> pd.Series:
def get_agg(self, results: pd.DataFrame, *args: str) -> pd.Series:
"""Get aggregation from model results.

Args:
Expand All @@ -419,7 +424,7 @@ def get_agg(results: pd.DataFrame, *args: str) -> pd.Series:
Returns:
Aggregated results.
"""
return results.groupby(["pod", "sitetret", *args, "measure"])["value"].sum()
return results.groupby([*self._aggregation_columns, *args, "measure"])["value"].sum()

def save_results(self, model_iteration: ModelIteration, path_fn: Callable[[str], str]) -> None:
"""Save the results of running the model.
Expand Down
4 changes: 4 additions & 0 deletions src/nhp/model/outpatients.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class OutpatientsModel(Model):
run_params: The parameters to use for each model run. Generated automatically if left as
None. Defaults to None.
save_full_model_results: Whether to save the full model results or not. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.
"""

def __init__(
Expand All @@ -35,6 +36,7 @@ def __init__(
hsa: Any = None,
run_params: dict | None = None,
save_full_model_results: bool = False,
aggregation_columns: list[str] | None = None,
) -> None:
"""Initialise the Outpatients Model.

Expand All @@ -44,6 +46,7 @@ def __init__(
hsa: Health Status Adjustment object. Defaults to None.
run_params: The run parameters to use. Defaults to None.
save_full_model_results: Whether to save full model results. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.
"""
# call the parent init function
super().__init__(
Expand All @@ -54,6 +57,7 @@ def __init__(
hsa,
run_params,
save_full_model_results,
aggregation_columns,
)
Comment thread
tomjemmett marked this conversation as resolved.

def _get_data(self, data_loader: Data) -> pd.DataFrame:
Expand Down
23 changes: 17 additions & 6 deletions src/nhp/model/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ def update(self, n=1):
tqdm.progress_callback(self.n)


def timeit(func: Callable, *args) -> Any:
def timeit(func: Callable, *args, **kwargs) -> Any:
"""Time how long it takes to evaluate function `f` with arguments `*args`."""
start = time.time()
results = func(*args)
results = func(*args, **kwargs)
print(f"elapsed: {time.time() - start:.3f}s")
return results

Expand All @@ -50,6 +50,7 @@ def _run_model(
run_params: dict,
progress_callback: Callable[[Any], None],
save_full_model_results: bool,
aggregation_columns: list[str] | None = None,
) -> list[ModelRunResult]:
"""Run the model iterations.

Expand All @@ -63,15 +64,18 @@ def _run_model(
run_params: The generated run parameters for the model run.
progress_callback: A callback function for progress updates.
save_full_model_results: Whether to save full model results.
aggregation_columns: The columns to use for aggregation. Defaults to None.

Returns:
A list containing the aggregated results for all model runs.
"""
model_class = model_type.__name__[:-5]
logging.info("%s", model_class)
logging.info(" * instantiating")
# ignore type issues here: Model has different arguments to Inpatients/Outpatients/A&E
model = model_type(params, data, hsa, run_params, save_full_model_results) # type: ignore
# ignore type issues here: model_type is Type[Model] so ty checks against Model.__init__,
# which has extra leading positional args (model_type, measures) that the concrete subclasses
# don't expose — the positional mapping is correct at runtime.
model = model_type(params, data, hsa, run_params, save_full_model_results, aggregation_columns) # ty: ignore[invalid-argument-type]
logging.info(" * running")
Comment thread
tomjemmett marked this conversation as resolved.

# set the progress callback for this run
Expand Down Expand Up @@ -115,6 +119,7 @@ def run_all(
nhp_data: Callable[[int, str], Data],
progress_callback: Callable[[Any], Callable[[Any], None]] = noop_progress_callback,
save_full_model_results: bool = False,
aggregation_columns: list[str] | None = None,
) -> tuple[dict[str, pd.DataFrame], list[str]]:
Comment thread
tomjemmett marked this conversation as resolved.
"""Run the model.

Expand All @@ -126,6 +131,7 @@ def run_all(
progress_callback: A callback function for updating progress.
Defaults to noop_progress_callback.
save_full_model_results: Whether to save full model results. Defaults to False.
aggregation_columns: The columns to use for aggregation. Defaults to None.

Returns:
A dictionary containing the results dataframes, and a list of the variants that were run.
Expand All @@ -152,6 +158,7 @@ def run_all(
run_params,
progress_callback(m.__name__[:-5]),
save_full_model_results,
aggregation_columns,
)
for m in model_types
]
Expand All @@ -161,13 +168,17 @@ def run_all(


def run_single_model_run(
params: dict, data_path: str, model_type: Type[Model], model_run: int
params: dict,
data_path: str,
model_type: Type[Model],
model_run: int,
aggregation_columns: list[str] | None = None,
) -> None:
Comment thread
tomjemmett marked this conversation as resolved.
"""Runs a single model iteration for easier debugging in vscode."""
data = Local.create(data_path)

print("initialising model... ", end="")
model = timeit(model_type, params, data)
model = timeit(model_type, params, data, aggregation_columns=aggregation_columns)
print("running model... ", end="")
m_run = timeit(ModelIteration, model, model_run)
print("aggregating results... ", end="")
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/nhp/model/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
@pytest.fixture
def mock_model():
"""Create a mock Model instance."""
with patch.object(Model, "__init__", lambda s, m, p, d, c: None):
with patch.object(Model, "__init__", lambda *args, **kwargs: None):
mdl = Model(None, None, None, None) # type: ignore
mdl.model_type = "aae"
mdl._aggregation_columns = ["pod", "sitetret"]
mdl.params = {
Comment thread
tomjemmett marked this conversation as resolved.
"input_data": "synthetic",
"model_runs": 3,
Expand Down
5 changes: 4 additions & 1 deletion tests/unit/nhp/model/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def test_run_all(mocker):
{"variant": "variants"},
pc_m(),
False,
None,
)
for m in [InpatientsModel, OutpatientsModel, AaEModel]
]
Expand Down Expand Up @@ -184,7 +185,9 @@ def test_run_single_model_run(mocker, capsys):
ndl_mock.create.assert_called_once_with("data")

assert timeit_mock.call_count == 3
assert timeit_mock.call_args_list[0] == call("model_type", params, "nhp_data")
assert timeit_mock.call_args_list[0] == call(
"model_type", params, "nhp_data", aggregation_columns=None
)
assert timeit_mock.call_args_list[2] == call(mr_mock.get_aggregate_results)

assert capsys.readouterr().out == "\n".join(
Expand Down
Loading