Parameters from file

Learning objectives:

  • Know the advantages of using external parameter files in simulation workflows
  • Learn how to create a parameter file and data dictionary.
  • Understand methods for importing parameters.

Relevant reproducibility guidelines:

  • STARS Reproducibility Recommendations: Avoid hard-coded parameters.
  • NHS Levels of RAP (🥈): Data is handled and output in a Tidy data format.

Required packages:

This should be available from environment setup in the “🧪 Test yourself” section of Environments.

import pandas as pd
import json
from collections import defaultdict
library(jsonlite)
library(R6)

The print_dict function is just a small helper used on this page to make dictionary outputs easier to read.

import json
import itertools


def print_dict(dictionary, max_items_per_level):
    """
    Pretty print the first n items of a dictionary in JSON format.

    Parameters
    ----------
    dictionary : dict
        Dictionary to print.
    max_items_per_level: int or list of int.
        If int, applies limit to all levels. If list, limit for
        [level1, level2, ...]. If the nesting is deeper, the last value is
        used for deeper levels.
    """
    # Ensure max_items_per_level is a list of limits per level
    if isinstance(max_items_per_level, int):
        levels = [max_items_per_level]
    else:
        levels = list(max_items_per_level)

    def _truncate(obj, level=0):
        """
        Recursively copy the dictionary, trimming to the allowed number
        of items at each level.

        Parameters
        ----------
        obj : dict or other
            The object (dict or non-dict) to be truncated.
        level : int
            Current depth in nested structure (0 = top/root).
        """
        # Choose the current level's limit, or last limit if deeper
        max_items = levels[min(level, len(levels)-1)]

        # If obj is a dict, slice its items to the limit
        if isinstance(obj, dict):
            # Use islice for efficiency; only take max_items items
            return {
                k: _truncate(v, level + 1)
                for k, v in itertools.islice(obj.items(), max_items)
            }
        # If obj is not a dict, return as is
        return obj

    # Create a truncated copy and print as pretty JSON
    trimmed_dict = _truncate(dictionary)
    print(json.dumps(trimmed_dict, indent=4, sort_keys=True))

❓ Why use external parameter files?

External parameter files can offer some advantages over storing parameters directly in scripts:

  • Easier for non-programmers to modify parameters.
  • Can easily share your simulation with different parameters versions (e.g. real parameters for internal use, synthetic parameters for external sharing, as discussed in input data management) without editing the code itself.
  • Less hard coding as parameters are completely separate.

In many cases though, storing parameters within a script is commonplace - particularly for simpler models - due to its simplicity (everything in one place, fewer files).

📝 Create parameter file

There are several file types you can use to store parameters, with CSV and JSON being two good options. Both have advantages over Excel files:

  • Open and non-proprietary format.
  • Work well with version control (as they are plain text).
  • Supported by many tools: spreadsheets, text editors, programming languages.
  • Easier to import and parse programmatically than Excel.

CSV example

For CSV files, use a simple tabular structure and follow tidy data principles - each row is an observation, each column a variable. Avoid merged cells or stacked headers, as these make data processing difficult.

# Import and preview parameter file
csv_content = pd.read_csv("parameters_file_resources/example_parameters.csv")
csv_content.head()
  patient        metric  value
0   adult  interarrival    5.0
1   adult  consultation   20.0
2   adult      transfer    0.3
3   child  interarrival    7.0
4   child  consultation   15.0
# Import and view the parameter table
read.csv(file.path("parameters_file_resources", "example_parameters.csv"))
  patient       metric value
1   adult interarrival   5.0
2   adult consultation  20.0
3   adult     transfer   0.3
4   child interarrival   7.0
5   child consultation  15.0
6   child     transfer   0.2
7 elderly interarrival  10.0
8 elderly consultation  30.0
9 elderly     transfer   0.5

JSON example

For JSON files, data are stored in hierachical / nested structures. Keep the structure clear and consistent.

# Import parameters
# pylint:disable=invalid-name
json_file = "parameters_file_resources/example_parameters.json"
with open(json_file, "r", encoding="utf-8") as f:
    json_content = json.load(f)

# Preview the dictionary
print_dict(json_content, [1, 3])
{
    "simulation_parameters": {
        "adult_consultation": {
            "class_name": "Exponential",
            "params": {
                "mean": 20.0
            }
        },
        "adult_interarrival": {
            "class_name": "Exponential",
            "params": {
                "mean": 5.0
            }
        },
        "adult_transfer": {
            "class_name": "Exponential",
            "params": {
                "mean": 0.3
            }
        }
    }
}
json_file <- fromJSON(
  file.path("parameters_file_resources", "example_parameters.json")
)
str(json_file)
List of 1
 $ simulation_parameters:List of 9
  ..$ adult_interarrival  :List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 5
  ..$ adult_consultation  :List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 20
  ..$ adult_transfer      :List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 0.3
  ..$ child_interarrival  :List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 7
  ..$ child_consultation  :List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 15
  ..$ child_transfer      :List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 0.2
  ..$ elderly_interarrival:List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 10
  ..$ elderly_consultation:List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 30
  ..$ elderly_transfer    :List of 2
  .. ..$ class_name: chr "Exponential"
  .. ..$ params    :List of 1
  .. .. ..$ mean: num 0.5

📖 Create data dictionary

As mentioned in the checklist for managing parameter data on the input data management page, you should include a data dictionary or documentation describing each parameter. This documentation should explain:

  • The parameter’s meaning
  • The parameter’s units
  • Any abbreviations or codes used.

The examples below were created using markdown (converted to PDF using pandoc), but you can use any suitable format (e.g. CSV, YAML, etc.) - as long as it is clear, consistent and accessible.

#| echo: false
import subprocess

subprocess.run([
    "pandoc",
    "parameters_file_resources/_csv_data_dictionary.md",
    "-o", "parameters_file_resources/_csv_data_dictionary.pdf"
], check=False)
subprocess.run([
    "pandoc",
    "parameters_file_resources/_json_data_dictionary.md",
    "-o", "parameters_file_resources/_json_data_dictionary.pdf"
], check=False)

CSV data dictionary example

JSON data dictionary example

📥 Methods for importing parameters

As explained in the parameters from script page, there are two key principles for managing parameters effectively in simulation models:

  1. Group parameters into a dedicated object.
  2. Pass this object explicitly to your model.

If you haven’t already, see the parameters from script page for more detail on why this explicit grouping and passing is important.

On that page, we discussed three implementation options: dictionary, function, and class. When defining parameters within scripts, we recommended using a function or class. But what about when you’re importing parameters from a file?

On that page, we discussed three implementation options: list, function, and R6 class. When defining parameters within scripts, we recommended using a function or class. But what about when you’re importing parameters from a file?

Implementation options for file-based parameters

You still have two main choices: a function or a class.

Direct dictionary import (loading the file straight into a dictionary) is rarely practical. It only works if the file structure exactly matches your model and no extra parameters are needed.

Function (returns a dictionary)

You still have two main choices: a function or a R6 class.

Direct list import (loading the file straight into a named list) is rarely practical. It only works if the file structure exactly matches your model and no extra parameters are needed.

Function (returns a named list)

  • ✅ Handles any processing needed to go from file to parameter structure.

  • ✅ Lets you merge file-based and script-based parameters into one object.

  • ✅ Keeps all import/processing logic together for easy reuse and debugging.

  • ❌ Dictionaries can be modified after creation, so validation must be performed in the model functions/classes (ensuring the final set of parameters is being checked). This means that errors are only caught later, rather than immediately at the point of import/change.

Class

  • ❌ Named list can be modified after creation, so validation must be performed in the model functions/classes (ensuring the final set of parameters is being checked). This means that errors are only caught later, rather than immediately at the point of import/change.

R6 class

  • ✅ Includes all function benefits: processing, merging, in one place.

  • ✅ Supports validation at creation or update, catching errors earlier.

For more context on validation, see the parameter validation page.

Using a function

Nested or flat dictionary

A function loads parameters and returns them as a dictionary. You can choose between a nested or flat structure, depending on complexity.

A nested dictionary is good for grouped parameters, like distributions with multiple values (mean, sd, etc.), or if you want to organise by category like patient type.

params = {
    "adult": {"interarrival": 5, "service_time": 10},
    "child": {"interarrival": 3, "service_time": 7},
    "distribution": {"mean": 0, "sd": 1}
}

print(params["adult"]["interarrival"])
5

A flat dictionary can be simpler to use if everything fits in one layer.

params = {
    "adult_interarrival": 5,
    "adult_service_time": 10,
    "child_interarrival": 3,
    "child_service_time": 7
}

print(params["adult_interarrival"])
5

Nested or flat list

A function loads parameters and returns them as a named list. You can choose between a nested or flat structure, depending on complexity.

A nested list is good for grouped parameters, like distributions with multiple values (mean, sd, etc.), or if you want to organise by category like patient type.

params <- list(
  adult = list(interarrival = 5L, service_time = 10L),
  child = list(interarrival = 3L, service_time = 7L),
  distribution = list(mean = 0L, sd = 1L)
)

params$adult$interarrival
[1] 5

A flat list can be simpler to use if everything fits in one layer.

params <- list(
  adult_interarrival = 5L,
  adult_service_time = 10L,
  child_interarrival = 3L,
  child_service_time = 7L
)

params$adult_interarrival
[1] 5

JSON example

This uses a nested format, as the JSON was already in that format:

def create_parameters(json_file, number_of_runs=10):
    """
    Create parameter dictionary by combining parameters from JSON with those
    defined in this function.

    Parameters
    ----------
    json_file : str
        Path to JSON file containing simulation parameters.
    number_of_runs : int, optional
        Number of simulation runs (default is 10).

    Returns
    -------
    param_dict : dict
        Nested dictionary of simulation parameters.
    """
    with open(json_file, "r", encoding="utf-8") as f:
        json_content = json.load(f)
    param_dict = json_content["simulation_parameters"]
    param_dict["number_of_runs"] = number_of_runs
    return param_dict


param = create_parameters("parameters_file_resources/example_parameters.json")
print_dict(param, 4)
{
    "adult_consultation": {
        "class_name": "Exponential",
        "params": {
            "mean": 20.0
        }
    },
    "adult_interarrival": {
        "class_name": "Exponential",
        "params": {
            "mean": 5.0
        }
    },
    "adult_transfer": {
        "class_name": "Exponential",
        "params": {
            "mean": 0.3
        }
    },
    "child_interarrival": {
        "class_name": "Exponential",
        "params": {
            "mean": 7.0
        }
    }
}
#' Create parameter list by combining parameters from JSON with those
#' defined in this function.
#'
#' @param json_file Character, path to JSON file containing simulation
#' parameters.
#' @param number_of_runs Integer, number of simulation runs (default is 10).
#'
#' @return Named list of simulation parameters (nested).
#' @export
create_parameters <- function(json_file, number_of_runs = 10L) {
  json_content <- jsonlite::fromJSON(json_file)
  param_list <- json_content$simulation_parameters
  param_list$number_of_runs <- number_of_runs
  return(param_list)
}

# Example usage:
param <- create_parameters(
  file.path("parameters_file_resources", "example_parameters.json")
)
print(param[1L:4L])
$adult_interarrival
$adult_interarrival$class_name
[1] "Exponential"

$adult_interarrival$params
$adult_interarrival$params$mean
[1] 5



$adult_consultation
$adult_consultation$class_name
[1] "Exponential"

$adult_consultation$params
$adult_consultation$params$mean
[1] 20



$adult_transfer
$adult_transfer$class_name
[1] "Exponential"

$adult_transfer$params
$adult_transfer$params$mean
[1] 0.3



$child_interarrival
$child_interarrival$class_name
[1] "Exponential"

$child_interarrival$params
$child_interarrival$params$mean
[1] 7

CSV example (flat)

def create_parameters(csv_file, number_of_runs=10):
    """
    Create flat parameter dictionary by combining parameters from CSV with
    those defined in this function.

    Parameters
    ----------
    csv_file : str
        Path to CSV file containing simulation parameters.
    number_of_runs : int, optional
        Number of simulation runs (default is 10).

    Returns
    -------
    param_dict : dict
        Flat dictionary of simulation parameters.
    """
    csv_content = pd.read_csv(csv_file)
    param_dict = {
        f"{row.patient}_{row.metric}": float(row.value)
        for row in csv_content.itertuples(index=False)
    }
    param_dict["number_of_runs"] = number_of_runs
    return param_dict


param = create_parameters("parameters_file_resources/example_parameters.csv")
print_dict(param, 4)
{
    "adult_consultation": 20.0,
    "adult_interarrival": 5.0,
    "adult_transfer": 0.3,
    "child_interarrival": 7.0
}
#' Create flat parameter list by combining parameters from CSV with those
#' defined in this function.
#'
#' @param csv_file Character, path to CSV file containing simulation
#' parameters.
#' @param number_of_runs Integer, number of simulation runs (default is 10).
#'
#' @return Named list of simulation parameters (flat).
#' @export
create_parameters <- function(csv_file, number_of_runs = 10L) {
  csv_content <- read.csv(csv_file)
  lst <- as.list(csv_content$value)
  names(lst) <- paste(csv_content$patient, csv_content$metric, sep = "_")
  lst$number_of_runs <- number_of_runs
  return(lst)
}

# Example usage:
param <- create_parameters(
  file.path("parameters_file_resources", "example_parameters.csv")
)
print(param[1L:4L])
$adult_interarrival
[1] 5

$adult_consultation
[1] 20

$adult_transfer
[1] 0.3

$child_interarrival
[1] 7

CSV example (nested)

def create_parameters(csv_file, number_of_runs=10):
    """
    Create nested parameter dictionary by combining parameters from CSV with
    those defined in this function.

    Parameters
    ----------
    csv_file : str
        Path to CSV file containing simulation parameters.
    number_of_runs : int, optional
        Number of simulation runs (default is 10).

    Returns
    -------
    dict
        Nested dictionary of simulation parameters.
    """
    csv_content = pd.read_csv(csv_file)
    param_dict = defaultdict(dict)
    for row in csv_content.itertuples(index=False):
        param_dict[row.patient][row.metric] = float(row.value)
    param_dict["number_of_runs"] = number_of_runs
    return dict(param_dict)


param = create_parameters("parameters_file_resources/example_parameters.csv")
print_dict(param, 4)
{
    "adult": {
        "consultation": 20.0,
        "interarrival": 5.0,
        "transfer": 0.3
    },
    "child": {
        "consultation": 15.0,
        "interarrival": 7.0,
        "transfer": 0.2
    },
    "elderly": {
        "consultation": 30.0,
        "interarrival": 10.0,
        "transfer": 0.5
    },
    "number_of_runs": 10
}
#' Create nested parameter list by combining parameters from CSV with those
#' defined in this function.
#'
#' @param csv_file Character, path to CSV file containing simulation
#' parameters.
#' @param number_of_runs Integer, number of simulation runs (default is 10).
#'
#' @return Named list of simulation parameters (nested).
#' @export
create_parameters <- function(csv_file, number_of_runs = 10L) {
  csv_content <- read.csv(csv_file)
  lst <- lapply(split(csv_content, csv_content$patient), function(subset) {
    setNames(as.list(subset$value), subset$metric)
  })
  lst$number_of_runs <- number_of_runs
  return(lst)
}

# Example usage:
param <- create_parameters(
  file.path("parameters_file_resources", "example_parameters.csv")
)
print(param[1L:4L])
$adult
$adult$interarrival
[1] 5

$adult$consultation
[1] 20

$adult$transfer
[1] 0.3


$child
$child$interarrival
[1] 7

$child$consultation
[1] 15

$child$transfer
[1] 0.2


$elderly
$elderly$interarrival
[1] 10

$elderly$consultation
[1] 30

$elderly$transfer
[1] 0.5


$number_of_runs
[1] 10

Using a class

In the class, parameters can again be nested or flat, but this time they are attributes of the class instance. Those attributes can themselves them be simple values or dictionaries.

As described above, classes also allow you to “lock down” the allowed parameters and add validation checks during initialisation and any changes. For examples of how to add validation when using a class, see the parameter validation page.

JSON example

class CreateParameters:
    """
    Store simulation parameters, combining those loaded from a JSON file with
    values defined in the class.
    """
    def __init__(self, json_file, number_of_runs=10):
        """
        Parameters
        ----------
        json_file : str
            Path to JSON file containing simulation parameters.
        number_of_runs : int, optional
            Number of simulation runs (default is 10).
        """
        # Import parameters from JSON file
        with open(json_file, "r", encoding="utf-8") as f:
            json_content = json.load(f)
        params = json_content["simulation_parameters"]

        # Assign file-based parameters as attributes
        for key, value in params.items():
            setattr(self, key, value)

        # Add class-defined parameters
        self.number_of_runs = number_of_runs


param = CreateParameters("parameters_file_resources/example_parameters.json")
print_dict(param.__dict__, 4)
{
    "adult_consultation": {
        "class_name": "Exponential",
        "params": {
            "mean": 20.0
        }
    },
    "adult_interarrival": {
        "class_name": "Exponential",
        "params": {
            "mean": 5.0
        }
    },
    "adult_transfer": {
        "class_name": "Exponential",
        "params": {
            "mean": 0.3
        }
    },
    "child_interarrival": {
        "class_name": "Exponential",
        "params": {
            "mean": 7.0
        }
    }
}

CSV example (flat)

class CreateParameters:
    """
    Store simulation parameters, combining those loaded from a CSV file with
    values defined in the class.
    """
    def __init__(self, csv_file, number_of_runs=10):
        """
        Parameters
        ----------
        csv_file : str
            Path to CSV file containing simulation parameters.
        number_of_runs : int, optional
            Number of simulation runs (default is 10).
        """
        # Import parameters from CSV file
        csv_content = pd.read_csv(csv_file)

        # Assign CSV parameters as attributes (flat style)
        for row in csv_content.itertuples(index=False):
            param_key = f"{row.patient}_{row.metric}"
            setattr(self, param_key, float(row.value))

        # Add class-defined parameters
        self.number_of_runs = number_of_runs


param = CreateParameters("parameters_file_resources/example_parameters.csv")
print_dict(param.__dict__, 4)
{
    "adult_consultation": 20.0,
    "adult_interarrival": 5.0,
    "adult_transfer": 0.3,
    "child_interarrival": 7.0
}

CSV example (nested)

class CreateParameters:
    """
    Store simulation parameters, combining those loaded from a CSV file with
    values defined in the class.
    """
    def __init__(self, csv_file, number_of_runs=10):
        """
        Parameters
        ----------
        csv_file : str
            Path to CSV file containing simulation parameters.
        number_of_runs : int, optional
            Number of simulation runs (default is 10).
        """
        # Import parameters from CSV file and structure as nested dictionary
        csv_content = pd.read_csv(csv_file)
        param_dict = defaultdict(dict)
        for row in csv_content.itertuples(index=False):
            param_dict[row.patient][row.metric] = float(row.value)
        param_dict["number_of_runs"] = number_of_runs

        # Assign file-based parameters as attributes
        for key, value in param_dict.items():
            setattr(self, key, value)

        # Add class-defined parameters
        self.number_of_runs = number_of_runs


param = CreateParameters("parameters_file_resources/example_parameters.csv")
print_dict(param.__dict__, 4)
{
    "adult": {
        "consultation": 20.0,
        "interarrival": 5.0,
        "transfer": 0.3
    },
    "child": {
        "consultation": 15.0,
        "interarrival": 7.0,
        "transfer": 0.2
    },
    "elderly": {
        "consultation": 30.0,
        "interarrival": 10.0,
        "transfer": 0.5
    },
    "number_of_runs": 10
}

Alternative set-up: class with explicit attributes

The previous examples dynamically set attributes based on whatever is present in your input file. Here, we show another approach - explicitly defining each attribute. This provides clarity and structure, although is more verbose.

You’ll notice that this class is similar to those in parameters from script - except that it has no default values.

class Parameters:
    """
    Container for simulation parameters across patient demographics.
    """
    def __init__(
        self,
        adult_interarrival, adult_consultation, adult_transfer,
        child_interarrival, child_consultation, child_transfer,
        elderly_interarrival, elderly_consultation, elderly_transfer
    ):
        """
        Initialise Parameters instance.

        Parameters
        ----------
        adult_interarrival : float
            Time between adult patient arrivals (e.g., in minutes or hours).
        adult_consultation : float
            Duration of consultation time for adult patients.
        adult_transfer : float
            Time required to transfer adult patients between stages.
        child_interarrival : float
            Time between child patient arrivals (e.g., in minutes or hours).
        child_consultation : float
            Duration of consultation time for child patients.
        child_transfer : float
            Time required to transfer child patients between stages.
        elderly_interarrival : float
            Time between elderly patient arrivals (e.g., in minutes or hours).
        elderly_consultation : float
            Duration of consultation time for elderly patients.
        elderly_transfer : float
            Time required to transfer elderly patients between stages.
        """
        # 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

Use a helper function that loads the data and matches it to the class attributes. For example, from a CSV file:

def setup_param_from_csv(csv_path):
    """
    Create a Parameters instance using parameter values loaded from a CSV file.

    Parameters
    ----------
    csv_path : str
        Path to CSV file containing the parameters. Should have columns
        "patient", "metric", and "value".

    Returns
    -------
    Parameters
        Instance of Parameters initialised with parameters from the CSV file.
    """

    # Import the parameters
    param_file = pd.read_csv(csv_path)

    # Create name-value mappings
    values = {
      f"{row.patient}_{row.metric}": row.value
      for row in param_file.itertuples()
    }

    # Pass these values to the Parameters class
    return Parameters(**values)


# Import parameters
params = setup_param_from_csv(
    "parameters_file_resources/example_parameters.csv"
)

# View object
print_dict(params.__dict__, 4)
{
    "adult_consultation": 20.0,
    "adult_interarrival": 5.0,
    "adult_transfer": 0.3,
    "child_interarrival": 7.0
}

This requires each parameter to be a field, rather than storing all parameters in a list within the class (as explained on the parameter validation page).

In R, we cannot dynamically add these fields when initialising - i.e. we have to explicitly specify each attribute.

If you didn’t want to do that (e.g. for large numbers of parameters), it would be possible to store the parameters as a named list within the class, but the list items themselves would still be modifiable, so this has no advantages over just using a function - for either, validation of the parameters present would need to be done within the model function/class.

You’ll notice that this class is similar to those in parameters from script - except that it has no default values.

#' @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).

Parameters <- R6Class( # nolint: object_name_linter
  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, adult_consultation, adult_transfer,
      child_interarrival, child_consultation, child_transfer,
      elderly_interarrival, elderly_consultation, elderly_transfer
    ) {
      # 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
    },

    #' @description
    #' Returns parameters as a named list.
    #'
    #' @return A named list containing all parameter values.

    get_params = function() {
      # Get all non-function fields
      all_names <- ls(self)
      is_not_function <- vapply(
        all_names,
        function(x) !is.function(self[[x]]),
        FUN.VALUE = logical(1L)
      )
      param_names <- all_names[is_not_function]
      mget(param_names, envir = self)
    }
  )
)

Use a helper function that loads the data and matches it to the class attributes. For example, from a CSV file:

#' Create parameter list from CSV file
#'
#' @param csv_path Character.  Path to CSV file with columns:
#'   "patient", "metric", "value".
#'
#' @return An instance of `Parameters` R6 class.

setup_param_from_csv <- function(csv_path) {

  # Import the parameters
  param_file <- read.csv(csv_path, stringsAsFactors = FALSE)

  # Create named vector (e.g. "adult_interarrival" = 5)
  values <- setNames(
    param_file$value,
    paste(param_file$patient, param_file$metric, sep = "_")
  )

  # Instantiate Parameters with values
  do.call(Parameters$new, as.list(values))
}


# Import parameters
params <- setup_param_from_csv(
  file.path("parameters_file_resources", "example_parameters.csv")
)

# View object
params$get_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

Or, from a JSON file:

#' Create parameter list from JSON file
#'
#' @param json_path Character. Path to JSON file containing the parameters.
#'
#' @return A named list containing all simulation parameters.

setup_param_from_json <- function(json_path) {

  # Import the JSON parameters
  json_file <- jsonlite::fromJSON(json_path)

  # Extract the simulation_parameters list
  sim_params <- json_file$simulation_parameters

  # Pull out the mean values from the nested structure
  values <- lapply(sim_params, function(x) x$params$mean)

  # Pass them into Parameters (arguments matched by name)
  do.call(Parameters$new, values)
}


# Import parameters
params <- setup_param_from_json(
  file.path("parameters_file_resources", "example_parameters.json")
)

# View object
params$get_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

🔍 Explore the example models

🩺 Nurse visit simulation

Not applicable as the nurse visit example stores parameters in a script.

🧠 Stroke pathway simulation

GitHub Click to visit pydesrap_stroke repository

Key files simulation/parameters.py
inputs/parameters.json
inputs/data_dictionary.md
What to look for? See how model parameters are stored in a structured JSON file and documented with a data dictionary. The Python code imports these parameters and stores them as an attribute in the Param class, while a few parameters (like warm-up period) remain defined in the script.
Why it matters? This model has 50+ parameters across multiple patient types and care settings. By keeping these in a separate file rather than in the script, it significantly reduces the amount of code for this project.

GitHub Click to visit rdesrap_stroke repository

Key file R/parameters.R
inst/extdata/parameters.json
inst/extdata/data_dictionary.md
What to look for? See how model parameters are stored in a structured JSON file and documented with a data dictionary. The R code imports these parameters and stores them within the named list returned by the function. A few parameters (like warm-up period) remain defined in the script.
Why it matters? This model has 50+ parameters across multiple patient types and care settings. By keeping these in a separate file rather than in the script, it significantly reduces the amount of code for this project.

🧪 Test yourself

If you haven’t already, now’s the time to practice working with external parameter files.

Task:

  • Download the example CSV and JSON parameter files from this page - for example, saving them into a data/ folder in your repository.
  • Create a new Jupyter notebook (e.g. in a notebooks/ directory).

  • In your notebook, try out at least two different parameter loading approaches from the examples above (e.g. flat vs nested structure, function vs class, CSV v.s. JSON).

  • Create a new Rmarkdown file (e.g. in a rmarkdown/ directory).

  • In your Rmarkdown file, try out at least two different parameter loading approaches from the examples above (e.g. flat vs nested structure, function vs class, CSV v.s. JSON).

  • Print or inspect the loaded parameters for each approach, and briefly note any differences or advantages you notice.
Hint
  • If you encounter errors, check the file path, the folder structure, and the filename extensions.