---
title: "Prior Elicitation Methods"
author: "Ndoh Penn"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Prior Elicitation Methods}
  %\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)
```

# Overview

Prior elicitation translates an expert's subjective beliefs about an unknown
quantity into a formal probability distribution. **bayprior** implements the
SHELF methodology (O'Hagan et al., 2006) across **six distribution families**
and three elicitation methods.

## Distribution Families

| Family | Support | Typical quantity |
|---|---|---|
| **Beta** | (0, 1) | Response rates, proportions |
| **Normal** | (−∞, ∞) | Mean differences, log odds ratios |
| **Gamma** | (0, ∞) | Event rates, median survival |
| **Log-Normal** | (0, ∞) | Hazard ratios, PK parameters |
| **Exponential** | (0, ∞) | Constant hazard rates, Poisson rate priors |
| **Weibull** | (0, ∞) | Non-constant hazard survival times (OS, PFS) |

## Elicitation Methods

- **Quantile matching** — expert specifies probability–value pairs
- **Moment matching** — expert specifies mean and SD
- **Roulette** — expert allocates chips across histogram bins

---

# Beta Prior (Binary Endpoints)

## Quantile Matching

```{r beta-quantile}
prior_q <- elicit_beta(
  quantiles = c("0.05" = 0.10, "0.50" = 0.30, "0.95" = 0.60),
  expert_id = "Expert_1",
  label     = "ORR (treatment arm)"
)
print(prior_q)
plot(prior_q)
```

## Moment Matching

```{r beta-moments}
prior_m <- elicit_beta(
  mean      = 0.30,
  sd        = 0.10,
  method    = "moments",
  expert_id = "Expert_1",
  label     = "ORR (treatment arm)"
)
print(prior_m)
```

Moment matching solves analytically:
$$\alpha = \mu \left(\frac{\mu(1-\mu)}{\sigma^2} - 1\right), \quad
\beta = (1-\mu)\left(\frac{\mu(1-\mu)}{\sigma^2} - 1\right)$$

---

# Normal Prior (Continuous Endpoints)

```{r normal-quantile}
prior_lor <- elicit_normal(
  quantiles = c("0.025" = -0.50, "0.50" = 0.20, "0.975" = 0.90),
  label     = "Log odds ratio"
)
plot(prior_lor)
```

```{r normal-moments}
prior_md <- elicit_normal(
  mean = 0.0, sd = 0.5, method = "moments",
  label = "Mean difference (sceptical)"
)
plot(prior_md)
```

---

# Gamma Prior (Rate and Count Endpoints)

The Gamma distribution is the conjugate prior for Poisson and exponential
likelihoods — a natural choice for event rate priors.

```{r gamma-moments}
prior_os <- elicit_gamma(
  mean = 18, sd = 6, method = "moments",
  label = "Median OS (months)"
)
plot(prior_os)
```

```{r gamma-quantile}
prior_rate <- elicit_gamma(
  quantiles = c("0.10" = 2, "0.50" = 5, "0.90" = 10),
  label     = "Event rate (per 100 patient-years)"
)
print(prior_rate)
```

$$\text{mean} = \frac{\text{shape}}{\text{rate}}, \quad
\text{SD} = \frac{\sqrt{\text{shape}}}{\text{rate}}$$

---

# Log-Normal Prior (Hazard Ratios)

```{r lognormal-quantile}
prior_hr <- elicit_lognormal(
  quantiles = c("0.05" = 0.40, "0.50" = 0.70, "0.95" = 1.20),
  label     = "Hazard ratio (treatment vs control)"
)
plot(prior_hr)
```

```{r lognormal-moments}
prior_pk <- elicit_lognormal(
  mean = 25, sd = 10, method = "moments",
  label = "AUC (ng/mL*h)"
)
print(prior_pk)
```

---

# Exponential Prior (Constant Hazard Rates) {#exponential}

The Exponential distribution models a **constant hazard rate** $\lambda > 0$.
The mean is $1/\lambda$ and the SD equals the mean. It is the conjugate prior
likelihood for Gamma priors on Poisson rates, and is appropriate when the
hazard rate is assumed constant over time.

**Use cases:** adverse event rates (events per person-time), constant-hazard
survival endpoints, Poisson rate priors for count data.

## Method: moments

```{r exp-moments}
# Mean hazard rate 0.05 => mean survival 1/0.05 = 20 months
prior_hz <- elicit_exponential(
  mean      = 0.05,
  method    = "moments",
  label     = "Hazard rate (constant)",
  expert_id = "Expert_1"
)
print(prior_hz)
plot(prior_hz)
```

## Method: rate

Direct rate specification (= $\lambda$):

```{r exp-rate}
prior_ae <- elicit_exponential(
  rate   = 0.12,
  method = "rate",
  label  = "AE rate per person-year"
)
print(prior_ae)
```

## Method: quantile

Fit the rate from expert-specified quantiles:

```{r exp-quantile}
prior_hz_q <- elicit_exponential(
  quantiles = c("0.25" = 0.02, "0.50" = 0.05, "0.75" = 0.10),
  method    = "quantile",
  label     = "Hazard rate"
)
print(prior_hz_q)
plot(prior_hz_q)
```

## Conjugate update

With Poisson/survival data:

$$\text{Prior: Exponential}(\lambda) \approx \text{Gamma}(1, \lambda)$$
$$\text{Posterior: Gamma}(1 + x,\; \lambda + n)$$

where $x$ = observed events, $n$ = exposure time or follow-up.

---

# Weibull Prior (Non-Constant Hazard Survival Times) {#weibull}

The Weibull distribution is the most widely used model for survival analysis.
It is parameterised by shape $k > 0$ and scale $\lambda > 0$:

$$\text{mean} = \lambda\,\Gamma(1 + 1/k), \quad
\text{SD} = \lambda\sqrt{\Gamma(1 + 2/k) - \Gamma(1 + 1/k)^2}$$

**Shape parameter interpretation:**

| Shape $k$ | Hazard | Clinical scenario |
|---|---|---|
| $k < 1$ | Decreasing | Early frailty, selection |
| $k = 1$ | Constant | Equivalent to Exponential |
| $k > 1$ | Increasing | Ageing, post-surgical deterioration |

## Method: moments

Specify prior mean and SD — bayprior solves for shape and scale via
Nelder-Mead optimisation:

```{r weibull-moments}
prior_wb <- elicit_weibull(
  mean      = 20,
  sd        = 10,
  method    = "moments",
  label     = "OS (months)",
  expert_id = "Expert_1"
)
print(prior_wb)
plot(prior_wb)
```

## Method: params

Direct shape and scale specification:

```{r weibull-params}
# k=2: linearly increasing hazard
prior_wb2 <- elicit_weibull(
  shape  = 2,
  scale  = 20,
  method = "params",
  label  = "PFS (months) — increasing hazard"
)
print(prior_wb2)
plot(prior_wb2)
```

## Method: quantile

```{r weibull-quantile}
prior_wb3 <- elicit_weibull(
  quantiles = c("0.10" = 5, "0.50" = 18, "0.90" = 40),
  method    = "quantile",
  label     = "OS (months)"
)
print(prior_wb3)
plot(prior_wb3)
```

## Posterior approximation note

The Weibull distribution does not have a closed-form conjugate prior. When used
in `prior_conflict()` or `sensitivity_grid()`, bayprior applies a Normal
approximation to the posterior matched to the Weibull's `fit_summary` moments.

---

# Roulette Method

The interactive chip-allocation method for clinical experts unfamiliar with
probability distributions. In the Shiny app this is fully interactive;
programmatically:

```{r roulette}
prior_rou <- elicit_roulette(
  chips  = c(0L, 1L, 3L, 7L, 9L, 7L, 4L, 2L, 1L, 1L),
  breaks = seq(0, 1, by = 0.1),
  family = "beta",
  label  = "Response rate"
)
print(prior_rou)
plot(prior_rou)
```

---

# Expert Pooling

## Linear Pooling

```{r linear-pooling}
e1 <- elicit_beta(mean = 0.25, sd = 0.08, method = "moments",
                  expert_id = "Oncologist_1", label = "ORR")
e2 <- elicit_beta(mean = 0.35, sd = 0.10, method = "moments",
                  expert_id = "Oncologist_2", label = "ORR")
e3 <- elicit_beta(
  quantiles = c("0.10" = 0.15, "0.50" = 0.30, "0.90" = 0.52),
  expert_id = "Statistician", label = "ORR"
)
consensus <- aggregate_experts(
  priors  = list(Oncologist_1 = e1, Oncologist_2 = e2, Statistician = e3),
  weights = c(0.40, 0.35, 0.25),
  method  = "linear"
)
plot(consensus)
```

## Pooling Compatibility

bayprior validates compatibility before pooling. Priors with **incompatible
supports** (e.g. Beta + Normal) are blocked with an error. Same-support
cross-family pooling (e.g. Gamma + Exponential) proceeds with a warning noting
that sensitivity analysis will use the dominant component's parameters.

```{r pooling-compat-valid}
# Same positive support — allowed (Gamma + Exponential)
g1   <- elicit_gamma(mean = 5, sd = 2, method = "moments",
                     expert_id = "E1", label = "Rate")
exp1 <- elicit_exponential(mean = 0.2, method = "moments",
                            expert_id = "E2", label = "Rate")
pool_pos <- aggregate_experts(
  priors  = list(E1 = g1, E2 = exp1),
  weights = c(0.5, 0.5)
)
print(pool_pos)
```

---

# Prior Object Structure

All `elicit_*()` functions return a `"bayprior"` S3 object:

```{r structure}
str(prior_q, max.level = 1)
```

| Slot | Content |
|---|---|
| `$dist` | Family: `"beta"`, `"normal"`, `"gamma"`, `"lognormal"`, `"exponential"`, `"weibull"`, `"mixture"` |
| `$params` | Named list of fitted hyperparameters |
| `$fit_summary` | `mean`, `sd`, `q025`, `q500`, `q975` |
| `$method` | Elicitation method |
| `$expert_id` | Expert identifier |
| `$label` | Quantity label |

---

# References

- O'Hagan, A. et al. (2006). *Uncertain Judgements: Eliciting Experts' Probabilities*. Wiley.
- Oakley, J. E. & O'Hagan, A. (2010). *SHELF: the Sheffield Elicitation Framework*. University of Sheffield.
