---
title: Sensitivity Analysis
date: 2025-04-04T18:05:00Z
modified: 2026-08-02T12:18:10Z
permalink: "https://www.micheledpierri.com/2025/04/04/sensitivity-analysis/"
type: post
status: publish
excerpt: ""
wpid: 1231
categories:
  - Data Analysis
  - Machine Learning
  - Programming
  - Statistics
tags:
  - Data Analysis
  - Machine Learning
  - Programming
  - Statistics
  - Python
featured_image: "https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_.png"
featured_image_alt: A group of barefoot children in worn old-fashioned clothes stands on a stormy beach, holding seashells to their ears as they gaze toward the sea and dramatic rays of sunlight breaking through heavy clouds above crashing waves.
timestamp: 2026-08-02T12:18:10Z
---

## Definition

Sensitivity analysis is a collection of techniques that determine how input parameters affect model results. Specifically, it measures how much variation in the results stems from different types of uncertainty.

For a model:

![Y=f(X_1,X_2,X_3…..X_n)](https://www.micheledpierri.com/wp-content/ql-cache/quicklatex.com-f26082c96e1ba447639a199974ff16eb_l3.svg "Rendered by QuickLaTeX.com")

examines how Y changes when each X is modified.

Sensitivity analysis can be applied across several key areas: predictive models, simulation, risk assessment, complex systems optimization, model validation.

Through sensitivity analysis, we can evaluate how variables affect outputs, simplify models by identifying negligible variables, pinpoint the most influential factors, and increase the transparency of model evaluation.

## Sensitivity Analysis Techniques

Here are the main sensitivity analysis techniques we will explore:

[One-at-a-Time (OAT)](#OAT)

[Sobol Analysis](#Sobol)

[FAST](#FAST)

[Regression-based (SRC, PCC)](#Regression)

[SHAP Values](#SHAP)

[Random Forest Feature Importance](#Random)

[Tornado Plot](#Tornado)

[Bayesian Sensitivity (PyMC, Prob. Mod.)](#Bayesian)

[DoE + ANOVA](#DoE)

### One At a Time (OAT)

This technique involves changing one input variable at a time while keeping all others constant, then measuring how the output changes.

While simple to implement, this technique has limitations: it may overlook non-linear relationships and, crucially, fails to capture interactions between variables.ired for security purposes.

import numpy as np
import matplotlib.pyplot as plt

# Define a simple model (nonlinear)
def model(x):
    """x = [x1, x2, x3]"""
    return np.sin(x[0]) + 0.5 * x[1]**2 + np.log1p(x[2])

# Baseline input
x_base = np.array([1.0, 2.0, 3.0])
y_base = model(x_base)

# Define perturbation (e.g., ±10%)
delta = 0.1

# Store results
sensitivities = []
labels = ['x1', 'x2', 'x3']

for i in range(len(x_base)):
    x_perturb = x_base.copy()
    x_perturb[i] *= (1 + delta)  # increase by 10%
    y_perturb = model(x_perturb)
    sensitivity = (y_perturb - y_base) / (x_perturb[i] - x_base[i])  # finite difference
    sensitivities.append(sensitivity)

# Plot results
plt.bar(labels, sensitivities)
plt.title('One-at-a-Time Sensitivity')
plt.ylabel('Δy / Δx')
plt.grid(True)
plt.show()```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Define a simple model (nonlinear)</span></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">model</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">x</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4">"""x = [x1, x2, x3]"""</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> np.sin(x[</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">]) </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.5</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> x[</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">]</span><span style="color: #FF79C6">**</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> np.log1p(x[</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2">])</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Baseline input</span></span>
<span class="line"><span style="color: #F8F8F2">x_base </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.array([</span><span style="color: #BD93F9">1.0</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">2.0</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">3.0</span><span style="color: #F8F8F2">])</span></span>
<span class="line"><span style="color: #F8F8F2">y_base </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> model(x_base)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Define perturbation (e.g., ±10%)</span></span>
<span class="line"><span style="color: #F8F8F2">delta </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.1</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Store results</span></span>
<span class="line"><span style="color: #F8F8F2">sensitivities </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"><span style="color: #F8F8F2">labels </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> [</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x1</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x2</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x3</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> i </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">range</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD">len</span><span style="color: #F8F8F2">(x_base)):</span></span>
<span class="line"><span style="color: #F8F8F2">    x_perturb </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> x_base.copy()</span></span>
<span class="line"><span style="color: #F8F8F2">    x_perturb[i] </span><span style="color: #FF79C6">*=</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> delta)  </span><span style="color: #6272A4"># increase by 10%</span></span>
<span class="line"><span style="color: #F8F8F2">    y_perturb </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> model(x_perturb)</span></span>
<span class="line"><span style="color: #F8F8F2">    sensitivity </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (y_perturb </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> y_base) </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> (x_perturb[i] </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> x_base[i])  </span><span style="color: #6272A4"># finite difference</span></span>
<span class="line"><span style="color: #F8F8F2">    sensitivities.append(sensitivity)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Plot results</span></span>
<span class="line"><span style="color: #F8F8F2">plt.bar(labels, sensitivities)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.title(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">One-at-a-Time Sensitivity</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.ylabel(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Δy / Δx</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
```

![One_at_a_Time sensitivity analysis plot](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_1-1024x768.png)

[Return to Techniques Index](#Top)

### Sobol sensitivity analysis

Sobol analysis builds upon the previous method by quantifying not only the individual contribution of each variable to the output, but also evaluating how variables interact with one another.

The results of a Sobol analysis include:

S1 = first-order index: measures the direct contribution of each individual variable

ST = total-order index: captures all interaction effects involving a variable

S2 = second-order index: measures the combined contribution of variable pairs

A high S1 value indicates a strong connection with the output. Variables with high S1-ST values show significant interactions with other variables. Variables with low ST values can be considered negligible and removed from the model.

To build a Sobol sensitivity analysis, first define a data dictionary for your dataset. For each variable, specify either the extremes (minimum-maximum) or percentiles (5th-95th).

Next, pass this dictionary to the Saltelli method, which generates a matrix of simulated data.

Then, input this Saltelli matrix into your model to generate the output.

Finally, the Sobol analysis calculates the S1, ST, and S3 indices to evaluate how each variable impacts the outcome.

import numpy as np
from SALib.sample import saltelli
from SALib.analyze import sobol
import matplotlib.pyplot as plt

# 1. Definition of the clinical problem (variables and ranges)
problem = {
    'num_vars': 4,
    'names': ['age', 'creat', 'ef', 'nyha'],
    'bounds': [
        [50, 85],    # Age (years)
        [0.6, 2.5],  # Creatinine (mg/dL)
        [20, 70],    # Ejection Fraction EF (%)
        [1, 4]       # NYHA Class (I-IV)
    ]
}

# 2. Sample generation using Saltelli scheme
X = saltelli.sample(problem, 1024, calc_second_order=True)

# 3. Definition of simulated clinical model
def clinical_model(X):
    age = X[:, 0]
    creat = X[:, 1]
    ef = X[:, 2]
    nyha = X[:, 3]

    # logistic risk model (simplified)
    logit = 0.03 * age + 0.8 * creat - 0.05 * ef + 0.4 * nyha
    risk = 1 / (1 + np.exp(-logit))  # probability between 0 and 1
    return risk

# 4. Output calculation
Y = clinical_model(X)

# 5. Sobol sensitivity analysis
Si = sobol.analyze(problem, Y, calc_second_order=True, print_to_console=True)

# 6. Visualization (S1 and ST)
labels = problem['names']
S1 = Si['S1']
ST = Si['ST']

x = np.arange(len(labels))
width = 0.35

plt.bar(x - width/2, S1, width, label='First-order (S1)')
plt.bar(x + width/2, ST, width, label='Total-order (ST)')
plt.xticks(x, labels)
plt.ylabel('Sobol Index')
plt.title('Sobol Sensitivity Analysis (Clinical Model)')
plt.legend()
plt.grid(True)
plt.show()```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> SALib.sample </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> saltelli</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> SALib.analyze </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> sobol</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Definition of the clinical problem (variables and ranges)</span></span>
<span class="line"><span style="color: #F8F8F2">problem </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">num_vars</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">4</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">names</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: [</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creat</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ef</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">nyha</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">bounds</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: [</span></span>
<span class="line"><span style="color: #F8F8F2">        [</span><span style="color: #BD93F9">50</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">85</span><span style="color: #F8F8F2">],    </span><span style="color: #6272A4"># Age (years)</span></span>
<span class="line"><span style="color: #F8F8F2">        [</span><span style="color: #BD93F9">0.6</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">2.5</span><span style="color: #F8F8F2">],  </span><span style="color: #6272A4"># Creatinine (mg/dL)</span></span>
<span class="line"><span style="color: #F8F8F2">        [</span><span style="color: #BD93F9">20</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">70</span><span style="color: #F8F8F2">],    </span><span style="color: #6272A4"># Ejection Fraction EF (%)</span></span>
<span class="line"><span style="color: #F8F8F2">        [</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">4</span><span style="color: #F8F8F2">]       </span><span style="color: #6272A4"># NYHA Class (I-IV)</span></span>
<span class="line"><span style="color: #F8F8F2">    ]</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Sample generation using Saltelli scheme</span></span>
<span class="line"><span style="color: #F8F8F2">X </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> saltelli.sample(problem, </span><span style="color: #BD93F9">1024</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">calc_second_order</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Definition of simulated clinical model</span></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">clinical_model</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">X</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    age </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    creat </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    ef </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    nyha </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># logistic risk model (simplified)</span></span>
<span class="line"><span style="color: #F8F8F2">    logit </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.03</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> age </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.8</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> creat </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.05</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> ef </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.4</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> nyha</span></span>
<span class="line"><span style="color: #F8F8F2">    risk </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> np.exp(</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">logit))  </span><span style="color: #6272A4"># probability between 0 and 1</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> risk</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 4. Output calculation</span></span>
<span class="line"><span style="color: #F8F8F2">Y </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> clinical_model(X)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 5. Sobol sensitivity analysis</span></span>
<span class="line"><span style="color: #F8F8F2">Si </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> sobol.analyze(problem, Y, </span><span style="color: #FFB86C; font-style: italic">calc_second_order</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">print_to_console</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 6. Visualization (S1 and ST)</span></span>
<span class="line"><span style="color: #F8F8F2">labels </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> problem[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">names</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">S1 </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> Si[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">S1</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #BD93F9">ST</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> Si[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ST</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">x </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.arange(</span><span style="color: #8BE9FD">len</span><span style="color: #F8F8F2">(labels))</span></span>
<span class="line"><span style="color: #F8F8F2">width </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.35</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">plt.bar(x </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> width</span><span style="color: #FF79C6">/</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2">, S1, width, </span><span style="color: #FFB86C; font-style: italic">label</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">First-order (S1)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.bar(x </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> width</span><span style="color: #FF79C6">/</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">ST</span><span style="color: #F8F8F2">, width, </span><span style="color: #FFB86C; font-style: italic">label</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Total-order (ST)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.xticks(x, labels)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.ylabel(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Sobol Index</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.title(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Sobol Sensitivity Analysis (Clinical Model)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.legend()</span></span>
<span class="line"><span style="color: #F8F8F2">plt.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
```

![Sobol Sensitivity Analysis with S1 and ST](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_sobol-1-1024x768.png)

[Return to Techniques Index](#Top)

### Fourier Amplitude Sensitivity Test (FAST)

The FAST analysis conducts sensitivity studies by transforming a multivariate function into a univariate function and analyzing its Fourier spectrum

Unlike Sobol analysis, FAST only analyzes variable importance—not interactions between variables—since it only provides the S1 parameter.

FAST works by converting complex input relationships into simpler wave patterns. Think of it like turning each input variable into a unique musical note. These notes are then played together in different combinations, while keeping their individual sounds distinct. By analyzing which notes appear strongest in the final output, we can identify which input variables have the biggest impact on the model’s results.

![Fourier Sensitivity Analysis](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_fourier-1024x819.png)

Example of FAST Analysis Implementation Using SALib:

import numpy as np
import matplotlib.pyplot as plt
from SALib.sample import fast_sampler
from SALib.analyze import fast

# 1. Define the problem with medical variables
problem = {
    'num_vars': 3,
    'names': ['age', 'creatinine', 'ejection_fraction'],
    'bounds': [
        [50, 85],       # Age in years
        [0.6, 2.5],     # Serum creatinine
        [20, 70]        # Left ventricular ejection fraction (%)
    ]
}

# 2. Define a simple clinical risk model (logit-based)
def clinical_model(X):
    age = X[:, 0]
    creat = X[:, 1]
    ef = X[:, 2]
    
    # Logistic-style linear combination
    logit = 0.04 * age + 0.8 * creat - 0.06 * ef
    risk = 1 / (1 + np.exp(-logit))  # mortality probability
    return risk

# 3. Generate samples using FAST
X = fast_sampler.sample(problem, 1000)

# 4. Evaluate the model
Y = clinical_model(X)

# 5. Perform FAST sensitivity analysis
Si = fast.analyze(problem, Y, print_to_console=True)

# 6. Plot the first-order sensitivity indices
plt.bar(problem['names'], Si['S1'])
plt.title('FAST Sensitivity Analysis (Clinical Model)')
plt.ylabel('First-order Index (S1)')
plt.grid(True)
plt.show()
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> SALib.sample </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> fast_sampler</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> SALib.analyze </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> fast</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Define the problem with medical variables</span></span>
<span class="line"><span style="color: #F8F8F2">problem </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">num_vars</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">,</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">names</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: [</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ejection_fraction</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">bounds</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: [</span></span>
<span class="line"><span style="color: #F8F8F2">        [</span><span style="color: #BD93F9">50</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">85</span><span style="color: #F8F8F2">],       </span><span style="color: #6272A4"># Age in years</span></span>
<span class="line"><span style="color: #F8F8F2">        [</span><span style="color: #BD93F9">0.6</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">2.5</span><span style="color: #F8F8F2">],     </span><span style="color: #6272A4"># Serum creatinine</span></span>
<span class="line"><span style="color: #F8F8F2">        [</span><span style="color: #BD93F9">20</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">70</span><span style="color: #F8F8F2">]        </span><span style="color: #6272A4"># Left ventricular ejection fraction (%)</span></span>
<span class="line"><span style="color: #F8F8F2">    ]</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Define a simple clinical risk model (logit-based)</span></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">clinical_model</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">X</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    age </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    creat </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    ef </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    </span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Logistic-style linear combination</span></span>
<span class="line"><span style="color: #F8F8F2">    logit </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.04</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> age </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.8</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> creat </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.06</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> ef</span></span>
<span class="line"><span style="color: #F8F8F2">    risk </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> np.exp(</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">logit))  </span><span style="color: #6272A4"># mortality probability</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> risk</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Generate samples using FAST</span></span>
<span class="line"><span style="color: #F8F8F2">X </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> fast_sampler.sample(problem, </span><span style="color: #BD93F9">1000</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 4. Evaluate the model</span></span>
<span class="line"><span style="color: #F8F8F2">Y </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> clinical_model(X)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 5. Perform FAST sensitivity analysis</span></span>
<span class="line"><span style="color: #F8F8F2">Si </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> fast.analyze(problem, Y, </span><span style="color: #FFB86C; font-style: italic">print_to_console</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 6. Plot the first-order sensitivity indices</span></span>
<span class="line"><span style="color: #F8F8F2">plt.bar(problem[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">names</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">], Si[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">S1</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">])</span></span>
<span class="line"><span style="color: #F8F8F2">plt.title(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">FAST Sensitivity Analysis (Clinical Model)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.ylabel(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">First-order Index (S1)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
<span class="line"></span>
```

[Return to Techniques Index](#Top)

### Regression-based Sensitivity Analysis

This type of sensitivity analysis is commonly used in medicine and involves using standardized features in [linear regression](https://www.micheledpierri.com/wp-content/uploads/wp-mfa-exports/page/linear-regression-in-statistics.md) to examine their influence on the output.

Since the features are standardized, their coefficients can be directly compared to show each feature’s relative influence on the outcome.

However, this analysis has limitations—it cannot capture [non-linear relationships](https://www.micheledpierri.com/wp-content/uploads/wp-mfa-exports/page/nonlinear-regression.md) or interactions between variables.

Example in Python:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler

# 1. Generate synthetic input data
np.random.seed(0)
n = 1000
X = np.random.uniform(low=-np.pi, high=np.pi, size=(n, 3))
x1, x2, x3 = X[:, 0], X[:, 1], X[:, 2]

# 2. Define nonlinear model (Ishigami-like)
def model(x1, x2, x3, a=7, b=0.1):
    return np.sin(x1) + a * np.sin(x2)**2 + b * x3**4 * np.sin(x1)

Y = model(x1, x2, x3)

# 3. Standardize features for SRC
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 4. Fit linear regression
reg = LinearRegression()
reg.fit(X_scaled, Y)

# 5. Get standardized regression coefficients
coef = reg.coef_
names = ['x1', 'x2', 'x3']

# 6. Plot
plt.bar(names, coef)
plt.title('Standardized Regression Coefficients (SRC)')
plt.ylabel('Sensitivity')
plt.grid(True)
plt.show()
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> pandas </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> pd</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> sklearn.linear_model </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> LinearRegression</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> sklearn.preprocessing </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> StandardScaler</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Generate synthetic input data</span></span>
<span class="line"><span style="color: #F8F8F2">np.random.seed(</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">n </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1000</span></span>
<span class="line"><span style="color: #F8F8F2">X </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.random.uniform(</span><span style="color: #FFB86C; font-style: italic">low</span><span style="color: #FF79C6">=-</span><span style="color: #F8F8F2">np.pi, </span><span style="color: #FFB86C; font-style: italic">high</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">np.pi, </span><span style="color: #FFB86C; font-style: italic">size</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">(n, </span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">))</span></span>
<span class="line"><span style="color: #F8F8F2">x1, x2, x3 </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">], X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">], X[</span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Define nonlinear model (Ishigami-like)</span></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">model</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">x1</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">x2</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">x3</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">a</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">7</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">b</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0.1</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> np.sin(x1) </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> a </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> np.sin(x2)</span><span style="color: #FF79C6">**</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> b </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> x3</span><span style="color: #FF79C6">**</span><span style="color: #BD93F9">4</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> np.sin(x1)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">Y </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> model(x1, x2, x3)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Standardize features for SRC</span></span>
<span class="line"><span style="color: #F8F8F2">scaler </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> StandardScaler()</span></span>
<span class="line"><span style="color: #F8F8F2">X_scaled </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> scaler.fit_transform(X)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 4. Fit linear regression</span></span>
<span class="line"><span style="color: #F8F8F2">reg </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> LinearRegression()</span></span>
<span class="line"><span style="color: #F8F8F2">reg.fit(X_scaled, Y)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 5. Get standardized regression coefficients</span></span>
<span class="line"><span style="color: #F8F8F2">coef </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> reg.coef_</span></span>
<span class="line"><span style="color: #F8F8F2">names </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> [</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x1</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x2</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x3</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 6. Plot</span></span>
<span class="line"><span style="color: #F8F8F2">plt.bar(names, coef)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.title(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Standardized Regression Coefficients (SRC)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.ylabel(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Sensitivity</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
<span class="line"></span>
```

![Standardized Regression Coefficients in Regression Sensitivity Analysis](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_src-1024x768.png)

[Return to Techniques Index](#Top)

### SHapley Additive exPlanations (SHAP)

SHAP is a sensitivity analysis technique that excels in Machine Learning by measuring how features affect output, even in black-box models.

It analyzes sensitivity at two levels: globally (examining how variables interact with the entire dataset) and locally (measuring how individual variables influence specific outcomes).

The SHAP framework automatically adapts to any model and generates visual results that clearly show both global and local variable impacts.

One of its key strengths is its ability to handle non-linear relationships.

The following Python example demonstrates how we create a synthetic medical dataset, train an [XGBoost model](https://www.micheledpierri.com/wp-content/uploads/wp-mfa-exports/page/ensemble-models.md) with it, and analyze the model using SHAP to understand both global and local variable importance.

import numpy as np
import pandas as pd
import shap
import xgboost as xgb
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

# 1. Simulate clinical data
np.random.seed(42)
n = 1000
X = pd.DataFrame({
    'age': np.random.randint(50, 90, n),
    'creatinine': np.random.uniform(0.6, 2.5, n),
    'ejection_fraction': np.random.uniform(20, 70, n),
    'nyha_class': np.random.randint(1, 5, n)
})

# 2. Simulate a nonlinear outcome (mortality risk)
def simulate_risk(X):
    logit = (
        0.04 * X['age'] +
        0.9 * X['creatinine'] +
        0.5 * X['nyha_class'] -
        0.06 * X['ejection_fraction']
    )
    prob = 1 / (1 + np.exp(-logit))
    return (prob > 0.5).astype(int)  # binary outcome

y = simulate_risk(X)

# 3. Train/test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 4. Train a gradient boosting model
model = xgb.XGBClassifier(use_label_encoder=False, eval_metric='logloss')
model.fit(X_train, y_train)

# 5. Compute SHAP values
explainer = shap.Explainer(model)
shap_values = explainer(X_test)

# 6. Global interpretation: bar plot
shap.plots.bar(shap_values, max_display=4)

# 7. Local explanation: waterfall for one patient
shap.plots.waterfall(shap_values[0])
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> pandas </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> pd</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> shap</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> xgboost </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> xgb</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> sklearn.model_selection </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> train_test_split</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Simulate clinical data</span></span>
<span class="line"><span style="color: #F8F8F2">np.random.seed(</span><span style="color: #BD93F9">42</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">n </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1000</span></span>
<span class="line"><span style="color: #F8F8F2">X </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pd.DataFrame({</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: np.random.randint(</span><span style="color: #BD93F9">50</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">90</span><span style="color: #F8F8F2">, n),</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: np.random.uniform(</span><span style="color: #BD93F9">0.6</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">2.5</span><span style="color: #F8F8F2">, n),</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ejection_fraction</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: np.random.uniform(</span><span style="color: #BD93F9">20</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">70</span><span style="color: #F8F8F2">, n),</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">nyha_class</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: np.random.randint(</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">5</span><span style="color: #F8F8F2">, n)</span></span>
<span class="line"><span style="color: #F8F8F2">})</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Simulate a nonlinear outcome (mortality risk)</span></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">simulate_risk</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">X</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    logit </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.04</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> X[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.9</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> X[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.5</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> X[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">nyha_class</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">-</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.06</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> X[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ejection_fraction</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    )</span></span>
<span class="line"><span style="color: #F8F8F2">    prob </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> np.exp(</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">logit))</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> (prob </span><span style="color: #FF79C6">></span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.5</span><span style="color: #F8F8F2">).astype(</span><span style="color: #8BE9FD; font-style: italic">int</span><span style="color: #F8F8F2">)  </span><span style="color: #6272A4"># binary outcome</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">y </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> simulate_risk(X)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Train/test split</span></span>
<span class="line"><span style="color: #F8F8F2">X_train, X_test, y_train, y_test </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> train_test_split(X, y, </span><span style="color: #FFB86C; font-style: italic">test_size</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0.2</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 4. Train a gradient boosting model</span></span>
<span class="line"><span style="color: #F8F8F2">model </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> xgb.XGBClassifier(</span><span style="color: #FFB86C; font-style: italic">use_label_encoder</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">False</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">eval_metric</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">logloss</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">model.fit(X_train, y_train)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 5. Compute SHAP values</span></span>
<span class="line"><span style="color: #F8F8F2">explainer </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> shap.Explainer(model)</span></span>
<span class="line"><span style="color: #F8F8F2">shap_values </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> explainer(X_test)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 6. Global interpretation: bar plot</span></span>
<span class="line"><span style="color: #F8F8F2">shap.plots.bar(shap_values, </span><span style="color: #FFB86C; font-style: italic">max_display</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">4</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 7. Local explanation: waterfall for one patient</span></span>
<span class="line"><span style="color: #F8F8F2">shap.plots.waterfall(shap_values[</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">])</span></span>
<span class="line"></span>
```

SHAP Sensitivity Analysis Global Interpretation Graph

![SHAP Sensitivity Analysis Global Interpretation Graph](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_shap_1.png)

SHAP Sensitivity Analysis Local Interpretation Graph

![SHAP Sensitivity Analysis Local Interpretation Graph](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_shap_2.png)

While the global interpretation graph is intuitive, the most valuable aspect of SHAP analysis lies in its local interpretation.

In the local interpretation, variables appear as color-coded arrows—red for positive effects on the outcome and blue for negative effects. Each arrow displays its corresponding “SHAP value,” representing that variable’s overall contribution to the final decision.

[Return to Techniques Index](#Top)

### Random Forest Sensitivity Analysis

Many Machine Learning algorithms include built-in functions for measuring feature importance.

[Random Forest algorithms](https://www.micheledpierri.com/wp-content/uploads/wp-mfa-exports/page/random-forest.md), for instance, offer two distinct methods of measuring feature importance:

Mean Decrease Impurity (MDI), which evaluates how effectively a variable’s splits reduce impurity in the model

Permutation Importance, which calculates how much model performance drops when a feature’s values are randomly shuffled

Python Example: Analyzing Feature Importance:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.inspection import permutation_importance

# 1. Generate synthetic data (same as before)
np.random.seed(0)
n = 1000
X = pd.DataFrame(np.random.uniform(-np.pi, np.pi, size=(n, 3)), columns=['x1', 'x2', 'x3'])

def model(X):
    a = 7
    b = 0.1
    x1, x2, x3 = X['x1'], X['x2'], X['x3']
    return np.sin(x1) + a * np.sin(x2)**2 + b * x3**4 * np.sin(x1)

y = model(X)

# 2. Train-test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 3. Fit Random Forest
rf = RandomForestRegressor(n_estimators=100)
rf.fit(X_train, y_train)

# 4. Get mean decrease impurity feature importance
importances = rf.feature_importances_
features = X.columns

# 5. Plot
plt.bar(features, importances)
plt.title('Random Forest Feature Importance (MDI)')
plt.ylabel('Importance Score')
plt.grid(True)
plt.show()

# 6. Permutation importance (model-agnostic)
perm = permutation_importance(rf, X_test, y_test, n_repeats=10, random_state=0)
perm_sorted_idx = perm.importances_mean.argsort()

# 7. Plot permutation-based importance
plt.barh(features[perm_sorted_idx], perm.importances_mean[perm_sorted_idx])
plt.title('Permutation Feature Importance')
plt.xlabel('Importance')
plt.grid(True)
plt.show()
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> pandas </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> pd</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> sklearn.ensemble </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> RandomForestRegressor</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> sklearn.model_selection </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> train_test_split</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> sklearn.inspection </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> permutation_importance</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Generate synthetic data (same as before)</span></span>
<span class="line"><span style="color: #F8F8F2">np.random.seed(</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">n </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1000</span></span>
<span class="line"><span style="color: #F8F8F2">X </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pd.DataFrame(np.random.uniform(</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">np.pi, np.pi, </span><span style="color: #FFB86C; font-style: italic">size</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">(n, </span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">)), </span><span style="color: #FFB86C; font-style: italic">columns</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x1</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x2</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x3</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">])</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">model</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">X</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    a </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">7</span></span>
<span class="line"><span style="color: #F8F8F2">    b </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.1</span></span>
<span class="line"><span style="color: #F8F8F2">    x1, x2, x3 </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x1</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">], X[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x2</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">], X[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">x3</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> np.sin(x1) </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> a </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> np.sin(x2)</span><span style="color: #FF79C6">**</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> b </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> x3</span><span style="color: #FF79C6">**</span><span style="color: #BD93F9">4</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> np.sin(x1)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">y </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> model(X)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Train-test split</span></span>
<span class="line"><span style="color: #F8F8F2">X_train, X_test, y_train, y_test </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> train_test_split(X, y, </span><span style="color: #FFB86C; font-style: italic">test_size</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0.2</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Fit Random Forest</span></span>
<span class="line"><span style="color: #F8F8F2">rf </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> RandomForestRegressor(</span><span style="color: #FFB86C; font-style: italic">n_estimators</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">100</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">rf.fit(X_train, y_train)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 4. Get mean decrease impurity feature importance</span></span>
<span class="line"><span style="color: #F8F8F2">importances </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> rf.feature_importances_</span></span>
<span class="line"><span style="color: #F8F8F2">features </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> X.columns</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 5. Plot</span></span>
<span class="line"><span style="color: #F8F8F2">plt.bar(features, importances)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.title(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Random Forest Feature Importance (MDI)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.ylabel(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Importance Score</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 6. Permutation importance (model-agnostic)</span></span>
<span class="line"><span style="color: #F8F8F2">perm </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> permutation_importance(rf, X_test, y_test, </span><span style="color: #FFB86C; font-style: italic">n_repeats</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">10</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">random_state</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">perm_sorted_idx </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> perm.importances_mean.argsort()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 7. Plot permutation-based importance</span></span>
<span class="line"><span style="color: #F8F8F2">plt.barh(features[perm_sorted_idx], perm.importances_mean[perm_sorted_idx])</span></span>
<span class="line"><span style="color: #F8F8F2">plt.title(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Permutation Feature Importance</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.xlabel(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Importance</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
<span class="line"></span>
```

![Random Forest Feature importance (MDI)](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_feature_importance_mdi-1024x768.png)

![Random Forest Permutation Feature Importance](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_feature_importance-1024x768.png)

[Return to Techniques Index](#Top)

### Tornado Plot Sensitivity Analysis

A Tornado Plot is a powerful tool for sensitivity analysis, widely used in medicine—especially for clinical decision analysis and risk modeling.

This visualization demonstrates how changing a single variable while holding others constant affects predictions, with variables ranked by their impact magnitude.

While effective, it provides only local analysis and may miss non-linear relationships in the data.

Now let’s examine how to create a tornado plot using simulated medical data:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 1. Define a baseline clinical input set
baseline = {
    'age': 70,               # years
    'creatinine': 1.2,       # mg/dL
    'ejection_fraction': 40, # %
    'nyha_class': 3          # NYHA I-IV
}

# 2. Define a simple logistic-style clinical model
def predict_risk(inputs):
    logit = (
        0.04 * inputs['age'] +
        0.9 * inputs['creatinine'] +
        0.5 * inputs['nyha_class'] -
        0.06 * inputs['ejection_fraction']
    )
    prob = 1 / (1 + np.exp(-logit))
    return prob

# 3. Define ±10% variation for deterministic sensitivity
delta = 0.1
results = []

for var in baseline:
    low = baseline.copy()
    high = baseline.copy()
    
    # Apply ±10% variation
    low[var] *= (1 - delta)
    high[var] *= (1 + delta)

    y_low = predict_risk(low)
    y_high = predict_risk(high)

    results.append({
        'Variable': var,
        'Low': y_low,
        'High': y_high,
        'Range': abs(y_high - y_low)
    })

# 4. Create DataFrame and sort
df = pd.DataFrame(results).sort_values(by='Range', ascending=True)

# 5. Plot tornado chart
fig, ax = plt.subplots(figsize=(8, 5))
for i, row in df.iterrows():
    ax.plot([row['Low'], row['High']], [row['Variable'], row['Variable']], lw=10, solid_capstyle='butt')
baseline_risk = predict_risk(baseline)
ax.axvline(baseline_risk, color='k', linestyle='--', label='Baseline risk')
ax.set_title("Tornado Plot - Sensitivity to Clinical Inputs")
ax.set_xlabel("Predicted Mortality Risk")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> pandas </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> pd</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Define a baseline clinical input set</span></span>
<span class="line"><span style="color: #F8F8F2">baseline </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">70</span><span style="color: #F8F8F2">,               </span><span style="color: #6272A4"># years</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">1.2</span><span style="color: #F8F8F2">,       </span><span style="color: #6272A4"># mg/dL</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ejection_fraction</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">40</span><span style="color: #F8F8F2">, </span><span style="color: #6272A4"># %</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">nyha_class</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #BD93F9">3</span><span style="color: #F8F8F2">          </span><span style="color: #6272A4"># NYHA I-IV</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Define a simple logistic-style clinical model</span></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">predict_risk</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">inputs</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    logit </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.04</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> inputs[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.9</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> inputs[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.5</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> inputs[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">nyha_class</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">-</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #BD93F9">0.06</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> inputs[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ejection_fraction</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    )</span></span>
<span class="line"><span style="color: #F8F8F2">    prob </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> np.exp(</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">logit))</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> prob</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Define ±10% variation for deterministic sensitivity</span></span>
<span class="line"><span style="color: #F8F8F2">delta </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.1</span></span>
<span class="line"><span style="color: #F8F8F2">results </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> []</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> var </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> baseline:</span></span>
<span class="line"><span style="color: #F8F8F2">    low </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> baseline.copy()</span></span>
<span class="line"><span style="color: #F8F8F2">    high </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> baseline.copy()</span></span>
<span class="line"><span style="color: #F8F8F2">    </span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Apply ±10% variation</span></span>
<span class="line"><span style="color: #F8F8F2">    low[var] </span><span style="color: #FF79C6">*=</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> delta)</span></span>
<span class="line"><span style="color: #F8F8F2">    high[var] </span><span style="color: #FF79C6">*=</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> delta)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    y_low </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> predict_risk(low)</span></span>
<span class="line"><span style="color: #F8F8F2">    y_high </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> predict_risk(high)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    results.append({</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Variable</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: var,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Low</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: y_low,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">High</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: y_high,</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Range</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">: </span><span style="color: #8BE9FD">abs</span><span style="color: #F8F8F2">(y_high </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> y_low)</span></span>
<span class="line"><span style="color: #F8F8F2">    })</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 4. Create DataFrame and sort</span></span>
<span class="line"><span style="color: #F8F8F2">df </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pd.DataFrame(results).sort_values(</span><span style="color: #FFB86C; font-style: italic">by</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Range</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">ascending</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 5. Plot tornado chart</span></span>
<span class="line"><span style="color: #F8F8F2">fig, ax </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> plt.subplots(</span><span style="color: #FFB86C; font-style: italic">figsize</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">(</span><span style="color: #BD93F9">8</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">5</span><span style="color: #F8F8F2">))</span></span>
<span class="line"><span style="color: #FF79C6">for</span><span style="color: #F8F8F2"> i, row </span><span style="color: #FF79C6">in</span><span style="color: #F8F8F2"> df.iterrows():</span></span>
<span class="line"><span style="color: #F8F8F2">    ax.plot([row[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Low</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">], row[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">High</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]], [row[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Variable</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">], row[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Variable</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]], </span><span style="color: #FFB86C; font-style: italic">lw</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">10</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">solid_capstyle</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">butt</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">baseline_risk </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> predict_risk(baseline)</span></span>
<span class="line"><span style="color: #F8F8F2">ax.axvline(baseline_risk, </span><span style="color: #FFB86C; font-style: italic">color</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">k</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">linestyle</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">--</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">label</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Baseline risk</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">ax.set_title(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Tornado Plot - Sensitivity to Clinical Inputs</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">ax.set_xlabel(</span><span style="color: #E9F284">"</span><span style="color: #F1FA8C">Predicted Mortality Risk</span><span style="color: #E9F284">"</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">ax.legend()</span></span>
<span class="line"><span style="color: #F8F8F2">ax.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.tight_layout()</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
<span class="line"></span>
```

![Sensitivity Analysis Tornado Plot with Clinical Inputs](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_tornado_plot-1024x640.png)

[Return to Techniques Index](#Top)

### Bayesian Sensitivity Analysis with PyMC

Unlike traditional models that assess feature importance through direct modification and outcome evaluation, the Bayesian method takes a distinct approach.

It treats inputs as probability distributions, which allows it to track uncertainty throughout the analysis and measure sensitivity based on posterior distributions.

While this approach is computationally intensive, it works particularly well with small datasets and provides full probability distributions instead of simple point estimates.

In Python, this analysis can be performed using the PyMC and ArviZ libraries

import pymc as pm
import arviz as az
import numpy as np
import matplotlib.pyplot as plt

# 1. Simulate synthetic clinical data (100 patients)
np.random.seed(42)
n = 100
age = np.random.normal(70, 10, n)
creatinine = np.random.normal(1.2, 0.3, n)
ejection_fraction = np.random.normal(45, 10, n)
nyha_class = np.random.randint(1, 5, n)

# Generate binary outcome (mortality) based on a latent logistic model
logit = (
    0.04 * age +
    0.9 * creatinine +
    0.5 * nyha_class -
    0.06 * ejection_fraction
)
prob = 1 / (1 + np.exp(-logit))
mortality = np.random.binomial(1, prob)

# 2. Fit Bayesian logistic regression with PyMC
with pm.Model() as model:
    # Priors
    beta_age = pm.Normal('beta_age', mu=0, sigma=1)
    beta_creat = pm.Normal('beta_creat', mu=0, sigma=1)
    beta_ef = pm.Normal('beta_ef', mu=0, sigma=1)
    beta_nyha = pm.Normal('beta_nyha', mu=0, sigma=1)
    intercept = pm.Normal('intercept', mu=0, sigma=1)

    # Linear model
    logit_p = (intercept +
               beta_age * age +
               beta_creat * creatinine +
               beta_ef * ejection_fraction +
               beta_nyha * nyha_class)

    # Likelihood
    p = pm.Deterministic('p', pm.math.sigmoid(logit_p))
    y_obs = pm.Bernoulli('y_obs', p=p, observed=mortality)

    # Sampling
    trace = pm.sample(1000, tune=1000, target_accept=0.95, return_inferencedata=True)

# 3. Plot posterior distributions
az.plot_posterior(trace, var_names=['beta_age', 'beta_creat', 'beta_ef', 'beta_nyha', 'intercept'], hdi_prob=0.95)
plt.tight_layout()
plt.show()
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> pymc </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> pm</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> arviz </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> az</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Simulate synthetic clinical data (100 patients)</span></span>
<span class="line"><span style="color: #F8F8F2">np.random.seed(</span><span style="color: #BD93F9">42</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">n </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">100</span></span>
<span class="line"><span style="color: #F8F8F2">age </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.random.normal(</span><span style="color: #BD93F9">70</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">10</span><span style="color: #F8F8F2">, n)</span></span>
<span class="line"><span style="color: #F8F8F2">creatinine </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.random.normal(</span><span style="color: #BD93F9">1.2</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">0.3</span><span style="color: #F8F8F2">, n)</span></span>
<span class="line"><span style="color: #F8F8F2">ejection_fraction </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.random.normal(</span><span style="color: #BD93F9">45</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">10</span><span style="color: #F8F8F2">, n)</span></span>
<span class="line"><span style="color: #F8F8F2">nyha_class </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.random.randint(</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9">5</span><span style="color: #F8F8F2">, n)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># Generate binary outcome (mortality) based on a latent logistic model</span></span>
<span class="line"><span style="color: #F8F8F2">logit </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #BD93F9">0.04</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> age </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #BD93F9">0.9</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> creatinine </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #BD93F9">0.5</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> nyha_class </span><span style="color: #FF79C6">-</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #BD93F9">0.06</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> ejection_fraction</span></span>
<span class="line"><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">prob </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> np.exp(</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">logit))</span></span>
<span class="line"><span style="color: #F8F8F2">mortality </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.random.binomial(</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, prob)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Fit Bayesian logistic regression with PyMC</span></span>
<span class="line"><span style="color: #FF79C6">with</span><span style="color: #F8F8F2"> pm.Model() </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> model:</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Priors</span></span>
<span class="line"><span style="color: #F8F8F2">    beta_age </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.Normal(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">mu</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">sigma</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    beta_creat </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.Normal(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_creat</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">mu</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">sigma</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    beta_ef </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.Normal(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_ef</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">mu</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">sigma</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    beta_nyha </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.Normal(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_nyha</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">mu</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">sigma</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">    intercept </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.Normal(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">intercept</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">mu</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">sigma</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Linear model</span></span>
<span class="line"><span style="color: #F8F8F2">    logit_p </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (intercept </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">               beta_age </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> age </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">               beta_creat </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> creatinine </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">               beta_ef </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> ejection_fraction </span><span style="color: #FF79C6">+</span></span>
<span class="line"><span style="color: #F8F8F2">               beta_nyha </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> nyha_class)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Likelihood</span></span>
<span class="line"><span style="color: #F8F8F2">    p </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.Deterministic(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">p</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, pm.math.sigmoid(logit_p))</span></span>
<span class="line"><span style="color: #F8F8F2">    y_obs </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.Bernoulli(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">y_obs</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">p</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">p, </span><span style="color: #FFB86C; font-style: italic">observed</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">mortality)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #6272A4"># Sampling</span></span>
<span class="line"><span style="color: #F8F8F2">    trace </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pm.sample(</span><span style="color: #BD93F9">1000</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">tune</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">1000</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">target_accept</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0.95</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">return_inferencedata</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Plot posterior distributions</span></span>
<span class="line"><span style="color: #F8F8F2">az.plot_posterior(trace, </span><span style="color: #FFB86C; font-style: italic">var_names</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_creat</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_ef</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">beta_nyha</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">intercept</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">], </span><span style="color: #FFB86C; font-style: italic">hdi_prob</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">0.95</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.tight_layout()</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
<span class="line"></span>
```

![Bayesian Sensitivity Analysis Plot](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_bayesian-1024x514.png)

[Return to Techniques Index](#Top)

### Design of Experiments (DoE) and ANOVA Sensitivity Analysis

Design of Experiments (DoE) is a statistical methodology for planning and structuring experiments, whether physical or simulated.

In a typical scenario, variables that influence risk are tested at their minimum and maximum values to measure their impact on outcomes.

This testing can be conducted through several approaches:

Full factorial: examines all possible combinations

Fractional factorial: analyzes a strategic subset of combinations

Plackett-Burman: identifies and prioritizes the most influential variables

Central composite: specifically designed for non-linear models

Once the DoE-based testing is complete, [ANOVA](https://www.micheledpierri.com/wp-content/uploads/wp-mfa-exports/page/one-way-anova.md) quantifies each variable’s influence on the output.

In summary, DoE structures the experimental design by identifying relevant test variables, while ANOVA measures how these variables contribute to output variation.

In the following Python example, we’ll execute the design manually for simplicity:

import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.formula.api import ols
import matplotlib.pyplot as plt

# 1. Manually create a 2-level full factorial design (3 variables → 8 combinations)
design = np.array([
    [-1, -1, -1],
    [-1, -1,  1],
    [-1,  1, -1],
    [-1,  1,  1],
    [ 1, -1, -1],
    [ 1, -1,  1],
    [ 1,  1, -1],
    [ 1,  1,  1]
])
design_df = pd.DataFrame(design, columns=['age', 'creatinine', 'ef'])

# 2. Rescale to realistic clinical values
design_df['age'] = (design_df['age'] + 1) * (85 - 50)/2 + 50
design_df['creatinine'] = (design_df['creatinine'] + 1) * (2.5 - 0.6)/2 + 0.6
design_df['ef'] = (design_df['ef'] + 1) * (70 - 20)/2 + 20

# 3. Simulate model output (mortality risk)
def clinical_model(row):
    logit = 0.04 * row['age'] + 0.9 * row['creatinine'] - 0.06 * row['ef']
    prob = 1 / (1 + np.exp(-logit))
    return prob

design_df['mortality'] = design_df.apply(clinical_model, axis=1)

# 4. Fit linear model with interactions
formula = 'mortality ~ age + creatinine + ef + age:creatinine + age:ef + creatinine:ef'
model = ols(formula, data=design_df).fit()

# 5. Perform ANOVA
anova_table = sm.stats.anova_lm(model, typ=2)
anova_table['Percent'] = 100 * anova_table['sum_sq'] / anova_table['sum_sq'].sum()

# 6. Plot percentage of variance explained
anova_table = anova_table.sort_values(by='Percent', ascending=True)
anova_table['Percent'].plot(kind='barh', figsize=(8,5))
plt.xlabel('% of Variance Explained')
plt.title('ANOVA Sensitivity Analysis (Manual Design)')
plt.grid(True)
plt.tight_layout()
plt.show()
```
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> numpy </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> np</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> pandas </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> pd</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> statsmodels.api </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> sm</span></span>
<span class="line"><span style="color: #FF79C6">from</span><span style="color: #F8F8F2"> statsmodels.formula.api </span><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> ols</span></span>
<span class="line"><span style="color: #FF79C6">import</span><span style="color: #F8F8F2"> matplotlib.pyplot </span><span style="color: #FF79C6">as</span><span style="color: #F8F8F2"> plt</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 1. Manually create a 2-level full factorial design (3 variables → 8 combinations)</span></span>
<span class="line"><span style="color: #F8F8F2">design </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> np.array([</span></span>
<span class="line"><span style="color: #F8F8F2">    [</span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    [</span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    [</span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    [</span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    [ </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    [ </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    [ </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">, </span><span style="color: #FF79C6">-</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">],</span></span>
<span class="line"><span style="color: #F8F8F2">    [ </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">,  </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">])</span></span>
<span class="line"><span style="color: #F8F8F2">design_df </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> pd.DataFrame(design, </span><span style="color: #FFB86C; font-style: italic">columns</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ef</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">])</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 2. Rescale to realistic clinical values</span></span>
<span class="line"><span style="color: #F8F8F2">design_df[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (design_df[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">85</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">50</span><span style="color: #F8F8F2">)</span><span style="color: #FF79C6">/</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">50</span></span>
<span class="line"><span style="color: #F8F8F2">design_df[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (design_df[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">2.5</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.6</span><span style="color: #F8F8F2">)</span><span style="color: #FF79C6">/</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.6</span></span>
<span class="line"><span style="color: #F8F8F2">design_df[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ef</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> (design_df[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ef</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">) </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">70</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">20</span><span style="color: #F8F8F2">)</span><span style="color: #FF79C6">/</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">20</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 3. Simulate model output (mortality risk)</span></span>
<span class="line"><span style="color: #FF79C6">def</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">clinical_model</span><span style="color: #F8F8F2">(</span><span style="color: #FFB86C; font-style: italic">row</span><span style="color: #F8F8F2">):</span></span>
<span class="line"><span style="color: #F8F8F2">    logit </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.04</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> row[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">age</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.9</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> row[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">creatinine</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0.06</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> row[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ef</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">]</span></span>
<span class="line"><span style="color: #F8F8F2">    prob </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> (</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">+</span><span style="color: #F8F8F2"> np.exp(</span><span style="color: #FF79C6">-</span><span style="color: #F8F8F2">logit))</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> prob</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">design_df[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">mortality</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> design_df.apply(clinical_model, </span><span style="color: #FFB86C; font-style: italic">axis</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">1</span><span style="color: #F8F8F2">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 4. Fit linear model with interactions</span></span>
<span class="line"><span style="color: #F8F8F2">formula </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">mortality ~ age + creatinine + ef + age:creatinine + age:ef + creatinine:ef</span><span style="color: #E9F284">'</span></span>
<span class="line"><span style="color: #F8F8F2">model </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> ols(formula, </span><span style="color: #FFB86C; font-style: italic">data</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">design_df).fit()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 5. Perform ANOVA</span></span>
<span class="line"><span style="color: #F8F8F2">anova_table </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> sm.stats.anova_lm(model, </span><span style="color: #FFB86C; font-style: italic">typ</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">2</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">anova_table[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Percent</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">100</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> anova_table[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">sum_sq</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">] </span><span style="color: #FF79C6">/</span><span style="color: #F8F8F2"> anova_table[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">sum_sq</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">].sum()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4"># 6. Plot percentage of variance explained</span></span>
<span class="line"><span style="color: #F8F8F2">anova_table </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> anova_table.sort_values(</span><span style="color: #FFB86C; font-style: italic">by</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Percent</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">ascending</span><span style="color: #FF79C6">=</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">anova_table[</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">Percent</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">].plot(</span><span style="color: #FFB86C; font-style: italic">kind</span><span style="color: #FF79C6">=</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">barh</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">, </span><span style="color: #FFB86C; font-style: italic">figsize</span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2">(</span><span style="color: #BD93F9">8</span><span style="color: #F8F8F2">,</span><span style="color: #BD93F9">5</span><span style="color: #F8F8F2">))</span></span>
<span class="line"><span style="color: #F8F8F2">plt.xlabel(</span><span style="color: #E9F284">'</span><span style="color: #BD93F9">% o</span><span style="color: #F1FA8C">f Variance Explained</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.title(</span><span style="color: #E9F284">'</span><span style="color: #F1FA8C">ANOVA Sensitivity Analysis (Manual Design)</span><span style="color: #E9F284">'</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.grid(</span><span style="color: #BD93F9">True</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">plt.tight_layout()</span></span>
<span class="line"><span style="color: #F8F8F2">plt.show()</span></span>
<span class="line"></span>
```

![Sensitivity Analysis with Anova Plot](https://www.micheledpierri.com/wp-content/uploads/2025/04/sensitivity_analysis_anova-1024x640.png)

[Return to Techniques Index](#Top)

## Summary of Sensitivity Analysis Technique



| Method | Type | Global ? | Interaction? | Model-Agnostic | Key Strength | Main Limitation |
| --- | --- | --- | --- | --- | --- | --- |
| One-at-a-Time (OAT) | Determin. | No | No | Yes | Simple and fast | Misses interactions and non inearities |
| Sobol’ Analysis | Variance-based | Yes | Yes | Yes | Full variance decomposition | Computationally intensive |
| FAST | Spectral | Yes | No | Yes | Efficient for main effects | Can’t capture interactions (unless eFAST) |
| Regression-based (SRC, PCC) | Statistical | Partial | No | Yes | Easy to interpret | Assumes linear relationships |
| SHAP Values | Additive ML | Yes | Yes | Yes | Local + global interpretability | Computationally heavy on large models |
| Random Forest Feature Importance | Tree-based ML | Yes | Partial | Partial | Built-in in tree models | Can be biased or misleading |
| Tornado Plot | Visual Determin. | No | No | Yes | Great for presentations and audits | Lacks statistical rigor |
| Bayesian Sensitivity (PyMC, Prob. Mod.) | Probabilistic | Yes | Yes | Yes | Accounts for uncertainty in inputs | Requires full probabilistic modeling |
| DoE + ANOVA | Statistical Design | Yes | Yes | Yes | Captures interaction effects explicitly | Requires structured input levels |
| Monte Carlo + Correlation | Sampling-based | Yes | No | Yes | Easy to implement | Only captures monotonic trends |

## Conclusion

Sensitivity analysis is an essential tool for the evaluation and interpretation of clinical predictive models. It not only improves accuracy but also helps understand their internal structure and behavior for input variable uncertainty. Specifically, it allows:

- Identifying which variables have the greatest influence on an outcome (e.g., post-operative mortality)

- Quantifying the relative importance and interactive or synergistic relationships between clinical factors

- Supporting the development of transparent models that are explainable and clinically justifiable

- Improving robustness and confidence in model-based decision-making