---
title: "Sensitivity Analysis"
author: "Ndoh Penn"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Sensitivity Analysis}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, fig.width = 7, fig.height = 4,
                      message = FALSE, warning = FALSE)
library(bayprior)
```

# Why Sensitivity Analysis Is Required

Regulatory guidelines — including the FDA Draft Guidance on Bayesian Statistical
Methods (2026) and ICH E9(R1) — require demonstrating that trial conclusions are
robust to plausible variations in prior specification.

**bayprior** provides two dedicated functions:

- `sensitivity_grid()` — evaluates posterior mean, SD, and efficacy probability
- `sensitivity_cri()` — focuses on credible interval width and bounds

> **Sensitivity analysis is fully independent of conflict diagnostics.** You can
> run it without first running `prior_conflict()` by supplying `data_summary`
> directly.

---

# Supported Data Types

| `type =` | Conjugate update | `param_grid` names |
|---|---|---|
| `"binary"` | Beta-Binomial | `alpha`, `beta` |
| `"continuous"` | Normal-Normal | `mu`, `sigma` |
| `"poisson"` | Gamma-Poisson | `shape`, `rate` |
| `"survival"` | Gamma-Exponential | `shape`, `rate` |

---

# Binary Data

```{r binary-setup}
prior    <- elicit_beta(mean = 0.30, sd = 0.10, method = "moments",
                        label = "Response rate")
data_obs <- list(type = "binary", x = 14, n = 40)
```

```{r binary-grid}
sa <- sensitivity_grid(
  prior        = prior,
  data_summary = data_obs,
  param_grid   = list(alpha = seq(1, 8, 1), beta = seq(2, 20, 2)),
  target       = c("posterior_mean", "prob_efficacy"),
  threshold    = 0.30
)
sa$influence_scores
```

```{r binary-tornado}
plot_tornado(sa)
```

```{r binary-heatmap}
plot_sensitivity(sa, target = "posterior_mean")
```

## Credible Interval Sensitivity

```{r binary-cri}
cri_sa <- sensitivity_cri(
  prior        = prior,
  data_summary = data_obs,
  param_grid   = list(alpha = seq(1, 8, 1), beta = seq(2, 20, 2)),
  cri_level    = 0.95
)
cri_sa$influence_scores
plot_sensitivity(cri_sa, target = "cri_width")
```

---

# Poisson / Count Data {#poisson}

Sensitivity with Poisson data uses Gamma-Poisson conjugate updating.

```{r poisson-grid}
prior_ae  <- elicit_gamma(mean = 0.15, sd = 0.06, method = "moments",
                           label = "AE rate (per person-year)")
data_pois <- list(type = "poisson", x = 18, n = 120)

sa_pois <- sensitivity_grid(
  prior        = prior_ae,
  data_summary = data_pois,
  param_grid   = list(shape = seq(2, 10, 1), rate = seq(5, 40, 5)),
  target       = c("posterior_mean", "prob_efficacy"),
  threshold    = 0.20
)
sa_pois$influence_scores
plot_tornado(sa_pois)
```

```{r poisson-heatmap}
plot_sensitivity(sa_pois, target = "posterior_mean")
```

---

# Survival / Time-to-Event Data {#survival}

Sensitivity with survival data uses Gamma-Exponential conjugate updating.

```{r survival-grid}
prior_hz  <- elicit_exponential(mean = 0.05, method = "moments",
                                 label = "OS hazard rate")
data_surv <- list(type = "survival", x = 30, n = 600)

sa_surv <- sensitivity_grid(
  prior        = prior_hz,
  data_summary = data_surv,
  param_grid   = list(shape = seq(1, 5, 0.5), rate = seq(5, 30, 5)),
  target       = c("posterior_mean", "prob_efficacy"),
  threshold    = 0.10
)
sa_surv$influence_scores
plot_tornado(sa_surv)
```

```{r survival-cri}
cri_surv <- sensitivity_cri(
  prior        = prior_hz,
  data_summary = data_surv,
  param_grid   = list(shape = seq(1, 5, 0.5), rate = seq(5, 30, 5)),
  cri_level    = 0.95
)
plot_sensitivity(cri_surv, target = "cri_width")
```

---

# Continuous Endpoints

```{r continuous}
prior_cont <- elicit_normal(mean = 0.0, sd = 0.3, method = "moments",
                             label = "Log odds ratio")
sa_cont <- sensitivity_grid(
  prior        = prior_cont,
  data_summary = list(type = "continuous", x = 0.20, sd = 0.25, n = 60),
  param_grid   = list(mu = seq(-0.5, 0.5, 0.1), sigma = seq(0.1, 0.8, 0.1)),
  target       = c("posterior_mean", "posterior_sd")
)
plot_tornado(sa_cont)
```

---

# Interpreting Influence Scores

| Score | Sensitivity | Implication |
|---|---|---|
| < 0.05 | Not sensitive | Prior has negligible influence |
| 0.05 – 0.15 | Moderate | Report alongside primary estimate |
| > 0.15 | Sensitive | Consider robust prior; emphasise data |

---

# Mixture Prior Sensitivity

```{r mixture-sa}
e1  <- elicit_beta(mean = 0.25, sd = 0.08, method = "moments",
                   expert_id = "E1", label = "ORR")
e2  <- elicit_beta(mean = 0.40, sd = 0.10, method = "moments",
                   expert_id = "E2", label = "ORR")
mix <- aggregate_experts(list(E1 = e1, E2 = e2), weights = c(0.5, 0.5))

sa_mix <- sensitivity_grid(
  prior        = mix,
  data_summary = list(type = "binary", x = 14, n = 40),
  param_grid   = list(alpha = seq(1, 8, 1), beta = seq(2, 16, 2)),
  target       = "posterior_mean"
)
plot_tornado(sa_mix)
```

Note: for mixture priors, the grid varies the dominant component's parameters.
A compatibility warning is shown in the Shiny app.

---

# Regulatory Reporting

Include in the clinical study report:

1. Influence score table — one row per posterior quantity
2. Tornado plot — ordered from most to least sensitive
3. Heatmap — for the primary efficacy quantity
4. Classification statement — sensitive / not sensitive per quantity

All three are generated automatically by `prior_report()` when a
`bayprior_sensitivity` object is supplied.

---

# References

- FDA (2026). *Draft Guidance: Bayesian Statistical Methods for Drug and Biological Products*.
- ICH E9(R1) (2019). *Addendum on Estimands and Sensitivity Analysis*.
