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.

Bayesian Changepoint Detection

José Mauricio Gómez Julián

2026-02-11

Introduction

Bayesian changepoint detection offers several advantages over frequentist methods:

  1. Probabilistic uncertainty: Full posterior distributions over changepoint locations
  2. Prior information: Incorporate domain knowledge
  3. Natural online updates: Sequential updating as new data arrives
  4. Existence probability: Probability that a changepoint exists at each location

BOCPD: Bayesian Online Changepoint Detection

BOCPD (Adams & MacKay, 2007) is the flagship Bayesian method:

library(RegimeChange)

# Generate example data
set.seed(123)
data <- c(rnorm(100, 0, 1), rnorm(100, 3, 1))

# Basic BOCPD
result <- detect_regimes(data, method = "bocpd")
print(result)
#> 
#> Regime Change Detection Results
#> ================================
#> 
#> Method: bocpd 
#> Change type: both 
#> Mode: offline 
#> 
#> Data: n = 200 observations
#> 
#> Changepoints detected: 0

Prior Specification

Normal-Gamma Prior

For unknown mean and variance (most common case):

# Define prior
prior <- normal_gamma(
  mu0 = 0,      # Prior mean for the mean
  kappa0 = 1,   # Prior precision for the mean
  alpha0 = 1,   # Shape for inverse-gamma on variance
  beta0 = 1     # Rate for inverse-gamma on variance
)

print(prior)
#> Regime Prior Specification
#> ==========================
#> Type: normal_gamma 
#> Description: Normal-Gamma prior for unknown mean and variance 
#> 
#> Parameters:
#>   mu0: 0
#>   kappa0: 1
#>   alpha0: 1
#>   beta0: 1

Using the prior:

result <- detect_regimes(data, method = "bocpd", prior = prior)

Normal Prior with Known Variance

When variance is known:

prior_known <- normal_known_var(
  mu0 = 0,         # Prior mean
  sigma0 = 1,      # Prior standard deviation for mean
  known_var = 1    # Known variance
)

result <- detect_regimes(data, method = "bocpd", prior = prior_known)

Poisson-Gamma Prior

For count data:

prior_poisson <- poisson_gamma(alpha0 = 1, beta0 = 1)

Hazard Prior

The hazard prior controls the expected frequency of changepoints:

Geometric Hazard

Constant hazard rate:

# Expected run length = 1/lambda = 100
hazard <- geometric_hazard(lambda = 0.01)
print(hazard)
#> Hazard Prior Specification
#> ==========================
#> Type: geometric 
#> Description: Geometric hazard (expected run length: 100.0) 
#> 
#> Parameters:
#>   lambda: 0.01
#>   Expected run length: 100.0

Constant Hazard

Fixed probability per time step:

hazard_const <- constant_hazard(lambda = 0.05)

Negative Binomial Hazard

For overdispersed run lengths:

hazard_nb <- negbin_hazard(r = 5, p = 0.1)

Posterior Analysis

Probability of Change at Each Point

result <- detect_regimes(data, method = "bocpd")

# Access changepoint probabilities
prob_change <- result$prob_change

# Plot posterior probability
plot(prob_change, type = "l", 
     main = "Posterior Probability of Changepoint",
     xlab = "Time", ylab = "P(changepoint)")
abline(v = 100, col = "red", lty = 2)

Posterior Visualization

plot(result, type = "posterior")

Run Length Distribution

plot(result, type = "runlength")

Shiryaev-Roberts Procedure

An alternative Bayesian approach optimal for minimizing detection delay:

result_sr <- detect_regimes(data, method = "shiryaev",
                            mu0 = 0,      # Pre-change mean
                            mu1 = 3,      # Post-change mean
                            sigma = 1,    # Known standard deviation
                            threshold = 100)
print(result_sr)
#> 
#> Regime Change Detection Results
#> ================================
#> 
#> Method: shiryaev 
#> Change type: both 
#> Mode: offline 
#> 
#> Data: n = 200 observations
#> 
#> Changepoints detected: 5 
#> Locations: 107, 112, 134, 142, 200 
#> 
#> Segments:
#>   Segment 1: [1, 107] (n=107) | mean=0.254, sd=1.085
#>   Segment 2: [108, 112] (n=5) | mean=2.781, sd=1.029
#>   Segment 3: [113, 134] (n=22) | mean=2.870, sd=0.796
#>   Segment 4: [135, 142] (n=8) | mean=2.908, sd=1.434
#>   Segment 5: [143, 200] (n=58) | mean=2.944, sd=1.014
#>   Segment 6: [201, 200] (n=0) | mean=NA, sd=NA

The Shiryaev-Roberts statistic:

plot(result_sr, type = "diagnostic")
#> Warning: Removed 1 row containing missing values or values outside the scale range
#> (`geom_hline()`).

Uncertainty Quantification

Credible Intervals

BOCPD provides natural credible intervals from the posterior:

# Highest posterior density interval
if (length(result$changepoints) > 0) {
  cat("Changepoint estimate:", result$changepoints[1], "\n")
  
  # Find 95% credible interval
  prob <- result$prob_change
  cumprob <- cumsum(prob) / sum(prob)
  lower <- which(cumprob >= 0.025)[1]
  upper <- which(cumprob >= 0.975)[1]
  cat("95% Credible interval: [", lower, ",", upper, "]\n")
}

Existence Probability

Probability that at least one changepoint exists:

existence_prob <- max(result$prob_change)
cat("Maximum changepoint probability:", round(existence_prob, 3), "\n")
#> Maximum changepoint probability: 0.02

Online Mode

BOCPD is naturally suited for online detection:

# Create online detector
detector <- regime_detector(
  method = "bocpd",
  prior = normal_gamma(),
  hazard = geometric_hazard(0.01),
  threshold = 0.5
)

# Simulate online processing
set.seed(123)
stream <- c(rnorm(100, 0, 1), rnorm(50, 3, 1))

alarm_time <- NULL
for (i in seq_along(stream)) {
  detector <- update(detector, stream[i])
  
  if (isTRUE(detector$last_result$alarm) && is.null(alarm_time)) {
    alarm_time <- i
    cat("Alarm at observation:", i, "\n")
    cat("Probability of change:", round(detector$last_result$prob_change, 3), "\n")
    break
  }
}

Multivariate BOCPD

For multivariate time series:

# Generate bivariate data
set.seed(42)
n <- 200
cp <- 100
data_mv <- rbind(
  matrix(rnorm(cp * 2, 0), ncol = 2),
  matrix(rnorm((n - cp) * 2, 2), ncol = 2)
)

# Normal-Wishart prior for multivariate data
prior_mv <- normal_wishart(
  mu0 = c(0, 0),
  kappa0 = 1,
  nu0 = 4,
  Psi0 = diag(2)
)

result_mv <- detect_regimes(data_mv, method = "bocpd", prior = prior_mv)
print(result_mv)
#> 
#> Regime Change Detection Results
#> ================================
#> 
#> Method: bocpd 
#> Change type: both 
#> Mode: offline 
#> 
#> Data: n = 200 observations
#> 
#> Changepoints detected: 0

Comparison: BOCPD vs Frequentist

# Compare BOCPD with PELT
comparison <- compare_methods(
  data = data,
  methods = c("bocpd", "pelt"),
  true_changepoints = 100
)
print(comparison)
#> 
#> Changepoint Detection Method Comparison
#> ========================================
#> 
#>  method n_changepoints   time_sec f1 hausdorff adj_rand
#>   bocpd              0 0.10733008  0       Inf        0
#>    pelt              1 0.02529216  1         0        1
#> 
#> True changepoints: 100 
#> 
#> Detected changepoints by method:
#>   bocpd: (none)
#>   pelt: 100

Advantages and Limitations

Advantages

Limitations

Best Practices

  1. Choose appropriate priors: Use domain knowledge when available
  2. Calibrate hazard rate: Set based on expected changepoint frequency
  3. Check sensitivity: Try different priors to assess robustness
  4. Use truncation: For long series, truncate run length for efficiency
  5. Visualize posterior: Always examine the full posterior distribution

References

Adams, R. P., & MacKay, D. J. C. (2007). Bayesian online changepoint detection. arXiv preprint arXiv:0710.3742.

Tartakovsky, A. G., & Moustakides, G. V. (2010). State-of-the-art in Bayesian changepoint detection. Sequential Analysis, 29(2), 125-145.

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.