import itertools
from IPython.display import HTML
from itables import to_html_datatable
import numpy as np
import pandas as pd
import scipy.stats as st
import simpy
from sim_tools.distributions import ExponentialScenario and sensitivity analysis
Learning objectives:
- Recognise the importance of sharing your scenario and sensitivity analysis code.
- Learn how to run a combination of scenarios.
- Understand differences between scenario and sensitivity analysis.
Relevant reproducibility guidelines:
- STARS Reproducibility Recommendations (⭐): Provide code for all scenarios and sensitivity analyses.
Pre-reading:
This page continues on from: Parallel processing.
Entity generation → Entity processing → Initialisation bias → Performance measures → Replications → Parallel processing → Scenario and sensitivity analysis
Required packages:
These should be available from environment setup in the “Test yourself” section of Environments.
library(dplyr)
library(kableExtra)
library(knitr)
library(simmer)
library(tidyr)Running and sharing scenarios
When creating a DES, you won’t just run one model - you run scenarios, which are different parameter set-ups. These are used to test how outcomes change under varying conditions.
When running scenarios, there are two important things to remember:
1. Run scenarios programmatically.
Don’t duplicate whole scripts just to test different set-ups. The idea is to build the model with functions and classes so you only need to change the parameters and re-run. That’s why we guide you through building the model this way in this book.
2. Share your scenario code.
You should share code and data for every scenario - not just the base case.
In Heather et al. (2025), six out of seven studies with scenarios lacked scenario code. This made it challenging and time-consuming to reproduce their results, as descriptions of the scenarios in the articles were often ambiguous and key parameters unclear or missing.
= 18
Simple scenarios
This code runs the simulation for each value of number_of_doctors from 3 to 6.
The returned dataframe contains the results from each of the five runs from the four scenarios.
results = []
# Loop through doctor counts 3-6, running the simulation
for i in range(3, 7):
param = Parameters(number_of_doctors=i)
runner = Runner(param=param)
result = runner.run_reps()["run"]
# Add scenario information to the results, then save to list
result["number_of_doctors"] = i
results.append(result)
# Combine results into a single dataframe
scenario_results = pd.concat(results, ignore_index=True)
# Show as interactive table
HTML(to_html_datatable(scenario_results))| Loading ITables v2.5.2 from the internet... (need help?) |
results <- list()
# Loop through doctor counts 3-6, running the simulation
for (i in 3L:6L) {
param <- create_params(number_of_doctors = i)
result <- runner(param = param)[["run_results"]]
# Add scenario information to the results, then save to list
result["number_of_doctors"] <- i
results[[length(results) + 1L]] <- result
}
# Combine results into a single dataframe
scenario_results <- do.call(rbind, results)
# Show as interactive table
kable(scenario_results) |> scroll_box(height = "400px")| replication | arrivals | mean_wait_time_doctor | mean_time_with_doctor | utilisation_doctor | mean_queue_length_doctor | mean_time_in_system | mean_patients_in_system | number_of_doctors |
|---|---|---|---|---|---|---|---|---|
| 1 | 9 | 1.1561321 | 12.600360 | 0.7276328 | 0.0867587 | 6.972457 | 2.3250216 | 3 |
| 2 | 10 | 0.2185845 | 15.462225 | 0.7846974 | 0.3030654 | 7.019465 | 2.3551596 | 3 |
| 3 | 8 | 1.6612341 | 11.704709 | 0.8021880 | 0.2856074 | 10.033307 | 1.7861155 | 3 |
| 4 | 4 | 1.3862671 | 8.268402 | 0.5661071 | 0.9366037 | 8.714268 | 0.8466787 | 3 |
| 5 | 7 | 1.1383003 | 8.211288 | 0.8055934 | 0.1965982 | 8.159880 | 1.5215678 | 3 |
| 1 | 9 | 0.0000000 | 14.361497 | 0.5548891 | 0.0000000 | 5.909446 | 2.0617147 | 4 |
| 2 | 8 | 0.0000000 | 10.902873 | 0.5048249 | 0.0000000 | 5.040122 | 1.5551330 | 4 |
| 3 | 8 | 0.1002196 | 12.491221 | 0.6609534 | 0.0000000 | 8.233837 | 1.6932483 | 4 |
| 4 | 5 | 0.0000000 | 7.220270 | 0.3307908 | 0.0000000 | 8.605687 | 0.8960278 | 4 |
| 5 | 5 | 0.0000000 | 7.764737 | 0.2861765 | 0.0000000 | 7.764737 | 1.0613887 | 4 |
| 1 | 9 | 0.0000000 | 14.361497 | 0.4439113 | 0.0000000 | 5.909446 | 2.0617147 | 5 |
| 2 | 8 | 0.0000000 | 10.902873 | 0.4038599 | 0.0000000 | 5.040122 | 1.5551330 | 5 |
| 3 | 8 | 0.0000000 | 13.200172 | 0.5337198 | 0.0000000 | 8.233837 | 1.6932483 | 5 |
| 4 | 5 | 0.0000000 | 7.220270 | 0.2646327 | 0.0000000 | 8.605687 | 0.8960278 | 5 |
| 5 | 5 | 0.0000000 | 7.764737 | 0.2289412 | 0.0000000 | 7.764737 | 1.0613887 | 5 |
| 1 | 9 | 0.0000000 | 14.361497 | 0.3699261 | 0.0000000 | 5.909446 | 2.0617147 | 6 |
| 2 | 8 | 0.0000000 | 10.902873 | 0.3365499 | 0.0000000 | 5.040122 | 1.5551330 | 6 |
| 3 | 8 | 0.0000000 | 13.200172 | 0.4447665 | 0.0000000 | 8.233837 | 1.6932483 | 6 |
| 4 | 5 | 0.0000000 | 7.220270 | 0.2205272 | 0.0000000 | 8.605687 | 0.8960278 | 6 |
| 5 | 5 | 0.0000000 | 7.764737 | 0.1907843 | 0.0000000 | 7.764737 | 1.0613887 | 6 |
Running several scenario combinations
If you are running several scenarios, it can be useful to define a helper function like the one shown below. This example is adapted from:
“HSMA - little book of DES” from Sammi Rosser, Dan Chalk and Amy Heather 2025 (MIT Licence).
def run_scenarios(scenarios, param_factory=None):
"""
Execute a set of scenarios and return the results from each run.
Parameters
----------
scenarios : dict
Dictionary where key is name of parameter and value is a list with
different values to run in scenarios.
param_factory : callable or None, optional
A callable that returns a new Parameters object for each scenario run.
This can be a class (e.g., `Parameters`) or a factory function/lambda
with preset arguments (e.g., `lambda: Parameters(number_of_runs=4)`).
If not provided, defaults to using `Parameters()` with no arguments.
Returns
-------
pandas.DataFrame
DataFrame with results from each run of each scenario.
Notes
-----
Function adapted from Rosser, Chalk and Heather 2025.
"""
# If none provided, use Parameters
if param_factory is None:
param_factory = Parameters
# Find every possible permutation of the scenarios
all_scenarios_tuples = list(itertools.product(*scenarios.values()))
# Convert back into dictionaries
all_scenarios_dicts = [
dict(zip(scenarios.keys(), p)) for p in all_scenarios_tuples
]
# Preview some of the scenarios
print(f"There are {len(all_scenarios_dicts)} scenarios. Running:")
# Run the scenarios...
results = []
for index, scenario_to_run in enumerate(all_scenarios_dicts):
print(scenario_to_run)
# Create fresh instance of parameter class for each scenario
param = param_factory()
# Update parameter list with the scenario parameters
param.scenario_name = index
for key in scenario_to_run:
setattr(param, key, scenario_to_run[key])
# Perform replications
scenario_exp = Runner(param)
scenario_res = scenario_exp.run_reps()["run"]
# Add scenario number and values to the results dataframe
scenario_res["scenario"] = index
for key in scenario_to_run:
scenario_res[key] = scenario_to_run[key]
# Add results from scenario to list
results.append(scenario_res)
return pd.concat(results)If you are running several scenarios, it can be useful to define a helper function like the one shown below.
#' Run a set of scenarios
#'
#' @param scenarios List where key is name of parameter and value is a list of
#' different values to run in scenarios
#' @param base_list List of parameters to use as base for scenarios, which can
#' be partial (as will input to create_params() function).
#' @param verbose Boolean, whether to print messages about scenarios as run.
#'
#' @return Tibble with results from each replication for each scenario.
#' @export
run_scenarios <- function(scenarios, base_list, verbose = TRUE) {
# Generate all permutations of the scenarios
all_scenarios <- expand.grid(scenarios)
# Preview the number of scenarios
if (isTRUE(verbose)) {
message(sprintf("There are %d scenarios.", nrow(all_scenarios)))
message("Base parameters:")
print(base_list)
}
results <- list()
# Iterate through each scenario
for (index in seq_len(nrow(all_scenarios))) {
# Filter to one of the scenarios
scenario_to_run <- all_scenarios[index, , drop = FALSE]
# Print the scenario parameters
formatted_scenario <- toString(
paste0(names(scenario_to_run), " = ", scenario_to_run)
)
# Print the scenario currently running
if (isTRUE(verbose)) {
message("Scenario: ", formatted_scenario)
}
# Create parameter list with scenario-specific values
s_args <- c(scenario_to_run, list(scenario_name = index))
# Create instance of parameter class with specified base parameters
s_param <- do.call(create_params, base_list)
# Update parameter list with the scenario parameters
for (name in names(s_args)) {
s_param[[name]] <- s_args[[name]]
}
# Run replications for the current scenario and get processed results
scenario_result <- runner(s_param)[["run_results"]]
# Append scenario parameters to the results
scenario_result[["scenario"]] <- index
for (key in names(scenario_to_run)) {
scenario_result[[key]] <- scenario_to_run[[key]]
}
# Append to results list
results[[index]] <- scenario_result
}
do.call(rbind, results)
}The function generates all possible scenario combinations from the values supplied in scenarios.
It then iterates through these, creating a fresh set of parameters for each scenario run. This ensures every scenario gets an independent set of parameters and avoids carrying state from one scenario to another.
The replication results from each scenario are saved in a list and finally combined into a single table.
Let’s run it!
# Run scenarios
scenario_results = run_scenarios(
scenarios={"interarrival_time": [4, 5, 6, 7, 8],
"number_of_doctors": [3, 4, 5]},
param_factory=Parameters
)There are 15 scenarios. Running:
{'interarrival_time': 4, 'number_of_doctors': 3}
{'interarrival_time': 4, 'number_of_doctors': 4}
{'interarrival_time': 4, 'number_of_doctors': 5}
{'interarrival_time': 5, 'number_of_doctors': 3}
{'interarrival_time': 5, 'number_of_doctors': 4}
{'interarrival_time': 5, 'number_of_doctors': 5}
{'interarrival_time': 6, 'number_of_doctors': 3}
{'interarrival_time': 6, 'number_of_doctors': 4}
{'interarrival_time': 6, 'number_of_doctors': 5}
{'interarrival_time': 7, 'number_of_doctors': 3}
{'interarrival_time': 7, 'number_of_doctors': 4}
{'interarrival_time': 7, 'number_of_doctors': 5}
{'interarrival_time': 8, 'number_of_doctors': 3}
{'interarrival_time': 8, 'number_of_doctors': 4}
{'interarrival_time': 8, 'number_of_doctors': 5}
# Save to CSV
scenario_results.to_csv(
"scenarios_resources/python_scenario_results.csv", index=False
)
# View scenario results
HTML(to_html_datatable(scenario_results.head(200)))| Loading ITables v2.5.2 from the internet... (need help?) |
# Run scenarios
scenarios <- list(
interarrival_time = c(4L, 5L, 6L, 7L, 8L),
number_of_doctors = c(3L, 4L, 5L)
)
scenario_results <- run_scenarios(
scenarios = scenarios, base_list = create_params()
)There are 15 scenarios.
Base parameters:
$interarrival_time
[1] 5
$consultation_time
[1] 10
$number_of_doctors
[1] 3
$warm_up_period
[1] 30
$data_collection_period
[1] 40
$number_of_runs
[1] 5
$verbose
[1] FALSE
Scenario: interarrival_time = 4, number_of_doctors = 3
Scenario: interarrival_time = 5, number_of_doctors = 3
Scenario: interarrival_time = 6, number_of_doctors = 3
Scenario: interarrival_time = 7, number_of_doctors = 3
Scenario: interarrival_time = 8, number_of_doctors = 3
Scenario: interarrival_time = 4, number_of_doctors = 4
Scenario: interarrival_time = 5, number_of_doctors = 4
Scenario: interarrival_time = 6, number_of_doctors = 4
Scenario: interarrival_time = 7, number_of_doctors = 4
Scenario: interarrival_time = 8, number_of_doctors = 4
Scenario: interarrival_time = 4, number_of_doctors = 5
Scenario: interarrival_time = 5, number_of_doctors = 5
Scenario: interarrival_time = 6, number_of_doctors = 5
Scenario: interarrival_time = 7, number_of_doctors = 5
Scenario: interarrival_time = 8, number_of_doctors = 5
# Save to CSV
write.csv(scenario_results,
file.path("scenarios_resources", "r_scenario_results.csv"))
# View scenario results
kable(scenario_results) |> scroll_box(height = "400px")| replication | arrivals | mean_wait_time_doctor | mean_time_with_doctor | utilisation_doctor | mean_queue_length_doctor | mean_time_in_system | mean_patients_in_system | scenario | interarrival_time | number_of_doctors |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 8 | 0.0000000 | 10.626767 | 0.4537486 | 0.0000000 | 5.909446 | 1.4915195 | 1 | 4 | 3 |
| 2 | 14 | 1.3489715 | 16.474475 | 0.8289468 | 1.0647613 | 6.529895 | 3.5927361 | 1 | 4 | 3 |
| 3 | 8 | 3.3185568 | 12.576986 | 0.9191876 | 0.6619987 | 14.293369 | 2.4039172 | 1 | 4 | 3 |
| 4 | 9 | 7.0789803 | 8.229096 | 1.0000000 | 1.7013415 | 14.810041 | 3.2499288 | 1 | 4 | 3 |
| 5 | 11 | 11.8431217 | 8.866916 | 1.0000000 | 2.7677553 | 16.007073 | 4.0062616 | 1 | 4 | 3 |
| 1 | 9 | 1.1561321 | 12.600360 | 0.7276328 | 0.0867587 | 6.972457 | 2.3250216 | 2 | 5 | 3 |
| 2 | 10 | 0.2185845 | 15.462225 | 0.7846974 | 0.3030654 | 7.019465 | 2.3551596 | 2 | 5 | 3 |
| 3 | 8 | 1.6612341 | 11.704709 | 0.8021880 | 0.2856074 | 10.033307 | 1.7861155 | 2 | 5 | 3 |
| 4 | 4 | 1.3862671 | 8.268402 | 0.5661071 | 0.9366037 | 8.714268 | 0.8466787 | 2 | 5 | 3 |
| 5 | 7 | 1.1383003 | 8.211288 | 0.8055934 | 0.1965982 | 8.159880 | 1.5215678 | 2 | 5 | 3 |
| 1 | 4 | 0.0000000 | 16.756011 | 0.6414842 | 0.0000000 | 7.594901 | 1.8938729 | 3 | 6 | 3 |
| 2 | 7 | 0.0000000 | 13.951263 | 0.6246311 | 0.0000000 | 9.878982 | 1.8342062 | 3 | 6 | 3 |
| 3 | 11 | 0.0000000 | 10.642901 | 0.6635152 | 0.0000000 | 5.471170 | 2.1379821 | 3 | 6 | 3 |
| 4 | 5 | 2.1255795 | 7.962361 | 0.6226607 | 0.7752625 | 10.087940 | 1.8885379 | 3 | 6 | 3 |
| 5 | 6 | 0.1698502 | 7.931855 | 0.6517116 | 0.0000000 | 7.985399 | 0.8057795 | 3 | 6 | 3 |
| 1 | 5 | 0.0000000 | 14.928869 | 0.4778604 | 0.0000000 | 8.986369 | 1.3699936 | 4 | 7 | 3 |
| 2 | 5 | 0.0000000 | 16.557442 | 0.5346736 | 0.0000000 | 9.466947 | 1.3707882 | 4 | 7 | 3 |
| 3 | 8 | 0.0000000 | 10.066787 | 0.5240666 | 0.0000000 | 3.468666 | 2.0591952 | 4 | 7 | 3 |
| 4 | 5 | 0.9021887 | 7.962361 | 0.4926338 | 0.7752625 | 8.864549 | 1.5240774 | 4 | 7 | 3 |
| 5 | 4 | 0.0000000 | 8.753199 | 0.3178118 | 0.0000000 | 7.926292 | 0.7389692 | 4 | 7 | 3 |
| 1 | 3 | 0.0000000 | 20.737360 | 0.4070758 | 0.0000000 | 8.986369 | 1.2813530 | 5 | 8 | 3 |
| 2 | 5 | 0.0000000 | 16.557442 | 0.4604853 | 0.0000000 | 9.466947 | 1.1994397 | 5 | 8 | 3 |
| 3 | 7 | 0.0000000 | 11.304930 | 0.4236727 | 0.0000000 | 3.882443 | 1.8788845 | 5 | 8 | 3 |
| 4 | 6 | 0.0000000 | 6.932281 | 0.3316804 | 0.0000000 | 4.751341 | 0.9071163 | 5 | 8 | 3 |
| 5 | 3 | 0.0000000 | 9.563759 | 0.2875069 | 0.0000000 | 7.926292 | 0.5634397 | 5 | 8 | 3 |
| 1 | 11 | 0.2873017 | 8.979263 | 0.7385688 | 0.0000000 | 5.072421 | 2.0138738 | 6 | 4 | 4 |
| 2 | 7 | 0.0000000 | 11.322431 | 0.6053971 | 0.0000000 | 5.722932 | 2.1066223 | 6 | 4 | 4 |
| 3 | 10 | 0.3279443 | 13.891435 | 0.8138707 | 0.0000000 | 11.073710 | 2.2892632 | 6 | 4 | 4 |
| 4 | 6 | 0.0000000 | 7.155858 | 0.3158917 | 0.0000000 | 6.212797 | 0.7887158 | 6 | 4 | 4 |
| 5 | 8 | 0.0000000 | 9.299128 | 0.3310623 | 0.0000000 | 7.062117 | 1.5393844 | 6 | 4 | 4 |
| 1 | 9 | 0.0000000 | 14.361497 | 0.5548891 | 0.0000000 | 5.909446 | 2.0617147 | 7 | 5 | 4 |
| 2 | 8 | 0.0000000 | 10.902873 | 0.5048249 | 0.0000000 | 5.040122 | 1.5551330 | 7 | 5 | 4 |
| 3 | 8 | 0.1002196 | 12.491221 | 0.6609534 | 0.0000000 | 8.233837 | 1.6932483 | 7 | 5 | 4 |
| 4 | 5 | 0.0000000 | 7.220270 | 0.3307908 | 0.0000000 | 8.605687 | 0.8960278 | 7 | 5 | 4 |
| 5 | 5 | 0.0000000 | 7.764737 | 0.2861765 | 0.0000000 | 7.764737 | 1.0613887 | 7 | 5 | 4 |
| 1 | 4 | 0.0000000 | 16.756011 | 0.4811132 | 0.0000000 | 7.594901 | 1.8938729 | 8 | 6 | 4 |
| 2 | 7 | 0.0000000 | 13.951263 | 0.4684733 | 0.0000000 | 9.878982 | 1.8342062 | 8 | 6 | 4 |
| 3 | 11 | 0.0000000 | 10.921073 | 0.4976364 | 0.0000000 | 5.471170 | 2.1379821 | 8 | 6 | 4 |
| 4 | 6 | 0.0000000 | 6.932281 | 0.4399759 | 0.0000000 | 6.932281 | 1.3959298 | 8 | 6 | 4 |
| 5 | 4 | 0.0000000 | 8.753199 | 0.2213847 | 0.0000000 | 7.926292 | 0.8176458 | 8 | 6 | 4 |
| 1 | 5 | 0.0000000 | 14.928869 | 0.3583953 | 0.0000000 | 8.986369 | 1.3699936 | 9 | 7 | 4 |
| 2 | 5 | 0.0000000 | 16.557442 | 0.4010052 | 0.0000000 | 9.466947 | 1.3707882 | 9 | 7 | 4 |
| 3 | 8 | 0.0000000 | 10.066787 | 0.3930499 | 0.0000000 | 3.468666 | 2.0591952 | 9 | 7 | 4 |
| 4 | 6 | 0.0000000 | 6.932281 | 0.3505276 | 0.0000000 | 6.932281 | 1.2725838 | 9 | 7 | 4 |
| 5 | 4 | 0.0000000 | 7.539044 | 0.1964017 | 0.0000000 | 5.772494 | 0.4947610 | 9 | 7 | 4 |
| 1 | 3 | 0.0000000 | 20.737360 | 0.3053068 | 0.0000000 | 8.986369 | 1.2813530 | 10 | 8 | 4 |
| 2 | 5 | 0.0000000 | 16.557442 | 0.3453640 | 0.0000000 | 9.466947 | 1.1994397 | 10 | 8 | 4 |
| 3 | 7 | 0.0000000 | 11.304930 | 0.3177545 | 0.0000000 | 3.882443 | 1.8788845 | 10 | 8 | 4 |
| 4 | 6 | 0.0000000 | 6.932281 | 0.2487603 | 0.0000000 | 4.751341 | 0.9071163 | 10 | 8 | 4 |
| 5 | 4 | 0.0000000 | 7.437496 | 0.2778820 | 0.0000000 | 7.437496 | 1.2817552 | 10 | 8 | 4 |
| 1 | 13 | 0.1711051 | 9.688685 | 0.6178914 | 0.0000000 | 5.063579 | 2.1269053 | 11 | 4 | 5 |
| 2 | 7 | 0.0000000 | 11.322431 | 0.4843177 | 0.0000000 | 5.722932 | 2.1066223 | 11 | 4 | 5 |
| 3 | 11 | 0.1017516 | 14.993140 | 0.7501085 | 0.0000000 | 11.329369 | 2.7785658 | 11 | 4 | 5 |
| 4 | 6 | 0.0000000 | 7.155858 | 0.2527133 | 0.0000000 | 6.212797 | 0.7887158 | 11 | 4 | 5 |
| 5 | 8 | 0.0000000 | 9.299128 | 0.2648499 | 0.0000000 | 7.062117 | 1.5393844 | 11 | 4 | 5 |
| 1 | 9 | 0.0000000 | 14.361497 | 0.4439113 | 0.0000000 | 5.909446 | 2.0617147 | 12 | 5 | 5 |
| 2 | 8 | 0.0000000 | 10.902873 | 0.4038599 | 0.0000000 | 5.040122 | 1.5551330 | 12 | 5 | 5 |
| 3 | 8 | 0.0000000 | 13.200172 | 0.5337198 | 0.0000000 | 8.233837 | 1.6932483 | 12 | 5 | 5 |
| 4 | 5 | 0.0000000 | 7.220270 | 0.2646327 | 0.0000000 | 8.605687 | 0.8960278 | 12 | 5 | 5 |
| 5 | 5 | 0.0000000 | 7.764737 | 0.2289412 | 0.0000000 | 7.764737 | 1.0613887 | 12 | 5 | 5 |
| 1 | 4 | 0.0000000 | 16.756011 | 0.3848905 | 0.0000000 | 7.594901 | 1.8938729 | 13 | 6 | 5 |
| 2 | 7 | 0.0000000 | 13.951263 | 0.3747787 | 0.0000000 | 9.878982 | 1.8342062 | 13 | 6 | 5 |
| 3 | 11 | 0.0000000 | 10.921073 | 0.3981091 | 0.0000000 | 5.471170 | 2.1379821 | 13 | 6 | 5 |
| 4 | 6 | 0.0000000 | 6.932281 | 0.3519807 | 0.0000000 | 6.932281 | 1.3959298 | 13 | 6 | 5 |
| 5 | 4 | 0.0000000 | 8.753199 | 0.1771077 | 0.0000000 | 7.926292 | 0.8176458 | 13 | 6 | 5 |
| 1 | 5 | 0.0000000 | 14.928869 | 0.2867162 | 0.0000000 | 8.986369 | 1.3699936 | 14 | 7 | 5 |
| 2 | 5 | 0.0000000 | 16.557442 | 0.3208042 | 0.0000000 | 9.466947 | 1.3707882 | 14 | 7 | 5 |
| 3 | 8 | 0.0000000 | 10.066787 | 0.3144400 | 0.0000000 | 3.468666 | 2.0591952 | 14 | 7 | 5 |
| 4 | 6 | 0.0000000 | 6.932281 | 0.2804221 | 0.0000000 | 6.932281 | 1.2725838 | 14 | 7 | 5 |
| 5 | 4 | 0.0000000 | 7.539044 | 0.1571214 | 0.0000000 | 5.772494 | 0.4947610 | 14 | 7 | 5 |
| 1 | 3 | 0.0000000 | 20.737360 | 0.2442455 | 0.0000000 | 8.986369 | 1.2813530 | 15 | 8 | 5 |
| 2 | 5 | 0.0000000 | 16.557442 | 0.2762912 | 0.0000000 | 9.466947 | 1.1994397 | 15 | 8 | 5 |
| 3 | 7 | 0.0000000 | 11.304930 | 0.2542036 | 0.0000000 | 3.882443 | 1.8788845 | 15 | 8 | 5 |
| 4 | 6 | 0.0000000 | 6.932281 | 0.1990082 | 0.0000000 | 4.751341 | 0.9071163 | 15 | 8 | 5 |
| 5 | 4 | 0.0000000 | 7.437496 | 0.2223056 | 0.0000000 | 7.437496 | 1.2817552 | 15 | 8 | 5 |
Sensitivity analysis
How does sensitivity analysis differ from scenario analysis?
Scenario analysis focuses on a set of pre-defined situations which are plausible and relevant to the problem being studied. It can often involve varying multiple parameters simultaneously. The purpose is to understand how the system operates under different hypothetical scenarios.
Sensitivity analysis varies one parameter (or a small group of parameters) and assess the impact of small changes in that parameter on outcomes. The purpose is to understand how uncertainty in the inputs affects the model, and how robust the model is to variation in those inputs.
Running sensitivity analyses
Sensitivity analyses should be run programmatically, just like scenario analyses, and the code should be shared for reproducibility.
They can be run using similar code. Below is an example using the run_scenarios function to vary the interarrival_time parameter.
# Run sensitivity analysis
sensitivity_results = run_scenarios(
scenarios={"interarrival_time": [4, 4.5, 5, 5.5, 6, 6.5, 7]},
param_factory=Parameters
)There are 7 scenarios. Running:
{'interarrival_time': 4}
{'interarrival_time': 4.5}
{'interarrival_time': 5}
{'interarrival_time': 5.5}
{'interarrival_time': 6}
{'interarrival_time': 6.5}
{'interarrival_time': 7}
# Save to CSV
sensitivity_results.to_csv(
"scenarios_resources/python_sensitivity_results.csv", index=False
)
# View sensitivity results
HTML(to_html_datatable(sensitivity_results.head(200)))| Loading ITables v2.5.2 from the internet... (need help?) |
# Run sensitivity analysis
sensitivity_results <- run_scenarios(
scenarios = list(interarrival_time = c(4L, 4.5, 5L, 5.5, 6L, 6.5, 7L)),
base_list = create_params()
)There are 7 scenarios.
Base parameters:
$interarrival_time
[1] 5
$consultation_time
[1] 10
$number_of_doctors
[1] 3
$warm_up_period
[1] 30
$data_collection_period
[1] 40
$number_of_runs
[1] 5
$verbose
[1] FALSE
Scenario: interarrival_time = 4
Scenario: interarrival_time = 4.5
Scenario: interarrival_time = 5
Scenario: interarrival_time = 5.5
Scenario: interarrival_time = 6
Scenario: interarrival_time = 6.5
Scenario: interarrival_time = 7
# Save to CSV
write.csv(sensitivity_results,
file.path("scenarios_resources", "r_sensitivity_results.csv"))
# View sensitivity results
kable(sensitivity_results) |> scroll_box(height = "400px")| replication | arrivals | mean_wait_time_doctor | mean_time_with_doctor | utilisation_doctor | mean_queue_length_doctor | mean_time_in_system | mean_patients_in_system | scenario | interarrival_time |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 8 | 0.0000000 | 10.626767 | 0.4537486 | 0.0000000 | 5.909446 | 1.4915195 | 1 | 4.0 |
| 2 | 14 | 1.3489715 | 16.474475 | 0.8289468 | 1.0647613 | 6.529895 | 3.5927361 | 1 | 4.0 |
| 3 | 8 | 3.3185568 | 12.576986 | 0.9191876 | 0.6619987 | 14.293369 | 2.4039172 | 1 | 4.0 |
| 4 | 9 | 7.0789803 | 8.229096 | 1.0000000 | 1.7013415 | 14.810041 | 3.2499288 | 1 | 4.0 |
| 5 | 11 | 11.8431217 | 8.866916 | 1.0000000 | 2.7677553 | 16.007073 | 4.0062616 | 1 | 4.0 |
| 1 | 11 | 4.4136200 | 10.968638 | 0.9298199 | 0.9756128 | 10.970224 | 3.1920586 | 2 | 4.5 |
| 2 | 10 | 4.2952348 | 11.628385 | 0.8730010 | 0.3030654 | 10.737537 | 3.5451830 | 2 | 4.5 |
| 3 | 8 | 6.3476572 | 13.246809 | 0.9533261 | 0.8691611 | 16.012763 | 3.1993868 | 2 | 4.5 |
| 4 | 4 | 1.6258165 | 9.999398 | 0.6284609 | 2.2429504 | 8.013161 | 0.7329990 | 2 | 4.5 |
| 5 | 7 | 1.4030508 | 7.662403 | 0.8019001 | 0.1902909 | 8.671934 | 1.8121060 | 2 | 4.5 |
| 1 | 9 | 1.1561321 | 12.600360 | 0.7276328 | 0.0867587 | 6.972457 | 2.3250216 | 3 | 5.0 |
| 2 | 10 | 0.2185845 | 15.462225 | 0.7846974 | 0.3030654 | 7.019465 | 2.3551596 | 3 | 5.0 |
| 3 | 8 | 1.6612341 | 11.704709 | 0.8021880 | 0.2856074 | 10.033307 | 1.7861155 | 3 | 5.0 |
| 4 | 4 | 1.3862671 | 8.268402 | 0.5661071 | 0.9366037 | 8.714268 | 0.8466787 | 3 | 5.0 |
| 5 | 7 | 1.1383003 | 8.211288 | 0.8055934 | 0.1965982 | 8.159880 | 1.5215678 | 3 | 5.0 |
| 1 | 8 | 0.0000000 | 13.183604 | 0.6309778 | 0.0000000 | 7.594901 | 1.7924370 | 4 | 5.5 |
| 2 | 6 | 0.0000000 | 14.204232 | 0.6859391 | 0.0000000 | 8.061193 | 1.7964074 | 4 | 5.5 |
| 3 | 9 | 1.0701721 | 10.484981 | 0.8439595 | 0.3755197 | 7.720035 | 2.8520302 | 4 | 5.5 |
| 4 | 8 | 1.6097542 | 12.349744 | 0.6932288 | 0.3067946 | 7.074266 | 2.0253767 | 4 | 5.5 |
| 5 | 7 | 0.6407407 | 8.647519 | 0.7276208 | 0.0000000 | 7.929976 | 1.1557192 | 4 | 5.5 |
| 1 | 4 | 0.0000000 | 16.756011 | 0.6414842 | 0.0000000 | 7.594901 | 1.8938729 | 5 | 6.0 |
| 2 | 7 | 0.0000000 | 13.951263 | 0.6246311 | 0.0000000 | 9.878982 | 1.8342062 | 5 | 6.0 |
| 3 | 11 | 0.0000000 | 10.642901 | 0.6635152 | 0.0000000 | 5.471170 | 2.1379821 | 5 | 6.0 |
| 4 | 5 | 2.1255795 | 7.962361 | 0.6226607 | 0.7752625 | 10.087940 | 1.8885379 | 5 | 6.0 |
| 5 | 6 | 0.1698502 | 7.931855 | 0.6517116 | 0.0000000 | 7.985399 | 0.8057795 | 5 | 6.0 |
| 1 | 5 | 0.0000000 | 14.928869 | 0.5853969 | 0.0000000 | 7.601250 | 1.6358209 | 6 | 6.5 |
| 2 | 6 | 0.0000000 | 15.719055 | 0.5136415 | 0.0000000 | 12.100828 | 1.5208300 | 6 | 6.5 |
| 3 | 8 | 0.0000000 | 10.066787 | 0.5979488 | 0.0000000 | 5.471170 | 2.0850030 | 6 | 6.5 |
| 4 | 5 | 1.5138841 | 7.962361 | 0.5499657 | 0.7752625 | 9.476245 | 1.6985554 | 6 | 6.5 |
| 5 | 5 | 0.0046527 | 13.128797 | 0.6701869 | 0.0000000 | 13.133450 | 1.8736129 | 6 | 6.5 |
| 1 | 5 | 0.0000000 | 14.928869 | 0.4778604 | 0.0000000 | 8.986369 | 1.3699936 | 7 | 7.0 |
| 2 | 5 | 0.0000000 | 16.557442 | 0.5346736 | 0.0000000 | 9.466947 | 1.3707882 | 7 | 7.0 |
| 3 | 8 | 0.0000000 | 10.066787 | 0.5240666 | 0.0000000 | 3.468666 | 2.0591952 | 7 | 7.0 |
| 4 | 5 | 0.9021887 | 7.962361 | 0.4926338 | 0.7752625 | 8.864549 | 1.5240774 | 7 | 7.0 |
| 5 | 4 | 0.0000000 | 8.753199 | 0.3178118 | 0.0000000 | 7.926292 | 0.7389692 | 7 | 7.0 |
Explore the example models
Nurse visit simulation
Click to visit pydesrap_mms repository
| Key files | simulation/run_scenarios.pynotebooks/analysis.ipynb |
| What to look for? | Similar implementation to above. |
Click to visit rdesrap_mms repository
| Key files | R/scenarios.Rrmarkdown/analysis.md |
| What to look for? | Similar implementation to above. |
Stroke pathway simulation
Click to visit pydesrap_stroke repository
| Key files | notebooks/analysis.ipynb |
| What to look for? | These scenarios are run one-by-one in the Jupyter notebook. They involve altering arrivals before running the simulation (5% increase in admissions), or changes in calculations (pooling beds). |
Click to visit rdesrap_stroke repository
| Key files | rmarkdown/analysis.md |
| What to look for? | These scenarios are run one-by-one in the Rmarkdown. They involve altering arrivals before running the simulation (5% increase in admissions), or changes in calculations (pooling beds). |
Test yourself
Try running your own scenario and sensitivity analyses.
You could build your own simple loop to test different values - just make sure you use a fresh set of parameters for each run.
Alternatively, use the run_scenarios function.