Skip to contents

The core functions of binaryRL have already been rewritten in Rcpp. This optimization reduces the runtime to roughly one-tenth of the previous R-based for-loop implementation. As a result, running MCMC with LaplacesDemon on binaryRL is very fast, while still allowing users to easily define and customize their reinforcement learning models.

Example Data

test_data <- binaryRL::Mason_2024_G2
# Ensure the Subject ID is a numeric value for easier indexing
test_data$Subject <- as.numeric(test_data$Subject)
# Filter the dataset to include only subjects 1 through 5
test_data <- test_data[test_data$Subject <= 5, ]

unique(test_data$Subject)
## [1] 1 2 3 4 5

Object Function

Argument 1: parm

Similar to other frameworks designed for individual-level analysis, the primary argument to the LaplacesDemon model function is a single vector of free parameters. Crucially, this vector does not represent the parameters for a single “black-box” function call. Instead, it is a concatenated vector containing the complete set of free parameters for every subject being modeled. Consequently, its total length is the number of free parameters per subject multiplied by the number of subjects.

Argument 2: Data

A key convenience of LaplacesDemon is that the user-defined Model function can accept a Data argument. This feature provides a clean and explicit mechanism for passing supplementary information—such as prior distributions, trial counts, or policy settings—directly to the model. Consequently, users can avoid more cumbersome methods, like environment binding, and instead bundle all necessary data and constants into a single, well-organized list.

Data <- list(
  # The prepared dataframe containing the experimental data
  df = test_data,
  # A vector of unique subject IDs to loop over during modeling
  id = unique(test_data$Subject),
  # Need to constrain the free parameters with Laplace
  priors = NULL,
  # The number of free parameters per subject (eta and tau)
  n_params = 2,
  # The total number of trials per subject in the experiment
  n_trials = 360,
  # The operational mode for the binaryRL::run_m function, set to "fit"
  mode = "fit",
  # The learning policy for binaryRL::run_m, set to "off-policy"
  policy = "off",
  # A vector of descriptive names for each parameter across all subjects
  parm.names = LaplacesDemon::as.parm.names(
    list(
      # Creates names like "[1,1]", "[1,2]", "[2,1]", "[2,2]", etc.
      outer(
        X = c("eta", "tau"),
        Y = 1:length(unique(test_data$Subject)),
        FUN = paste, sep = ""
      )
    )
  ),
  # The names of variables to monitor during the MCMC run (Log Posterior)
  mon.names = "LP",
  # The total number of subjects
  N = length(unique(test_data$Subject))
)

Defining Object Function

Create a model function that accepts parm and Data as its arguments, with a return value that conforms to the structure required by LaplacesDemon. Within this function, you need to calculate the sum of the log-likelihoods and the sum of the log-posteriors across all subjects by using specified prior distributions.

Model <- function(parm, Data) {
  
  # Initialize accumulators for sum of log-likelihood and log-posterior
  sum_LL <- 0
  sum_LP <- 0
  
  # Loop through each subject ID provided in the Data list
  for (i in as.vector(Data$id)) {
    
    # read unconstrained params
    eta_unconstrained <- parm[(i - 1) * 2 + 1]
    tau_unconstrained <- parm[(i - 1) * 2 + 2]
    
    # convert to constrained params
    eta <- stats::plogis(eta_unconstrained)
    tau <- exp(tau_unconstrained)
    
    # pass constrained params to the input vector
    #parm[(i - 1) * 2 + 1] <- eta
    #parm[(i - 1) * 2 + 2] <- tau
    
    res <- binaryRL::run_m(
      data = Data$df,
      id = Data$id[i],
      eta = eta,
      tau = tau,
      priors = Data$priors,
      n_params = Data$n_params,
      n_trials = Data$n_trials,
      mode = Data$mode,
      policy = Data$policy
    )
    
    # Add the log-likelihood of the current subject to the total
    sum_LL <- sum_LL + res$ll
    # Add the log-posterior of the current subject to the total
    LP_eta <- stats::dnorm(x = eta, mean = 0.5, sd = 0.15, log = TRUE)
    LP_tau <- stats::dexp(x = tau, rate = 1, log = TRUE)
    sum_LP <- sum_LP + LP_eta + LP_tau
  }
  
  LP <- sum_LL + sum_LP
  
  # Return a list in the format required by LaplacesDemon
  return(
    list(
      LP = LP,
      Dev = -2 * sum_LL,
      Monitor = c(LP = LP),
      yhat = NULL,
      parm = parm
    )
  )
}

Test Object Function

Initial.Values <- stats::runif(Data$N * Data$n_params)
Model(parm = Initial.Values, Data = Data)
## $LP
## [1] -1635.234
## 
## $Dev
## [1] 3260.12
## 
## $Monitor
##        LP 
## -1635.234 
## 
## $yhat
## NULL
## 
## $parm
##  [1] 0.080750138 0.834333037 0.600760886 0.157208442 0.007399441 0.466393497
##  [7] 0.497777389 0.289767245 0.732881987 0.772521511

Run LaplacesDemon

Fit <- LaplacesDemon::LaplacesDemon(
  Model = Model,
  Data = Data,
  Packages = "binaryRL",
  # Set initial values (# of free params * # of subjects)
  Initial.Values = stats::runif(length(Data$id) * Data$n_params),
  # The relationship between `Algorithm` and `Specs` is special
  # Refer to the LaplacesDemon help documents for details
  Algorithm = "MWG", Specs = list(B = NULL),
  #Algorithm = "NUTS", Specs = list(A = 5, delta = 0.6, epsilon = NULL, Lmax = Inf),
  # The total number of iterations to perform in each chain.
  Iterations = 10,
  # The thinning interval. A value of n keeps one sample every n-th iteration.
  Thinning = 1,
  # Logs for every ? iterations
  Status = 1
)

LaplacesDemon::Consort(Fit)
#############################################################
# Consort with Laplace's Demon                              #
#############################################################
Call:
LaplacesDemon::LaplacesDemon(Model = Model, Data = Data, Initial.Values = stats::runif(length(Data$id) * 
    Data$n_params), Iterations = 10, Status = 1, Thinning = 1, 
    Algorithm = "MWG", Specs = list(B = NULL), Packages = "binaryRL")

Acceptance Rate: 0.29091
Algorithm: Metropolis-within-Gibbs
Covariance Matrix: (NOT SHOWN HERE; diagonal shown instead)
    [1,1]     [2,1]     [1,2]     [2,2]     [1,3]     [2,3]     [1,4]     [2,4]     [1,5]     [2,5] 
0.5670132 0.5670132 0.5670132 0.5670132 0.5670132 0.5670132 0.5670132 0.5670132 0.5670132 0.5670132 

Covariance (Diagonal) History: (NOT SHOWN HERE)
Deviance Information Criterion (DIC):
          All Stationary
Dbar 3355.960   3355.960
pD    378.877    378.877
DIC  3734.837   3734.837
Initial Values:
 [1] 0.09859165 0.69300856 0.75132556 0.99870695 0.34549739 0.29300554 0.66773598 0.59801099 0.70105000 0.10186876

Iterations: 11
Log(Marginal Likelihood): NA
Minutes of run-time: 1.39
Model: (NOT SHOWN HERE)
Monitor: (NOT SHOWN HERE)
Parameters (Number of): 10
Posterior1: (NOT SHOWN HERE)
Posterior2: (NOT SHOWN HERE)
Recommended Burn-In of Thinned Samples: 0
Recommended Burn-In of Un-thinned Samples: 0
Recommended Thinning: 10
Specs: (NOT SHOWN HERE)
Status is displayed every 1 iterations
Summary1: (SHOWN BELOW)
Summary2: (SHOWN BELOW)
Thinned Samples: 11
Thinning: 1


Summary of All Samples
                  Mean          SD        MCSE          ESS            LB        Median            UB
[1,1]    -6.326389e-01  0.00000000  0.00000000 2.220446e-16 -6.326389e-01 -6.326389e-01    -0.6326389
[2,1]     6.930086e-01  0.00000000  0.00000000 2.220446e-16  6.930086e-01  6.930086e-01     0.6930086
[1,2]    -7.464188e-03  0.19969803  0.09095104 1.100000e+01 -9.168507e-02 -9.168507e-02     0.4487322
[2,2]     9.987070e-01  0.00000000  0.00000000 2.220446e-16  9.987070e-01  9.987070e-01     0.9987070
[1,3]     6.445531e-01  0.00000000  0.00000000 2.220446e-16  6.445531e-01  6.445531e-01     0.6445531
[2,3]    -5.424715e-01  0.31279239  0.14245907 1.100000e+01 -6.743889e-01 -6.743889e-01     0.1720812
[1,4]     6.677360e-01  0.00000000  0.00000000 2.220446e-16  6.677360e-01  6.677360e-01     0.6677360
[2,4]    -2.994002e-01  0.30341356  0.22470223 2.220446e-16 -7.339427e-01 -2.994002e-01     0.1351423
[1,5]     7.705329e-01  0.04985889  0.03392331 3.014025e+00  6.831834e-01  8.100874e-01     0.8100874
[2,5]     4.602979e-01  0.59291341  0.43910024 2.220446e-16 -3.888602e-01  4.602979e-01     1.3094560
Deviance  3.355960e+03 27.52734495 14.79642686 3.718372e+00  3.330060e+03  3.349300e+03  3415.7700000
LP       -1.683476e+03 13.59142083  7.03639451 3.903581e+00 -1.713494e+03 -1.679777e+03 -1671.8078968


Summary of Stationary Samples
                  Mean          SD        MCSE          ESS            LB        Median            UB
[1,1]    -6.326389e-01  0.00000000  0.00000000 2.220446e-16 -6.326389e-01 -6.326389e-01    -0.6326389
[2,1]     6.930086e-01  0.00000000  0.00000000 2.220446e-16  6.930086e-01  6.930086e-01     0.6930086
[1,2]    -7.464188e-03  0.19969803  0.09095104 1.100000e+01 -9.168507e-02 -9.168507e-02     0.4487322
[2,2]     9.987070e-01  0.00000000  0.00000000 2.220446e-16  9.987070e-01  9.987070e-01     0.9987070
[1,3]     6.445531e-01  0.00000000  0.00000000 2.220446e-16  6.445531e-01  6.445531e-01     0.6445531
[2,3]    -5.424715e-01  0.31279239  0.14245907 1.100000e+01 -6.743889e-01 -6.743889e-01     0.1720812
[1,4]     6.677360e-01  0.00000000  0.00000000 2.220446e-16  6.677360e-01  6.677360e-01     0.6677360
[2,4]    -2.994002e-01  0.30341356  0.22470223 2.220446e-16 -7.339427e-01 -2.994002e-01     0.1351423
[1,5]     7.705329e-01  0.04985889  0.03392331 3.014025e+00  6.831834e-01  8.100874e-01     0.8100874
[2,5]     4.602979e-01  0.59291341  0.43910024 2.220446e-16 -3.888602e-01  4.602979e-01     1.3094560
Deviance  3.355960e+03 27.52734495 14.79642686 3.718372e+00  3.330060e+03  3.349300e+03  3415.7700000
LP       -1.683476e+03 13.59142083  7.03639451 3.903581e+00 -1.713494e+03 -1.679777e+03 -1671.8078968

Demonic Suggestion

Due to the combination of the following conditions,

1. Metropolis-within-Gibbs
2. The acceptance rate (0.2909091) is within the interval [0.15,0.5].
3. At least one target MCSE is >= 6.27% of its marginal posterior
   standard deviation.
4. At least one target distribution has an effective sample size
   (ESS) less than 100. The worst mixing chain is: [1,1] (ESS=2.220446e-16).
5. Each target distribution became stationary by
   1 iteration.

Laplace's Demon has not been appeased, and suggests
copy/pasting the following R code into the R console,
and running it.

Initial.Values <- as.initial.values(Fit)
Fit <- LaplacesDemon(Model, Data=Data, Initial.Values,
     Covar=Fit$Covar, Iterations=110, Status=7, Thinning=10,
     Algorithm="AMWG", Specs=list(B=NULL, n=11, Periodicity=34))

Laplace's Demon is finished consorting.