The Metropolis–Hastings (MH) algorithm is a Markov Chain Monte Carlo
(MCMC) technique widely used to sample from complex probability
distributions. Specifically, this report employs a Random Walk
Metropolis–Hastings approach, in which proposed updates to the parameter
values are generated by random perturbations around the current state.
This algorithm enables efficient approximation the target distribution
by drawing a series of dependent samples.
Part 1a)
Step 1: Initialise N = 10000, s = 1, x and define the probability
density function
# Initialize total iterations, random walk step size, and initial chain value
total_iter <- 10000
proposal_sd <- 1
initial_val <- 1
# Define the target PDF (Laplace distribution)
target_pdf <- function(u) {
0.5 * exp(-abs(u))}
Step 2: Compute acceptance ratio, sample mean and standard
deviation
# For reproducible results
set.seed(2025)
# Create a vector to store the Markov chain, including the initial state
chain_vec <- numeric(total_iter + 1)
chain_vec[1] <- initial_val
# Generate the chain using a random walk Metropolis–Hastings update
for (idx in 2:(total_iter + 1)) {
# Propose from a Normal centered at the current value
candidate_val <- rnorm(1, mean = chain_vec[idx - 1], sd = proposal_sd)
# Acceptance ratio
ratio_val <- target_pdf(candidate_val) / target_pdf(chain_vec[idx - 1])
if (runif(1) < ratio_val) {
chain_vec[idx] <- candidate_val
} else {
chain_vec[idx] <- chain_vec[idx - 1]
}
}
# Compute sample mean and sample standard deviation
chain_mean <- mean(chain_vec)
chain_sd <- sd(chain_vec)
print(paste("Sample Mean:", chain_mean))
## [1] "Sample Mean: 0.10283493141666"
print(paste("Sample Standard Deviation:", chain_sd))
## [1] "Sample Standard Deviation: 1.37523764167713"
Visualizing Samples vs. True PDF
library(ggplot2)
# Convert the chain into a data frame for plotting
chain_df <- data.frame(samples = chain_vec)
# Histogram of sampled values, overlay with kernel density and the theoretical Laplace PDF
chain_plot <- ggplot(chain_df, aes(x = samples)) +
geom_histogram(aes(y = ..density..), bins = 30, fill = "black", color = "white", alpha = 0.9) +
geom_density(aes(color = "Kernel Density"), show.legend = TRUE) +
stat_function(fun = target_pdf, aes(color = "Laplace PDF"), show.legend = TRUE) +
labs(
title = "Random Walk MH Samples vs. Target PDF",
x = "Sample Values",
y = "Density"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
plot.background = element_rect(fill = "white")
) +
coord_cartesian(ylim = c(0, 0.6))
print(chain_plot)
## Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(density)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Part 1b)
Calculate the R-hat for the random walk Metropolis algorithm with N,
s and J fixed
# Define the number of parallel chains(J), chain length(N), and tiny step (S)
num_chains <- 4
chain_length <- 2000
tiny_step <- 0.001
set.seed(2025)
# Initialize vectors to store mean and variance from each chain
multi_means <- numeric(num_chains)
multi_vars <- numeric(num_chains)
for (ch in seq_len(num_chains)) {
# Set up storage for one chain
chain_rw <- numeric(chain_length + 1)
chain_rw[1] <- runif(1, min = 0, max = 10)
# Perform the Metropolis updates
for (i in 2:(chain_length + 1)) {
candidate_point <- rnorm(1, mean = chain_rw[i - 1], sd = tiny_step)
ratio_rw <- target_pdf(candidate_point) / target_pdf(chain_rw[i - 1])
if (runif(1) < ratio_rw) {
chain_rw[i] <- candidate_point
} else {
chain_rw[i] <- chain_rw[i - 1]
}
}
# Compute mean and variance for this chain
m_val <- mean(chain_rw)
v_val <- var(chain_rw)
# Regenerate chain if degenerate
while (m_val == 0 || v_val == 0) {
chain_rw <- numeric(chain_length + 1)
chain_rw[1] <- runif(1, min = 0, max = 10)
for (i in 2:(chain_length + 1)) {
candidate_point <- rnorm(1, mean = chain_rw[i - 1], sd = tiny_step)
ratio_rw <- target_pdf(candidate_point) / target_pdf(chain_rw[i - 1])
if (runif(1) < ratio_rw) {
chain_rw[i] <- candidate_point
} else {
chain_rw[i] <- chain_rw[i - 1]
}
}
m_val <- mean(chain_rw)
v_val <- var(chain_rw)
}
multi_means[ch] <- m_val
multi_vars[ch] <- v_val
}
# Print chain-wise results
for (ch_idx in seq_len(num_chains)) {
print(paste0("Chain ", ch_idx, " Mean: ", multi_means[ch_idx]))
}
## [1] "Chain 1 Mean: 7.31603240758345"
## [1] "Chain 2 Mean: 1.36686917489894"
## [1] "Chain 3 Mean: 8.39301887503724"
## [1] "Chain 4 Mean: 0.62088159832511"
for (ch_idx in seq_len(num_chains)) {
print(paste0("Chain ", ch_idx, " Variance: ", multi_vars[ch_idx]))
}
## [1] "Chain 1 Variance: 0.000241227451640372"
## [1] "Chain 2 Variance: 0.000379619349923515"
## [1] "Chain 3 Variance: 0.000150828610372669"
## [1] "Chain 4 Variance: 0.000356809962395742"
Checking if algorithm has converged using R-hat value
within_chain_var <- mean(multi_vars)
grand_mean <- mean(multi_means)
between_chain_var <- sum((multi_means - grand_mean)^2) / num_chains
r_hat_val <- sqrt((between_chain_var + within_chain_var) / within_chain_var)
print(paste("R-hat value:", r_hat_val))
## [1] "R-hat value: 206.084819413271"
print(paste("Within-chain variance (W):", within_chain_var))
## [1] "Within-chain variance (W): 0.000282121343583074"
print(paste("Grand mean (M):", grand_mean))
## [1] "Grand mean (M): 4.42420051396118"
print(paste("Between-chain variance (B):", between_chain_var))
## [1] "Between-chain variance (B): 11.9816801437582"
Keeping N and J fixed, repeat the algorithm and compute R-hat values
with different s values
set.seed(2025)
num_experiments <- 50
store_s_vals <- numeric(num_experiments)
store_r_hat <- numeric(num_experiments)
for (exp_idx in 1:num_experiments) {
# Draw a random step size from [0.001, 1]
rand_s <- runif(1, min = 0.001, max = 1)
store_s_vals[exp_idx] <- rand_s
# Prepare arrays for chain stats
chain_means_temp <- numeric(num_chains)
chain_vars_temp <- numeric(num_chains)
# Generate all chains for this step size
for (j in seq_len(num_chains)) {
local_chain <- numeric(chain_length + 1)
local_chain[1] <- runif(1, min = 1, max = 10)
for (i in 2:(chain_length + 1)) {
prop_step <- rnorm(1, mean = local_chain[i - 1], sd = rand_s)
ratio_step <- target_pdf(prop_step) / target_pdf(local_chain[i - 1])
if (runif(1) < ratio_step) {
local_chain[i] <- prop_step
} else {
local_chain[i] <- local_chain[i - 1]
}
}
temp_m <- mean(local_chain)
temp_v <- var(local_chain)
# Re-generate if degenerate
while (temp_m == 0 || temp_v == 0) {
local_chain <- numeric(chain_length + 1)
local_chain[1] <- runif(1, min = 1, max = 10)
for (i in 2:(chain_length + 1)) {
prop_step <- rnorm(1, mean = local_chain[i - 1], sd = rand_s)
ratio_step <- target_pdf(prop_step) / target_pdf(local_chain[i - 1])
if (runif(1) < ratio_step) {
local_chain[i] <- prop_step
} else {
local_chain[i] <- local_chain[i - 1]
}
}
temp_m <- mean(local_chain)
temp_v <- var(local_chain)
}
chain_means_temp[j] <- temp_m
chain_vars_temp[j] <- temp_v
}
# Compute R-hat for the current step size
w_val <- mean(chain_vars_temp)
m_val <- mean(chain_means_temp)
b_val <- sum((chain_means_temp - m_val)^2) / num_chains
store_r_hat[exp_idx] <- sqrt((b_val + w_val) / w_val)
}
# Print out the R-hat results
for (i in seq_along(store_r_hat)) {
cat("R-hat value for experiment", i, ":", store_r_hat[i], "\n")
}
## R-hat value for experiment 1 : 1.001214
## R-hat value for experiment 2 : 1.003216
## R-hat value for experiment 3 : 1.003943
## R-hat value for experiment 4 : 1.006484
## R-hat value for experiment 5 : 1.006643
## R-hat value for experiment 6 : 1.014261
## R-hat value for experiment 7 : 1.003671
## R-hat value for experiment 8 : 1.001425
## R-hat value for experiment 9 : 1.003522
## R-hat value for experiment 10 : 1.008971
## R-hat value for experiment 11 : 1.008042
## R-hat value for experiment 12 : 1.025151
## R-hat value for experiment 13 : 1.010688
## R-hat value for experiment 14 : 1.058981
## R-hat value for experiment 15 : 1.005817
## R-hat value for experiment 16 : 1.018423
## R-hat value for experiment 17 : 1.011306
## R-hat value for experiment 18 : 1.019429
## R-hat value for experiment 19 : 1.063338
## R-hat value for experiment 20 : 1.015571
## R-hat value for experiment 21 : 1.006284
## R-hat value for experiment 22 : 1.033345
## R-hat value for experiment 23 : 1.003507
## R-hat value for experiment 24 : 1.002972
## R-hat value for experiment 25 : 1.009715
## R-hat value for experiment 26 : 1.005645
## R-hat value for experiment 27 : 1.002291
## R-hat value for experiment 28 : 1.001018
## R-hat value for experiment 29 : 1.003177
## R-hat value for experiment 30 : 1.003575
## R-hat value for experiment 31 : 1.11584
## R-hat value for experiment 32 : 1.011023
## R-hat value for experiment 33 : 1.002769
## R-hat value for experiment 34 : 1.017684
## R-hat value for experiment 35 : 1.001204
## R-hat value for experiment 36 : 1.329461
## R-hat value for experiment 37 : 1.009586
## R-hat value for experiment 38 : 1.025362
## R-hat value for experiment 39 : 1.063411
## R-hat value for experiment 40 : 1.041186
## R-hat value for experiment 41 : 1.046596
## R-hat value for experiment 42 : 1.004053
## R-hat value for experiment 43 : 1.026688
## R-hat value for experiment 44 : 1.00818
## R-hat value for experiment 45 : 1.002364
## R-hat value for experiment 46 : 1.514574
## R-hat value for experiment 47 : 1.090423
## R-hat value for experiment 48 : 1.001141
## R-hat value for experiment 49 : 1.003697
## R-hat value for experiment 50 : 1.005901
Plot r-hat values over s values
# Combine s and R-hat into a data frame for ggplot
results_df <- data.frame(
step_sizes = store_s_vals,
r_hat_vals = store_r_hat
)
rhat_plot <- ggplot(results_df, aes(x = step_sizes, y = r_hat_vals)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE, color = "red") +
labs(
title = "R-hat vs. Step Size",
x = "Step Size (s)",
y = "R-hat Values"
) +
theme_minimal() +
theme(
plot.title = element_text(hjust = 0.5),
plot.background = element_rect(fill = "white")
) +
coord_cartesian(ylim = c(0, 2)) +
geom_hline(yintercept = 1.05, linetype = "dashed", color = "blue")
print(rhat_plot)
## `geom_smooth()` using formula = 'y ~ x'
