---
title: "BSCB-vignette"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{BSCB-vignette}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
# Bayesian simultaneous credible bands for the polynomial model
## Overview
This demo illustrates how to use the **BSCB** package to construct and
evaluate Bayesian simultaneous credible bands (BSCB) and Bayesian pointwise
credible bands (BPCB) for polynomial regression. These methods are demonstrated:
- **BSCB-C**: BSCB under the Normal-Gamma conjugate prior
- **BSCB-I-J**: BSCB under the independent Jeffreys prior
- **BPCB-I-J**: BPCB under the independent Jeffreys prior
```{r setup}
library(BSCB)
```
## 1. Simulate Data
We simulate data from a quadratic regression model
$Y_i = \theta_0 + \theta_1 x_i + \theta_2 x_i^2 + \varepsilon_i$,
where $\varepsilon_i \sim N(0, \sigma^2)$.
Notably, $L$ should set to $L=500,000$, whereas here $L=5000$ just to show a quick example.
```{r}
# Simulate data from a quadratic model using a D-optimal covariate design
theta_true <- c(-6, -3, 0.25)
alpha <- 0.05
a <- -0.5
b <- 0.5
L <- 5000
p <- 2
n <- 20
e_sd <- 0.2
sim_data <- generate_simulation_data(
p = p,
n = n,
e_sd = e_sd,
theta_true = theta_true,
a = a,
b = b,
replication = 1,
design_index = 2,
center_index = 1
)
X <- sim_data$X
x <- sim_data$X[,2]
Y <- as.numeric(sim_data$Y.list[[1]])
```
```{r}
# You could also generate data in the simple way
# set.seed(123)
# n <- 20
# x <- seq(-0.5, 0.5, length.out = n)
# X <- cbind(1, x, x^2)
# theta_true <- c(-6, -3, 0.25)
# Y <- as.numeric(X %*% theta_true + rnorm(n, sd = 0.2))
```
## 2. Construct the Bands
### BSCB-C: BSCB under the Normal-Gamma conjugate prior
The Normal-Gamma conjugate prior supports three hyperparameter specifications:
`"empirical"` (empirical Bayes), `"unit_info"` (unit-information prior), and
`"g_prior"` (Zellner's g-prior). The critical constant $\lambda$ is estimated
via $L$ Monte Carlo draws.
```{r}
# --- BSCB-C: Bayesian simultaneous credible bands under the Normal-Gamma conjugate prior ---
fit_c <- compute_bscb_conjugate(
X = X,
Y = Y,
alpha = alpha,
a = a,
b = b,
L = L,
theta_true = theta_true,
hyperparameter = "g_prior", # "empirical", "unit_info", or "g_prior"
optimize_type = "P" # "P" = polyroot (recommended)
)
cat("Critical constant (BSCB-C):", fit_c$lambda, "\n")
cat("Posterior mean of theta:\n")
print(round(fit_c$mu_star, 4))
```
### BSCB-I-J: BSCB under the independent Jeffreys prior
BSCB-I-J is equivalent to the FSCB in Liu et al.(2008) for finite sample sizes.
```{r}
# --- BSCB-I-J: Bayesian simultaneous credible bands under the Independent Jeffreys prior ---
fit_j <- compute_bscb_ind_jeffreys(
X = X,
Y = Y,
alpha = alpha,
a = a,
b = b,
theta_true = theta_true,
L = L
)
cat("Critical constant (BSCB-J):", fit_j$lambda, "\n")
```
### BSCB-H-C: BSCB under the normal-half-Cauchy prior(0,2) implemented via HMC
If you would like to produce BSCB-H-C, you can use the following codes.
```{r}
# mod <- instantiate::stan_package_model(
# name = "HMC_model",
# package = "BSCB",
# compile = TRUE
# )
```
```{r}
# --- BSCB-H-C: BSCB under the normal-half-Cauchy prior(0,2) implemented via HMC ---
# fit_h <- compute_bscb_hmc(
# X = X,
# Y = Y,
# V = diag(n),
# alpha = alpha,
# a = a,
# b = b,
# theta_true = theta_true,
# prior_type = "normal_half_cauchy",
# L = L,
# draw_num = 10000
# )
#
# cat("Critical constant (BSCB-H-C):", fit_h$lambda, "\n")
```
### BPCB-I-J: BPCB under the independent Jeffreys prior
BPCB-I-J is constructed by connecting confidence intervals at each individual points in the covariate domain. It's also equivalent to the FPCB.
```{r}
# --- BPCB-I-J: Bayesian pointwise credible bands under the Independent Jeffreys prior ---
fit_p <- compute_bpcb_ind_jeffreys(
X = X,
Y = Y,
alpha = alpha,
a = a,
b = b,
theta_true = theta_true
)
```
## 3. Evaluate Bands over a Grid and Plot
```{r,fig.width=8, fig.height=6, out.width="100%"}
library(ggplot2)
x_seq <- seq(-0.5, 0.5, length.out = 500)
y_true <- as.numeric(cbind(1, x_seq, x_seq^2) %*% theta_true)
df_obs <- data.frame(x = x, Y = as.numeric(Y))
# Collect all band boundaries into a single data frame
df_bands <- data.frame(
x = rep(x_seq, 3),
lower = c(as.numeric(fit_c$lower_bound(x_seq)),
as.numeric(fit_j$lower_bound(x_seq)),
as.numeric(fit_p$lower_bound(x_seq))),
upper = c(as.numeric(fit_c$upper_bound(x_seq)),
as.numeric(fit_j$upper_bound(x_seq)),
as.numeric(fit_p$upper_bound(x_seq))),
method = rep(c("BSCB-C-G",
"BSCB-I-J",
"BPCB-I-J"),
each = length(x_seq))
)
df_true <- data.frame(x = x_seq, y = y_true)
band_colours <- c(
"BSCB-C-G" = "#4DAF4A",
"BSCB-I-J" = "#E41A1C",
"BPCB-I-J" = "#377EB8"
)
band_linetypes <- c(
"BSCB-C-G" = "F1",
"BSCB-I-J" = "dotdash",
"BPCB-I-J" = "solid"
)
ggplot() +
# Shaded credible regions
geom_ribbon(
data = df_bands,
mapping = aes(x = x, ymin = lower, ymax = upper,
fill = method),
alpha = 0.10
) +
# Band boundaries
geom_line(
data = df_bands,
mapping = aes(x = x, y = lower,
colour = method,
linetype = method),
linewidth = 0.8
) +
geom_line(
data = df_bands,
mapping = aes(x = x, y = upper,
colour = method,
linetype = method),
linewidth = 0.8
) +
# True regression curve
geom_line(
data = df_true,
mapping = aes(x = x, y = y),
colour = "navyblue",
linewidth = 0.7,
linetype = "solid"
) +
# Observed data
geom_point(
data = df_obs,
mapping = aes(x = x, y = Y),
colour = "gray50",
size = 1.5
) +
scale_colour_manual(
values = band_colours,
name = "Method",
breaks = c("BSCB-C-G", "BSCB-I-J", "BPCB-I-J")) +
scale_fill_manual(
values = band_colours,
name = "Method",
breaks = c("BSCB-C-G", "BSCB-I-J", "BPCB-I-J")) +
scale_linetype_manual(
values = band_linetypes,
name = "Method",
breaks = c("BSCB-C-G", "BSCB-I-J", "BPCB-I-J")) +
labs(
title = "95% BSCB-C-G, BSCB-I-J and BPCB-I-J for Quadratic Regression",
x = "x",
y = "y"
) +
theme_bw() +
theme(legend.position = "bottom",
plot.title = element_text(hjust = 0.5))
```
## 4. Evaluate Coverage
### Empirical Simultaneous Coverage Rate (ESCR)
`coverage_ESCR()` returns 1 if the true regression function lies within the
band at all points in $[a, b]$, and 0 otherwise.
```{r}
escr_j <- coverage_ESCR(fit_j, optimize_type = "P", verbose = TRUE)
escr_c <- coverage_ESCR(fit_c, optimize_type = "P", verbose = TRUE)
escr_p <- coverage_ESCR(fit_p, optimize_type = "P", verbose = TRUE)
cat("ESCR (BSCB-I-J):", escr_j, "\n")
cat("ESCR (BSCB-C-G):", escr_c, "\n")
cat("ESCR (BPCB-I-J):", escr_p, "\n")
```
### Posterior Simultaneous Coverage Probability (PSCP)
`coverage_PSCP()` estimates the proportion of posterior draws for which
$\sup_{x \in [a,b]} T(x) \leq \lambda$.
```{r}
pscp_j <- coverage_PSCP(fit_j, draw_num = 10000,
optimize_type = "P", verbose = TRUE)
pscp_c <- coverage_PSCP(fit_c, draw_num = 10000,
optimize_type = "P", verbose = TRUE)
pscp_p <- coverage_PSCP(fit_p, draw_num = 10000,
optimize_type = "P", verbose = TRUE)
cat("PSCP (BSCB-I-J):", round(pscp_j, 4), "\n")
cat("PSCP (BSCB-C-G):", round(pscp_c, 4), "\n")
cat("PSCP (BPCB-I-J):", round(pscp_p, 4), "\n")
```
### Summary Table
```{r}
summary_tab <- data.frame(
Method = c("BSCB-C-G", "BSCB-I-J"),
Lambda = round(c(fit_c$lambda, fit_j$lambda), 4),
ESCR = c(escr_c, escr_j),
PSCP = round(c(pscp_c, pscp_j), 4)
)
knitr::kable(summary_tab, caption = "Coverage summary for one simulated dataset")
```