The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Validation and Simulation Study for the Rtwalk Package

This vignette runs the t-walk on targets whose answers are known and compares the estimates with the truth. It also shows the basic workflow of the package: running chains, checking convergence, and summarizing the output. Two further vignettes go deeper: vignette("reproducing_christen_fox") reproduces the experiments of the original article, and vignette("sampler_comparison") compares the t-walk with other general-purpose samplers.

library(Rtwalk)
set.seed(2024)

A small helper puts the posterior summaries of summary() next to the true values:

compare <- function(fit, truth, burnin_frac = 0.2) {
  s <- summary(fit, burnin_frac = burnin_frac)
  data.frame(
    parameter = s$Parameter, true_mean = truth$mean, estimate = s$Mean,
    mcse = s$MCSE, true_sd = truth$sd, sd = s$SD, ess_bulk = s$ESS_bulk,
    rhat = s$Rhat
  )
}

1. A first example: the standard normal

fit <- twalk(function(x) dnorm(x, log = TRUE), n_iter = 20000,
             x0 = -2, xp0 = 2, show_progress = FALSE)
fit
#> t-walk MCMC output
#> Iterations per chain: 20000 
#> Chains: 1 
#> Dimension: 1 
#> 
#> SAMPLER PERFORMANCE
#> Acceptance rate: 62.17%
#> Actual move rate: 62.17%
#> Accepted no-move rate: 0.00%
#> Kernel statistics: available in `$kernel_statistics`
compare(fit, list(mean = 0, sd = 1))
#>   parameter true_mean estimate   mcse true_sd     sd ess_bulk  rhat
#> 1    theta1         0  -0.0283 0.0379       1 1.0569      789 1.003

The estimate is within a few Monte Carlo standard errors (mcse) of the true value, the bulk effective sample size is large and R-hat is close to 1.

2. Correlated bivariate normal, four chains

Several chains started from dispersed points make the R-hat diagnostic informative. Starting points are given as matrices with one row per chain.

mu <- c(1, -0.5)
Sigma <- matrix(c(4, 2.1, 2.1, 2.25), 2)
log_bvn <- function(x) mvtnorm::dmvnorm(x, mu, Sigma, log = TRUE)

starts <- function() matrix(rnorm(8, sd = 5), nrow = 4)
fit_bvn <- twalk(log_bvn, n_iter = 10000, x0 = starts(), xp0 = starts(),
                 n_chains = 4, n_cores = 1, show_progress = FALSE)
compare(fit_bvn, list(mean = mu, sd = sqrt(diag(Sigma))))
#>   parameter true_mean estimate   mcse true_sd     sd ess_bulk  rhat
#> 1    theta1       1.0   1.2458 0.0734     2.0 1.8922      667 1.005
#> 2    theta2      -0.5  -0.3539 0.0535     1.5 1.4122      699 1.009

plot() shows the trace of the log posterior, which is the first thing to inspect for burn-in, and of each parameter, with one colour per chain:

plot(fit_bvn)

3. A constrained parameter: Gamma posterior

Return -Inf outside the support. Named starting points give named parameters, both inside log_posterior and in the output.

y <- c(2.1, 0.7, 1.9, 3.4, 0.4, 1.2, 2.8, 1.5)
# Exponential likelihood with rate lambda and a Gamma(2, 1) prior:
# the posterior is Gamma(2 + n, 1 + sum(y)).
log_post_rate <- function(theta) {
  if (theta[["lambda"]] <= 0) return(-Inf)
  sum(dexp(y, theta[["lambda"]], log = TRUE)) + dgamma(theta[["lambda"]], 2, 1, log = TRUE)
}

fit_rate <- twalk(log_post_rate, n_iter = 20000, x0 = c(lambda = 0.5),
                  xp0 = c(lambda = 1), show_progress = FALSE)
shape <- 2 + length(y); rate <- 1 + sum(y)
compare(fit_rate, list(mean = shape / rate, sd = sqrt(shape) / rate))
#>   parameter true_mean estimate   mcse   true_sd     sd ess_bulk rhat
#> 1    lambda 0.6666667   0.6631 0.0079 0.2108185 0.2107      814    1

4. Ten correlated dimensions

d <- 10
Sigma10 <- 0.7^abs(outer(1:d, 1:d, "-"))
mu10 <- 1:d
log_mvn <- function(x) mvtnorm::dmvnorm(x, mu10, Sigma10, log = TRUE)

fit10 <- twalk(log_mvn, n_iter = 1e5, x0 = rep(0, d), xp0 = rep(2, d),
               show_progress = FALSE, thin = 10)
compare(fit10, list(mean = mu10, sd = rep(1, d)))
#>    parameter true_mean estimate   mcse true_sd     sd ess_bulk  rhat
#> 1     theta1         1   1.2170 0.1053       1 0.9906       89 1.005
#> 2     theta2         2   2.1741 0.1277       1 1.0001       64 1.008
#> 3     theta3         3   3.1228 0.1324       1 0.9865       56 1.004
#> 4     theta4         4   4.1371 0.1530       1 0.9788       44 1.008
#> 5     theta5         5   5.1238 0.1579       1 0.9792       42 1.002
#> 6     theta6         6   6.1319 0.1246       1 1.0547       79 1.002
#> 7     theta7         7   7.0902 0.1419       1 1.0357       56 1.003
#> 8     theta8         8   8.0093 0.1329       1 0.9749       57 1.015
#> 9     theta9         9   8.9690 0.0980       1 0.9010       86 1.026
#> 10   theta10        10  10.0047 0.0875       1 0.8701      100 1.017

Thinning (thin = 10) stores one iteration in ten, which reduces memory use in long runs; diagnostics are computed on the stored draws.

5. Bayesian logistic regression

Additional arguments of twalk() are passed to the log posterior.

n_obs <- 1000
X <- cbind(1, rnorm(n_obs), rnorm(n_obs, sd = 1.5))
beta_true <- c(0.5, -1.2, 0.8)
y_bin <- rbinom(n_obs, 1, plogis(X %*% beta_true))

log_post_logit <- function(beta, X, y) {
  eta <- drop(X %*% beta)
  sum(y * eta - pmax(eta, 0) - log1p(exp(-abs(eta)))) +
    sum(dnorm(beta, 0, 5, log = TRUE))
}

fit_logit <- twalk(log_post_logit, n_iter = 30000,
                   x0 = c(b0 = 0, b1 = 0, b2 = 0), xp0 = c(0.2, -0.2, 0.1),
                   X = X, y = y_bin, show_progress = FALSE)

glm_fit <- glm(y_bin ~ X - 1, family = binomial)
compare(fit_logit, list(mean = unname(coef(glm_fit)),
                        sd = unname(sqrt(diag(vcov(glm_fit))))))
#>   parameter  true_mean estimate   mcse    true_sd     sd ess_bulk  rhat
#> 1        b0  0.5778943   0.5828 0.0054 0.08083740 0.0842      245 1.018
#> 2        b1 -1.1976142  -1.2040 0.0064 0.09831746 0.1011      249 1.023
#> 3        b2  0.7625827   0.7648 0.0037 0.06478374 0.0663      326 1.019

With 1000 observations and a vague prior the posterior is close to the sampling distribution of the maximum likelihood estimate, so the two agree.

6. Continuing a run

If the diagnostics suggest that more iterations are needed, twalk_continue() extends the same Markov chain from where it stopped:

fit_more <- twalk_continue(fit_logit, log_post_logit, n_iter = 30000,
                           X = X, y = y_bin, show_progress = FALSE)
fit_more$n_iter
#> [1] 60000
summary(fit_more)$ESS_bulk
#> [1] 531 616 663

7. Interoperability

Results convert to the formats of the coda and posterior packages:

coda::effectiveSize(coda::as.mcmc(fit_logit, burnin_frac = 0.2))
#>       b0       b1       b2 
#> 278.0470 346.2790 300.5871

if (requireNamespace("posterior", quietly = TRUE)) {
  draws <- posterior::as_draws_df(fit_bvn, burnin_frac = 0.2)
  posterior::summarise_draws(draws)
}
#> # A tibble: 3 Ă— 10
#>   variable   mean median    sd   mad    q5   q95  rhat ess_bulk ess_tail
#>   <chr>     <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>    <dbl>    <dbl>
#> 1 theta1    1.25   1.21  1.89  1.87  -1.82  4.46  1.01     667.    1024.
#> 2 theta2   -0.354 -0.406 1.41  1.34  -2.63  2.04  1.01     699.     977.
#> 3 lp__     -3.57  -3.24  0.985 0.694 -5.57 -2.65  1.01     737.    1478.

8. How the two points and the rates are reported

Each t-walk chain evolves two points. The primary, time-ordered trajectory used by summary() is stored in samples, the auxiliary one in companion_samples, and the log posterior of the stored points in log_posterior.

The returned acceptance_rate follows the Metropolis–Hastings definition of the original algorithm. move_rate reports the proportion of iterations that actually changed the state, while no_move_rate reports accepted identity proposals; kernel_statistics breaks these rates down by move:

fit10$kernel_statistics
#>     kernel proposed accepted moved acceptance_rate  move_rate
#> 1 traverse    49259     2680  2369      0.05440630 0.04809273
#> 2     walk    49095    15025 14723      0.30603931 0.29988797
#> 3     blow      811       59    56      0.07274969 0.06905055
#> 4      hop      835      317   308      0.37964072 0.36886228

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.