--- title: "`biexponential()` & `berm()`" output: rmarkdown::html_vignette: fig_width: 5 fig_height: 4 self_contained: false vignette: | %\VignetteIndexEntry{berm()} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} bibliography: references.bib csl: apa.csl --- ```{r echo = FALSE, warning=FALSE} library(YEAB) ``` ## Introduction The BERM [Bi-exponential Refractory Model, see @brackney2017] serves as a powerful tool for analyzing and understanding the organization of operant behavior in experimental treatments. Extensive empirical evidence has shown that operant behavior, even during reinforcement, is not unitary but instead organized into bouts of responses separated by relatively long pauses [e.g., @brackney2017; @shull2011]. Consequently, operant performance is determined by the rate at which bouts are initiated, the response rate within bouts, and the length of bouts. Using overall response rate as a unitary measure conflates these dissociable behavioral components. Conversely, a microscopic behavioral analysis breaks down performance into its behavioral components by decomposing overall response rate into several elementary measures. Moreover, empirical evidence suggests that the distribution of interresponse times (IRTs) is a mixture of two exponential distributions. This aligns with the notion that responses are organized into bouts: one exponential distribution describes long IRTs separating bouts, while the other describes short IRTs separating responses within bouts. Adding a refractory period between responses (the time it takes to action the operant), the bi-exponential distribution may be expressed as follows: $$p(IRT = \tau | \tau \ge \delta) = p w e^{-w (\tau - \delta)} + (1-p)b e^{-b (\tau - \delta)},\ w > b$$ Where $p$ is the proportion of IRTs within bouts and $1 − p$ is the complementary proportion of IRTs between bouts, $b$ is the bout-initiation rate, $w$ is the within-bout response rate, and $\delta$ is the refractory period. The necessity of $\delta$ is justified both theoretically, because each response takes a finite amount of time to complete, and empirically. ```{r } set.seed(43) l1 <- 1 / 10 l2 <- 1 / 40 p <- 0.4 n <- 200 delta <- 0.03 irt <- c( rexp(round(n * p), l1), rexp(round(n * (1 - p)), l2) ) + delta plot(irt) ``` ```{r} biexponential(irt) ``` ```{r} berm(irt, 0.03) ``` To compare the biexponential and BERM models, we can plot the survival functions of the original data and the simulated data from the models. The survival function represents the probability that an inter-response time (IRT) is greater than a given value. The BERM model includes a refractory period $\delta$ between responses, which can be specified as an additional parameter. The following code demonstrates how to plot the survival functions for the original data and the simulated data from the biexponential and BERM models, using the empirical cumulative distribution function (ECDF) and kernel density estimation (KDE) to estimate the survival function. ```{r} library(ks) library(splines) # Function to compute the survival function using KDE with a smoothed tail compute_survival_kde <- function(log_data, n_points = 100, log_transform = TRUE) { # Apply log transformation if specified if (log_transform) log_data <- log(log_data + 1e-5) # Small constant to avoid log(0) # Compute KDE kde <- kde(log_data) x <- seq(min(log_data), max(log_data), length.out = n_points) pdf <- predict(kde, x = x) # PDF from KDE cdf <- cumsum(pdf) / sum(pdf) # Approximate CDF survival <- 1 - cdf # Survival function # Apply spline smoothing to the tail of the survival function smooth_survival <- smooth.spline(x, survival, spar = 0.7) # Adjust spar as needed data.frame(x = exp(x) - 1e-5, survival = predict(smooth_survival, x)$y) # Transform back if log-transformed } # Function to simulate and compute survival function with smoothing simulate_survival <- function(params, model_type, delta = NULL, n = 100) { if (model_type == "biexponential") { q <- params["w"] l0 <- params["l0"] l1 <- params["l1"] n1 <- round(n * q) n2 <- n - n1 irt_sim <- c(rexp(n1, rate = 1 / l0), rexp(n2, rate = 1 / l1)) } else if (model_type == "berm") { q <- params["w"] l0 <- params["l0"] l1 <- params["l1"] delta <- params["d"] n1 <- round(n * q) n2 <- n - n1 irt_sim <- c(rexp(n1, rate = 1 / l0) + delta, rexp(n2, rate = 1 / l1) + delta) } else { stop("Invalid model type. Choose 'biexponential' or 'berm'.") } # Compute KDE-based survival function for the simulated IRTs kde <- kde(irt_sim) x <- seq(min(irt_sim), max(irt_sim), length.out = n) pdf <- predict(kde, x = x) cdf <- cumsum(pdf) / sum(pdf) survival <- 1 - cdf # Smooth the tail of the survival function using spline smooth_survival <- smooth.spline(x, survival, spar = 0.7) data.frame(x = x, survival = predict(smooth_survival, x)$y) } # Function to plot survival functions for original data and simulations plot_survival_comparison <- function( log_data, berm_params, biexponential_params, n_points = 200, num_sims = 100) { # Compute survival function for the original data survival_orig <- compute_survival_kde(log_data, n_points = n_points) # Plot original data survival function in blue plot(survival_orig$x, survival_orig$survival, type = "l", col = "blue", lwd = 2, xlab = "Inter-Response Time", ylab = "p(IRT > x)", main = "Survival Function Comparison", log = "y", ylim = c(0.0001, 1) ) # Simulate and plot survival functions for the biexponential model (in yellow) for (i in 1:num_sims) { survival_biexp <- simulate_survival( biexponential_params, "biexponential", n = length(log_data) ) lines(survival_biexp$x, survival_biexp$survival, col = rgb(251, 107, 95, 60, maxColorValue = 255), lwd = 0.5 ) # Semi-transparent yellow } # Simulate and plot survival functions for the BERM model (in green) for (i in 1:num_sims) { survival_berm <- simulate_survival(berm_params, "berm", n = length(log_data)) lines(survival_berm$x, survival_berm$survival, col = rgb(59, 132, 23, alpha = 52, maxColorValue = 255), lwd = 0.5 ) # Semi-transparent green } leg_green <- rgb(59, 132, 23, alpha = 255, maxColorValue = 255) leg_red <- rgb(251, 107, 95, alpha = 255, maxColorValue = 255) legend("topright", legend = c("Original Data", "Biexponential Model", "BERM Model"), col = c("blue", leg_red, leg_green), lty = 1, lwd = 2 ) } # Example usage with generated data and parameters set.seed(43) # Original data generation l1 <- 1 / 20 l2 <- 1 / 2 p <- 0.07 n <- 400 delta <- 0.03 irt <- c(rexp(round(n * p), l1), rexp(round(n * (1 - p)), l2)) + delta # Parameters (replace with optimized values from your model fitting process) biexponential_params <- as.numeric(biexponential(irt)) names(biexponential_params) <- c("w", "l0", "l1") berm_params <- as.numeric(berm(irt, delta)) names(berm_params) <- c("w", "l0", "l1", "d") # Plot comparison of survival functions plot_survival_comparison(irt, berm_params, biexponential_params, num_sims = 100, n_points = 300) ``` ## References