library(R6)
Parameters from script
Learning objectives:
- Recognise the drawbacks of hardcoding parameters in simulation models.
- Learn about methods and best practices for parameter management.
- Note ways to keep models organised when working with many parameters .
Relevant reproducibility guidelines:
- STARS Reproducibility Recommendations: Avoid hard-coded parameters.
Required packages:
This should be available from environment setup in the “🧪 Test yourself” section of Environments.
Discrete-event simulations (DES) require many parameters - like arrival rates, resource times, and probabilities - which often need to be changed for different scenarios and analyses. Managing these parameters well makes your simulations easier to update, track, and reuse.
This page explains best practices for managing model parameters directly within scripts.
🚫 What not to do: hardcoding parameters
Hardcoding means writing parameter values directly into your code. For example:
def model():
# Hardcoded parameter values
= 5
interarrival_time = 20
consultation_time = 0.3
transfer_prob # ...rest of the model...
<- function() {
model # Hardcoded parameter values
<- 5
interarrival_time <- 20
consultation_time <- 0.3
transfer_prob # ...rest of the model...
}
This makes it very difficult to change the values. Modellers might choose to…
1. Edit parameters directly in the script.
This involves manually changing parameter values in the script each time you want to run a new scenario. Problems with this approach include:
- ❌ Not a reproducible analytical pipeline. Alot of manual intervention is required to re-run the model with different parameters.
- ❌ Error-prone. It would be easy to make mistakes or forget to update all relevant values.
- ❌ Parameters can get lost. If you lost your notes or forget to record what you used, you won’t know what values were used for past runs.
2. Duplicate scripts for each scenario.
This involves copying the entire script, changing parameters in each copy, and running them separately. Problems with this approach include:
- ❌ Code duplication. This means any changes - like bug fixes or improvements to the model - must be made to every copy. This is tedious and there is a risk that some copies are missed or updated incorrectly.
- ❌ Hard to keep track. With multiple script copies, it can become difficult to know which scripts correspond to which scenarios, and which parameters were used (as have to delve into the model code to identify them).
📄 A slight improvement: global parameters
A better (but still limited) approach is to define all parameters at the top of your script.
This way, parameters are no longer hidden within the code. It becomes much easier to see which parameters are being used, and to change the values used.
# Parameters for base case
= 5
INTERARRIVAL_TIME = 20
CONSULTATION_TIME = 0.3
TRANSFER_PROB
def model():
# Use the global parameters
# ...
# Parameters for base case
<- 5
interarrival_time <- 20
consultation_time <- 0.3
transfer_prob
<- function() {
model # Use the global parameters
# ...
}
For scenarios, you would define the same global variables with alternative values:
# Scenario 1
= 6
INTERARRIVAL_TIME = 20
CONSULTATION_TIME = 0.3 TRANSFER_PROB
# Scenario 2
= 5
INTERARRIVAL_TIME = 20
CONSULTATION_TIME = 0.4 TRANSFER_PROB
# Scenario 1
<- 6
interarrival_time <- 20
consultation_time <- 0.3 transfer_prob
# Scenario 2
<- 5
interarrival_time <- 20
consultation_time <- 0.4 transfer_prob
The improvements are that parameters are:
- ✅ No longer hardcoded. Within the model, it refers to the variable name rather than a specific value,which means we are able to now list the values in one outside the model logic.
- ✅ Centralised. All parameters are in one place, making them easier to find and change.
However, there are still several disadvantages:
- ❌ Still inflexible. In order to re-run the model with different scenarios, you would still need to do the approaches above - editing code directly or duplicating scripts for each scenario.
- ❌ Not scalable. As the number of scenarios or parameters grows, managing all these global variables becomes messy.
✅ Recommendation for managing parameters in DES
Two critical practices
To manage parameters effectively, you need to:
- Group parameters into a dedicated object.
- Pass this object explicitly to your model.
Why?
- ✅ Clear parameter sets. Every scenario has its own object with all the parameters needed. This can be easily viewed, and comes in handy in logs to easily print a copy of all parameters used for a scenario.
- ✅ No global variables. By explicitly passing our parameters, we avoid accidental parameter reuse between scenarios (which is a possibility with global variables!).
- ✅ Fewer inputs. If all parameters are in one object, then we can just pass that as a single input to our model function/class, reducing the number of arguments we need to pass.
It’s important to use both of these practices.
If you only do option 1 (group parameters, but use as globals), parameters might accidentally be modified elsewhere, or one scenario’s parameters might affect another’s.
# BAD: Parameters are grouped but still global
= Parameters()
global_params
def simulate():
# Uses global_params.interarrival_time... 😬
...
# BAD: Parameters are grouped but still global
<- list()
global_params
<- function() {
simulate # Uses global_params$interarrival_time ... 😬
... }
If you only do option 2 (pass parameters, but don’t group them), you end up with messy, error-prone code that’s hard to maintain:
# BAD: Parameters are passed but disorganised
def simulate(interarrival_time, consultation_time, transfer_prob, ...):
# 10+ parameters? Hard to track!
...
# BAD: Parameters are passed but disorganised
<- function(interarrival_time, consultation_time, transfer_prob, ...) {
simulate # 10+ parameters? Hard to track!
... }
Three implementation options
There are three implementation options: dictionary, function or class.
Using a dictionary
# Base case
= {
base_params "interarrival_time": 5,
"consultation_time": 20,
"transfer_prob": 0.3,
}print(base_params)
{'interarrival_time': 5, 'consultation_time': 20, 'transfer_prob': 0.3}
# Create a scenario by copying and tweaking only what's needed
= base_params.copy()
scenario1 "interarrival_time"] = 6
scenario1[print(scenario1)
{'interarrival_time': 6, 'consultation_time': 20, 'transfer_prob': 0.3}
Using a function
def create_params(
=5, consultation_time=20, transfer_prob=0.3
interarrival_time
):"""
Generate parameter dictionary.
Parameters
----------
interarrival_time : float
Time between arrivals (minutes).
consultation_time : float
Length of consultation (minutes).
transfer_prob : float
Transfer probability (0-1).
Returns
-------
Dictionary containing parameters.
"""
return {
"interarrival_time": interarrival_time,
"consultation_time": consultation_time,
"transfer_prob": transfer_prob
}
# Base case
= create_params()
base_params print(base_params)
{'interarrival_time': 5, 'consultation_time': 20, 'transfer_prob': 0.3}
# Create a scenario with altered inter-arrival time
= create_params(interarrival_time=6)
scenario1 print(scenario1)
{'interarrival_time': 6, 'consultation_time': 20, 'transfer_prob': 0.3}
Using a class
class Parameters:
"""
Parameter class.
"""
def __init__(
self, interarrival_time=5, consultation_time=20, transfer_prob=0.3
):"""
Initialise Parameters instance.
Parameters
----------
interarrival_time : float
Time between arrivals (minutes).
consultation_time : float
Length of consultation (minutes).
transfer_prob : float
Transfer probability (0-1).
"""
self.interarrival_time = interarrival_time
self.consultation_time = consultation_time
self.transfer_prob = transfer_prob
# Base case
= Parameters()
base_params print(base_params.__dict__)
{'interarrival_time': 5, 'consultation_time': 20, 'transfer_prob': 0.3}
# Create a scenario with altered inter-arrival time
= Parameters(interarrival_time=6)
scenario1 print(scenario1.__dict__)
{'interarrival_time': 6, 'consultation_time': 20, 'transfer_prob': 0.3}
There are three implementation options: list, function or R6 class.
Using a list
# Base case
<- list(
base_params interarrival_time = 5L,
consultation_time = 20L,
transfer_prob = 0.3
) base_params
$interarrival_time
[1] 5
$consultation_time
[1] 20
$transfer_prob
[1] 0.3
# Create a scenario by copying and tweaking only what's needed
<- base_params
scenario1 $interarrival_time <- 6L
scenario1 scenario1
$interarrival_time
[1] 6
$consultation_time
[1] 20
$transfer_prob
[1] 0.3
Using a function
#' Generate parameter list.
#'
#' @param interarrival_time Numeric. Time between arrivals (minutes).
#' @param consultation_time Numeric. Length of consultation (minutes).
#' @param transfer_prob Numeric. Transfer probability (0-1).
#'
#' @return A named list of parameters.
<- function(interarrival_time = 5L,
create_params consultation_time = 20L,
transfer_prob = 0.3) {
list(
interarrival_time = interarrival_time,
consultation_time = consultation_time,
transfer_prob = transfer_prob
)
}
# Base case
<- create_params()
base_params base_params
$interarrival_time
[1] 5
$consultation_time
[1] 20
$transfer_prob
[1] 0.3
# Create a scenario with altered inter-arrival time
<- create_params(interarrival_time = 6L)
scenario1 scenario1
$interarrival_time
[1] 6
$consultation_time
[1] 20
$transfer_prob
[1] 0.3
Using a R6 class
Include a get_params
method to easily return a list of parameters.
#' @title Parameter class.
#'
#' @field interarrival_time Numeric. Time between arrivals (minutes).
#' @field consultation_time Numeric. Length of consultation (minutes).
#' @field transfer_prob Numeric. Transfer probability (0-1).
<- R6Class( # nolint: object_name_linter
Parameters classname = "Parameters",
public = list(
interarrival_time = NULL,
consultation_time = NULL,
transfer_prob = NULL,
initialize = function(
interarrival_time = 5L,
consultation_time = 20L,
transfer_prob = 0.3
) {$interarrival_time <- interarrival_time
self$consultation_time <- consultation_time
self$transfer_prob <- transfer_prob
self
},
#' @description
#' Returns parameters as a named list.
get_params = function() {
# Get all non-function fields
<- ls(self)
all_names <- vapply(
is_not_function
all_names,function(x) !is.function(self[[x]]),
FUN.VALUE = logical(1L)
)<- all_names[is_not_function]
param_names mget(param_names, envir = self)
}
)
)
# Base case
<- Parameters$new()
base_params $get_params() base_params
$consultation_time
[1] 20
$interarrival_time
[1] 5
$transfer_prob
[1] 0.3
# Create a scenario with altered inter-arrival time
<- Parameters$new(interarrival_time = 6L)
scenario1 $get_params() scenario1
$consultation_time
[1] 20
$interarrival_time
[1] 6
$transfer_prob
[1] 0.3
Which option is best?
The most robust approach is to use a function or class to manage your parameters.
- ✅ Functions and classes make it easy to create variations for different scenarios, since you simply change the inputs when you define a new scenario. For example, you can create a new scenario by only specifying the parameter you want to change, while all other parameters remain at their default values.
- ❌ With a dictionary, you have to make a copy of the base dictionary and then manually change individual values for each scenario. This can become cumbersome as the number of parameters or scenarios grows - and is just a bit more clunky!
- ❌ With a list, you have to make a copy of the base list and then manually change individual values for each scenario. This can become cumbersome as the number of parameters or scenarios grows - and is just a bit more clunky!
Your choice may be further informed by options for parameter validation, where classes can be superior as you can incorporate validation within the class - although it is ultimately possible to perform similar validation regardless of whether you use dictionaries, functions and classes - as discussed on the parameter validation page.
It’s worth noting though that, while R does support object-oriented programming (i.e. using classes), these are less commonly used in typical R code.
🧮 Handling a large number of parameters
You may need to manage many parameters - for example, if you have several patient types and/or units each with their own arrival times, resource times, and so on.
We have suggested a few strategies you could use…
Using a single function or class for all parameters
This can be convenient for smaller models, though can get unwieldly as the number of parameters grow, including potentially quite long parameter names! Although you could return a nested object to address this.
Function example (with nested dictionaries)
def create_params(
=5, adult_consultation=20, adult_transfer=0.3,
adult_interarrival=7, child_consultation=15, child_transfer=0.2,
child_interarrival=10, elderly_consultation=30, elderly_transfer=0.5
elderly_interarrival
):"""
Generate parameter dictionary.
Parameters
----------
adult_interarrival : float
Time between adult patient arrivals (minutes).
adult_consultation : float
Length of adult patient consultation (minutes).
adult_transfer : float
Adult patient transfer probability (0-1).
child_interarrival : float
Time between child patient arrivals (minutes).
child_consultation : float
Length of child patient consultation (minutes).
child_transfer : float
Child patient transfer probability (0-1).
elderly_interarrival : float
Time between elderly patient arrivals (minutes).
elderly_consultation : float
Length of elderly patient consultation (minutes).
elderly_transfer : float
Elderly patient transfer probability (0-1).
Returns
-------
Dictionary with three keys ("adult", "child", "elderly"), each containing a
dictionary of parameters.
"""
return {
"adult": {
"interarrival_time": adult_interarrival,
"consultation_time": adult_consultation,
"transfer_prob": adult_transfer
},"child": {
"interarrival_time": child_interarrival,
"consultation_time": child_consultation,
"transfer_prob": child_transfer
},"elderly": {
"interarrival_time": elderly_interarrival,
"consultation_time": elderly_consultation,
"transfer_prob": elderly_transfer
}
}
# Example usage
= create_params()
base_params print(base_params)
{'adult': {'interarrival_time': 5, 'consultation_time': 20, 'transfer_prob': 0.3}, 'child': {'interarrival_time': 7, 'consultation_time': 15, 'transfer_prob': 0.2}, 'elderly': {'interarrival_time': 10, 'consultation_time': 30, 'transfer_prob': 0.5}}
Class example
class Parameters:
"""
Parameter class.
"""
def __init__(
self,
=5, adult_consultation=20, adult_transfer=0.3,
adult_interarrival=7, child_consultation=15, child_transfer=0.2,
child_interarrival=10, elderly_consultation=30, elderly_transfer=0.5
elderly_interarrival
):"""
Initialise Parameters instance.
Parameters
----------
adult_interarrival : float
Time between adult patient arrivals (minutes).
adult_consultation : float
Length of adult patient consultation (minutes).
adult_transfer : float
Adult patient transfer probability (0-1).
child_interarrival : float
Time between child patient arrivals (minutes).
child_consultation : float
Length of child patient consultation (minutes).
child_transfer : float
Child patient transfer probability (0-1).
elderly_interarrival : float
Time between elderly patient arrivals (minutes).
elderly_consultation : float
Length of elderly patient consultation (minutes).
elderly_transfer : float
Elderly patient transfer probability (0-1).
"""
# Adult parameters
self.adult_interarrival = adult_interarrival
self.adult_consultation = adult_consultation
self.adult_transfer = adult_transfer
# Child parameters
self.child_interarrival = child_interarrival
self.child_consultation = child_consultation
self.child_transfer = child_transfer
# Elderly parameters
self.elderly_interarrival = elderly_interarrival
self.elderly_consultation = elderly_consultation
self.elderly_transfer = elderly_transfer
# Example usage
= Parameters()
base_params print(base_params.__dict__)
{'adult_interarrival': 5, 'adult_consultation': 20, 'adult_transfer': 0.3, 'child_interarrival': 7, 'child_consultation': 15, 'child_transfer': 0.2, 'elderly_interarrival': 10, 'elderly_consultation': 30, 'elderly_transfer': 0.5}
Function example (with nested lists)
#' Generate parameter list.
#'
#' @param adult_interarrival Numeric. Time between adult patient arrivals
#' (minutes).
#' @param adult_consultation Numeric. Length of adult patient consultation
#' (minutes).
#' @param adult_transfer Numeric. Adult patient transfer probability (0-1).
#' @param child_interarrival Numeric. Time between child patient arrivals
#' (minutes).
#' @param child_consultation Numeric. Length of child patient consultation
#' (minutes).
#' @param child_transfer Numeric. Child patient transfer probability (0-1).
#' @param elderly_interarrival Numeric. Time between elderly patient arrivals
#' (minutes).
#' @param elderly_consultation Numeric. Length of elderly patient consultation
#' (minutes).
#' @param elderly_transfer Numeric. Elderly patient transfer probability (0-1).
#'
#' @return A named list with three keys ("adult", "child", "elderly"), each
#' containing a list of parameters.
<- function(
create_params adult_interarrival = 5L, adult_consultation = 20L, adult_transfer = 0.3,
child_interarrival = 7L, child_consultation = 15L, child_transfer = 0.2,
elderly_interarrival = 10L, elderly_consultation = 30L,
elderly_transfer = 0.5
) {list(
adult = list(
interarrival_time = adult_interarrival,
consultation_time = adult_consultation,
transfer_prob = adult_transfer
),child = list(
interarrival_time = child_interarrival,
consultation_time = child_consultation,
transfer_prob = child_transfer
),elderly = list(
interarrival_time = elderly_interarrival,
consultation_time = elderly_consultation,
transfer_prob = elderly_transfer
)
)
}
# Example usage
<- create_params()
base_params base_params
$adult
$adult$interarrival_time
[1] 5
$adult$consultation_time
[1] 20
$adult$transfer_prob
[1] 0.3
$child
$child$interarrival_time
[1] 7
$child$consultation_time
[1] 15
$child$transfer_prob
[1] 0.2
$elderly
$elderly$interarrival_time
[1] 10
$elderly$consultation_time
[1] 30
$elderly$transfer_prob
[1] 0.5
R6 class example
#' @title Parameter class.
#'
#' @field adult_interarrival Numeric. Time between adult patient arrivals
#' (minutes).
#' @field adult_consultation Numeric. Length of adult patient consultation
#' (minutes).
#' @field adult_transfer Numeric. Adult patient transfer probability (0-1).
#' @field child_interarrival Numeric. Time between child patient arrivals
#' (minutes).
#' @field child_consultation Numeric. Length of child patient consultation
#' (minutes).
#' @field child_transfer Numeric. Child patient transfer probability (0-1).
#' @field elderly_interarrival Numeric. Time between elderly patient arrivals
#' (minutes).
#' @field elderly_consultation Numeric. Length of elderly patient consultation
#' (minutes).
#' @field elderly_transfer Numeric. Elderly patient transfer probability (0-1).
<- R6Class( # nolint: object_name_linter
Parameters classname = "Parameters",
public = list(
# Adult parameters
adult_interarrival = NULL,
adult_consultation = NULL,
adult_transfer = NULL,
# Child parameters
child_interarrival = NULL,
child_consultation = NULL,
child_transfer = NULL,
# Elderly parameters
elderly_interarrival = NULL,
elderly_consultation = NULL,
elderly_transfer = NULL,
#' @description
#' Initialise Parameters instance.
initialize = function(
adult_interarrival = 5L, adult_consultation = 20L,
adult_transfer = 0.3, child_interarrival = 7L,
child_consultation = 15L, child_transfer = 0.2,
elderly_interarrival = 10L, elderly_consultation = 30L,
elderly_transfer = 0.5
) {# Adult parameters
$adult_interarrival <- adult_interarrival
self$adult_consultation <- adult_consultation
self$adult_transfer <- adult_transfer
self
# Child parameters
$child_interarrival <- child_interarrival
self$child_consultation <- child_consultation
self$child_transfer <- child_transfer
self
# Elderly parameters
$elderly_interarrival <- elderly_interarrival
self$elderly_consultation <- elderly_consultation
self$elderly_transfer <- elderly_transfer
self
},
#' @description
#' Returns parameters as a named list.
#'
#' @return A named list containing all parameter values.
get_params = function() {
# Get all non-function fields
<- ls(self)
all_names <- vapply(
is_not_function
all_names,function(x) !is.function(self[[x]]),
FUN.VALUE = logical(1L)
)<- all_names[is_not_function]
param_names mget(param_names, envir = self)
}
)
)
# Example usage
<- Parameters$new()
base_params $get_params() base_params
$adult_consultation
[1] 20
$adult_interarrival
[1] 5
$adult_transfer
[1] 0.3
$child_consultation
[1] 15
$child_interarrival
[1] 7
$child_transfer
[1] 0.2
$elderly_consultation
[1] 30
$elderly_interarrival
[1] 10
$elderly_transfer
[1] 0.5
Using multiple functions or classes
Alternatively, parameters can be split into logical groups (e.g., patient type, parameter type), with each group having its own function or class. These groups are then combined into a single parameter set.
For simulations with a large number of parameters, this approach simplifies input management. It also enables validation to be performed for each individual function/class.
Function example
def create_arrivals(adult=5, child=7, elderly=10):
"""
Generate dictionary of inter-arrival times (minutes).
Parameters
----------
adult : float
Time between arrivals for adults.
child : float
Time between arrivals for children.
elderly : float
Time between arrivals for elderly people.
Returns
-------
Dictionary of inter-arrival times for each patient type.
"""
return {
"adult": adult,
"child": child,
"elderly": elderly
}
def create_consultations(adult=20, child=15, elderly=30):
"""
Generate dictionary of consultation times (minutes).
Parameters
----------
adult : float
Consultation duration for adults.
child : float
Consultation duration for children.
elderly : float
Consultation duration for elderly people.
Returns
-------
Dictionary of consultation times for each patient type.
"""
return {
"adult": adult,
"child": child,
"elderly": elderly
}
def create_transfers(adult=0.3, child=0.2, elderly=0.5):
"""
Generate dictionary of transfer probabilities.
Parameters
----------
adult : float
Transfer probability for adults.
child : float
Transfer probability for children.
elderly : float
Transfer probability for elderly people.
Returns
-------
Dictionary of transfer probabilities for each patient type.
"""
return {
"adult": adult,
"child": child,
"elderly": elderly
}
def create_parameters(
=None,
arrivals=None,
consultations=None
transfers
):"""
Generate complete parameter dictionary for simulation.
Uses None as default argument, as setting e.g. arrivals=create_arrivals()
is dangerous, since it creates a single dictionary shared by subsequent
calls.
Parameters
----------
arrivals : dict
Dictionary of inter-arrival times (minutes) for each patient type.
consultations : dict
Dictionary of consultation durations (minutes) for each patient type.
transfers : dict
Dictionary of transfer probabilities (0-1) for each patient type.
Returns
-------
Nested dictionary with three keys ("arrivals", "consultations",
"transfers"), each containing a dictionary of parameters.
"""
# Create the individual dictionaries if none provided
if arrivals is None:
= create_arrivals()
arrivals if consultations is None:
= create_consultations()
consultations if transfers is None:
= create_transfers()
transfers # Create dictionary
return {
"arrivals": arrivals,
"consultations": consultations,
"transfers": transfers
}
# Example usage
= create_parameters()
base_params print(base_params)
{'arrivals': {'adult': 5, 'child': 7, 'elderly': 10}, 'consultations': {'adult': 20, 'child': 15, 'elderly': 30}, 'transfers': {'adult': 0.3, 'child': 0.2, 'elderly': 0.5}}
Class example
class Arrivals:
"""
Inter-arrival times (minutes).
"""
def __init__(self, adult=5, child=7, elderly=10):
"""
Initialise Arrivals instance.
Parameters
----------
adult : float
Time between arrivals for adults.
child : float
Time between arrivals for children.
elderly : float
Time between arrivals for elderly people.
"""
self.adult = adult
self.child = child
self.elderly = elderly
class Consultations:
"""
Consultation times (minutes).
"""
def __init__(self, adult=20, child=15, elderly=30):
"""
Initialise Consultations instance.
Parameters
----------
adult : float
Consultation duration for adults.
child : float
Consultation duration for children.
elderly : float
Consultation duration for elderly people.
"""
self.adult = adult
self.child = child
self.elderly = elderly
class Transfers:
"""
Transfer probabilities (0-1).
"""
def __init__(self, adult=0.3, child=0.2, elderly=0.5):
"""
Initialise Transfers instance.
Parameters
----------
adult : float
Transfer probability for adults (0-1).
child : float
Transfer probability for children (0-1).
elderly : float
Transfer probability for elderly people (0-1).
"""
self.adult = adult
self.child = child
self.elderly = elderly
class Parameters():
"""
Complete parameter class.
"""
def __init__(
self,
=Arrivals(),
arrivals=Consultations(),
consultations=Transfers()
transfers
):"""
Initialise Parameters instance.
Parameters
----------
arrivals : dict
Class with inter-arrival times (minutes) for each patient type.
consultations : dict
Class with consultation durations (minutes) for each patient type.
transfers : dict
Class with transfer probabilities (0-1) for each patient type.
"""
self.arrivals = arrivals
self.consultations = consultations
self.transfers = transfers
# Example usage
= Parameters()
base_params print(base_params.arrivals.__dict__)
{'adult': 5, 'child': 7, 'elderly': 10}
print(base_params.consultations.__dict__)
{'adult': 20, 'child': 15, 'elderly': 30}
print(base_params.transfers.__dict__)
{'adult': 0.3, 'child': 0.2, 'elderly': 0.5}
Function example
#' Generate list of inter-arrival times (minutes).
#'
#' @param adult Numeric. Time between adult patient arrivals.
#' @param child Numeric. Time between child patient arrivals.
#' @param elderly Numeric. Time between elderly patient arrivals.
#'
#' @return A named list of inter-arrival times for each patient type.
<- function(adult = 5L, child = 7L, elderly = 10L) {
create_arrivals list(
adult = adult,
child = child,
elderly = elderly
)
}
#' Generate list of consultation times (minutes).
#'
#' @param adult Numeric. Consultation duration for adults.
#' @param child Numeric. Consultation duration for children.
#' @param elderly Numeric. Consultation duration for elderly people.
#'
#' @return A named list of consultation times for each patient type.
<- function(adult = 20L, child = 15L, elderly = 30L) {
create_consultations list(
adult = adult,
child = child,
elderly = elderly
)
}
#' Generate list of transfer probabilities (0-1).
#'
#' @param adult Numeric. Transfer probability for adults.
#' @param child Numeric. Transfer probability for children.
#' @param elderly Numeric. Transfer probability for elderly people.
#'
#' @return A named list of transfer probabilities for each patient type.
<- function(adult = 0.3, child = 0.2, elderly = 0.5) {
create_transfers list(
adult = adult,
child = child,
elderly = elderly
)
}
#' Generate complete parameter list for simulation.
#'
#' @param arrivals List. Named list of inter-arrival times (minutes) for each
#' patient type.
#' @param consultations List. Named list of consultation durations (minutes)
#' for each patient type.
#' @param transfers List. Named list of transfer probabilities (0-1) for each
#' patient type.
#'
#' @return A named list with three keys ("arrivals", "consultations",
#' "transfers"), each containing a list of parameters for each patient type.
<- function(
create_parameters arrivals = create_arrivals(),
consultations = create_consultations(),
transfers = create_transfers()
) {list(
arrivals = arrivals,
consultations = consultations,
transfers = transfers
)
}
# Example usage
<- create_parameters()
base_params base_params
$arrivals
$arrivals$adult
[1] 5
$arrivals$child
[1] 7
$arrivals$elderly
[1] 10
$consultations
$consultations$adult
[1] 20
$consultations$child
[1] 15
$consultations$elderly
[1] 30
$transfers
$transfers$adult
[1] 0.3
$transfers$child
[1] 0.2
$transfers$elderly
[1] 0.5
R6 class example
With modified get_params()
method to extract fields from each component class.
#' @title Arrivals class.
#' @description Inter-arrival times (minutes).
#'
#' @field adult Numeric. Time between adult patient arrivals (minutes).
#' @field child Numeric. Time between child patient arrivals (minutes).
#' @field elderly Numeric. Time between elderly patient arrivals (minutes).
<- R6Class( # nolint: object_name_linter
Arrivals classname = "Arrivals",
public = list(
adult = NULL,
child = NULL,
elderly = NULL,
#' @description
#' Initialise Arrivals instance.
initialize = function(adult = 5L, child = 7L, elderly = 10L) {
$adult <- adult
self$child <- child
self$elderly <- elderly
self
}
)
)
#' @title Consultations class.
#' @description Consultation times (minutes).
#'
#' @field adult Numeric. Length of adult patient consultation (minutes).
#' @field child Numeric. Length of child patient consultation (minutes).
#' @field elderly Numeric. Length of elderly patient consultation (minutes).
<- R6Class( # nolint: object_name_linter
Consultations classname = "Consultations",
public = list(
adult = NULL,
child = NULL,
elderly = NULL,
#' @description
#' Initialise Consultations instance.
initialize = function(adult = 20L, child = 15L, elderly = 30L) {
$adult <- adult
self$child <- child
self$elderly <- elderly
self
}
)
)
#' @title Transfers class.
#' @description Transfer probabilities (0-1).
#'
#' @field adult Numeric. Adult patient transfer probability (0-1).
#' @field child Numeric. Child patient transfer probability (0-1).
#' @field elderly Numeric. Elderly patient transfer probability (0-1).
<- R6Class( # nolint: object_name_linter
Transfers classname = "Transfers",
public = list(
adult = NULL,
child = NULL,
elderly = NULL,
#' @description
#' Initialise Transfers instance.
initialize = function(adult = 0.3, child = 0.2, elderly = 0.5) {
$adult <- adult
self$child <- child
self$elderly <- elderly
self
}
)
)
#' @title Parameters class.
#' @description Complete parameter class for simulation.
#'
#' @field arrivals Arrivals. Instance with inter-arrival times for each patient
#' type.
#' @field consultations Consultations. Instance with consultation durations for
#' each patient type.
#' @field transfers Transfers. Instance with transfer probabilities for each
#' patient type.
<- R6Class( # nolint: object_name_linter
Parameters classname = "Parameters",
public = list(
arrivals = NULL,
consultations = NULL,
transfers = NULL,
#' @description
#' Initialise Parameters instance.
initialize = function(
arrivals = Arrivals$new(),
consultations = Consultations$new(),
transfers = Transfers$new()
) {$arrivals <- arrivals
self$consultations <- consultations
self$transfers <- transfers
self
},
#' @description
#' Returns parameters as a named list.
#'
#' @return A named list containing all parameter values organised by type.
get_params = function() {
# Get all non-function fields from self
<- ls(self)
all_names <- vapply(
is_not_function
all_names,function(x) !is.function(self[[x]]),
FUN.VALUE = logical(1L)
)<- all_names[is_not_function]
instance_names
# Get the instances
<- mget(instance_names, envir = self)
instances
# Apply parameter extraction to each instance using lapply
lapply(instances, function(instance) {
if (inherits(instance, "R6")) {
# Extract non-function fields from R6 instance
<- ls(instance)
all_fields <- vapply(
is_not_function
all_fields,function(x) !is.function(instance[[x]]),
FUN.VALUE = logical(1L)
)<- all_fields[is_not_function]
field_names mget(field_names, envir = instance)
else {
} # Return non-R6 objects as-is
instance
}
})
}
)
)
# Example usage
<- Parameters$new()
base_params $get_params() base_params
$arrivals
$arrivals$adult
[1] 5
$arrivals$child
[1] 7
$arrivals$elderly
[1] 10
$consultations
$consultations$adult
[1] 20
$consultations$child
[1] 15
$consultations$elderly
[1] 30
$transfers
$transfers$adult
[1] 0.3
$transfers$child
[1] 0.2
$transfers$elderly
[1] 0.5
🔍 Explore the example models
🩺 Nurse visit simulation
Click to visit pydesrap_mms repository
Key file | simulation/param.py |
What to look for? | Notice how all parameters are grouped into a single Param class that gets passed to the simulation class. This demonstrates the “dedicated object” approach we recommend. |
Why it matters? | This simulation manages 11 different parameters. Imagine trying to pass all these individually to every function! |
Click to visit rdesrap_mms repository
Key file | R/parameters.R |
What to look for? | Notice how all parameters are grouped into a single parameters function that gets passed to the simulation functions. This demonstrates the “dedicated object” approach we recommend. |
Why it matters? | This simulation manages 11 different parameters. Imagine trying to pass all these individually to every function! |
🧠 Stroke pathway simulation
Not applicable as the stroke example stores parameters in a file.
🧪 Test yourself
If you haven’t already, let’s have a go at managing parameters inside scripts.
Task:
Create a new Jupyter notebook in your project (e.g. in a
notebooks/
directory).Try out at least two different ways of grouping and passing parameters, using examples from above (e.g. dictionary, function, or class).
Create a new Rmarkdown file in your project (e.g. in a
rmarkdown/
directory).Try out at least two different ways of grouping and passing parameters, using examples from above (e.g. list, function, or R6 class).
- For each option, create at least one variant scenario by overriding one or more parameters.