Introduction
This vignette provides an overview of the DiseasyModel
class and its use in creating a diseasy
model.
As described in the package introduction
(vignette("diseasy")
), the power of diseasy
comes from combining different “functional” modules with model
“templates” to create a large ensemble of models easily.
It is DiseasyModel
that defines how such model templates
should look, and provides the mechanism by which the functional modules
are loaded in the model templates to create the individual model members
of the ensemble.
Before we dive into the details of DiseasyModel
, let’s
first consider what it means to be a “model template”:
Model Templates
Developing a model in diseasy
involves constructing a
model template that incorporates one or more functional modules into a
model design.
For instance, let’s consider the creation of a simple SIR (Susceptible, Infectious, Recovered) infection model with a seasonal forcing of the overall infection rate, denoted by .
The SEIR model with seasonal forcing can be represented by the following set of differential equations:
When developing this model traditionally, we would choose some functional form for the seasonal forcing , e.g. a sinusoidal relationship:
The strategy of diseasy
is to source the seasonal
forcing
directly from the corresponding functional module
(DiseasySeason
).
Every implementation of a seasonal forcing in
DiseasySeason
has the same function signature (i.e. they
all take only a single argument
once configured).
This way, we can configure different instances of
DiseasySeason
using the whole range of (available)
plausible functional forms of seasonal forcing:
- No seasonal forcing ()
- Sinusoidal forcing
- Temperature dependent forcing
- Other plausible seasonal forcing forms
Since the modules of diseasy
are “objects” in an
object-oriented-programming sense, the functional forms of the seasonal
forcing can have parameters (as we see in the list above), but these are
stored in the object when configuring the module, and the actual
seasonal forcing exposed to the models all have the same function
signature that takes only time as input
()1.
This way, we can easily swap out different seasonal forcing models in our model templates without having to change the model equations themselves. If new seasonal forcing models are developed, they are then automatically available to the developed model.
This is the philosophy behind the diseasy
package, since
we can easily swap out different functional modules for the templates
and generate a large ensemble of models with functional forms for the
disease dynamics.
The DiseasyModel
class
To create a model template in diseasy
is to create a new
class that inherits the DiseasyModel
class.
This gives the model template access to the functional modules that are available in the package, it defines how the user sets the parameters of the model and queries the model for results.
In addition, it provides useful utilities for the implementation of the model such as logging and caching.
A container for functional modules
The DiseasyModel
class contains slots for instances of
functional modules:
-
$activity
:{DiseasyActivity}
-
$observables
:{DiseasyObservables}
-
$season
:{DiseasySeason}
-
$variant
:{DiseasyVariant}
This means, that once a new DiseasyModel
instance is
created, it will have the functional modules internally available. Going
back to the above example of the SIR model with seasonal forcing, the
model template would be access the seasonal forcing
via the call self$season$model_t(t)
.
Training data
The DiseasyModel
class also contains the method
$get_training_data()
that provides training data to the
models.
This method interfaces with the DiseasyObservables
module to provide data for an observable at some stratification level.
The level of stratification is generally a flexible parameter that will
be set by the user when requesting results from the model. For example,
the user my request results at the country level or at the regional
level.
The helper method $get_training_data()
will then provide
the training data for the observable at the requested level. This data
will contain one record for each time point in the training period, with
columns for the date of the observation, the values of the different
(optional) stratification, and the value of the observable on the date,
and finally, an integer counter for the day relative to the end of the
training period.
Model-user interface
Beyond providing access to the functional modules,
DiseasyModel
defines how the user will interface with the
model.
Setting parameters
The default parameters of the model should be contained within the
$parameters
slot of the model template.
These are then exposed to the user, and logic can be implemented to allow the user to set some parameters of the model while leaving others free to be determined by the model during model fitting.
For technical reasons2, you should implement the
private$default_parameters()
slot as a function that
combines the model-specific parameters with the inherited parameters
from the superclasses.
{r .parameters implementation, eval = FALSE} # nolint: assignment_linter, object_usage_linter default_parameters = function() { modifyList( super$default_parameters(), # Obtain parameters from the superclasses # Overwrite with model-specific parameters list("model_specific_param_1" = 1, "model_specific_param_2" = "method_1"), keep.null = TRUE ) }
# nolint end During initialisation, these are then evaluated to a single
list of parameters, updated with the user-specified values for the
parmaters and stored in the private$.parameters
slot. The
parameters are then acccessable via $parameters
.
Validating the parameters
The model template can test the user provided parameters
by implementing the private$validate_parameters()
method.
Note that some parameters may be set inherited from superclasses, so the
implementation of the $validate_parameters()
method should
also call the parent validation method
(super$validate_parameters()
).
{r .parameter validation implementation, eval = FALSE} # nolint: assignment_linter, object_usage_linter validate_parameters = function() { checkmate::assert_number(self$parameters$model_specific_param_1) checkmate::assert_choice( self$parameters$model_specific_param_2, c("method_1", "method_2") ) super$validate_parameters() }
# nolint end
Getting results
Once the parameters of the model is set (either by the user or by the
model), the user can query the model for results using the
$get_results()
function. Through this function, the user
provides the parameters of the result that should be provided (target
observable, time-window and stratification level). In return, the
function returns a standardised data.frame
-like output that
can be used for further analysis in conjunction with the results from
other models.
The outputs of the model are standardised, and should follow the
structure of the training_data
provided by
$get_training_data()
enriched by model prediction, model id
and realization id.