---
title: "**RegimeChange**"
author: "**José Mauricio Gómez Julián**"
date: "**January 29, 2026**"
output:
  pdf_document:
    toc: true
    toc_depth: 3
    number_sections: true
    highlight: tango
    latex_engine: xelatex
    keep_tex: false
  html_document:
    toc: true
    toc_depth: '3'
    df_print: paged
subtitle: Robust Changepoint Detection for Time Series
header-includes:
- \usepackage{booktabs}
- \usepackage{longtable}
- \usepackage{array}
- \usepackage{multirow}
- \usepackage{float}
- \usepackage{fancyvrb}
- \usepackage{geometry}
- \geometry{margin=1in}
- \usepackage{hyperref}
- \hypersetup{colorlinks=true, linkcolor=blue, urlcolor=blue}
fontsize: 11pt
documentclass: article
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  echo = TRUE,
  eval = FALSE,
  message = FALSE,
  warning = FALSE,
  fig.align = "center"
)
```

\newpage

# Introduction

**RegimeChange** is an R package for detecting structural breaks and regime changes in time series data. It provides a unified interface to both frequentist and Bayesian methods, with particular emphasis on robust performance in challenging scenarios including low signal-to-noise ratios, subtle variance changes, autocorrelated data, and heavy-tailed distributions.

# Philosophical Foundations

## The Ontological Problem

Regime change detection addresses a fundamental philosophical question:

> **How do we distinguish a real change from the inherent variability of the world?**

Every observable system exhibits fluctuations. The central question is: is this observed fluctuation "noise" (random variation within the same regime) or "signal" (evidence that the system transitioned to a qualitatively different state)?

## Conceptual Structure

### Duality of States

We assume the world can be described through "regimes" or "states" characterized by parameters $\theta$. A system is in state $\theta_0$ until, at some unknown moment, it transitions to $\theta_1$. This is a **discrete ontology of change**: there is no graduality, there is rupture.

### Imperfect Observation

We do not observe $\theta$ directly, but noisy manifestations $y_i$. The epistemology here is probabilistic: each observation is a "clue" about the true state, but none is conclusive by itself.

### Evidence Accumulation

The statistic $S = \sum s_i$ embodies a fundamental epistemological principle: **evidence accumulates**. Each observation contributes a "vote" in favor of $H_0$ or $H_1$, and the sum represents the total weight of evidence.

## The Fundamental Dilemma

There exists an **irresolvable epistemological tension** between two types of error:

| Error | Technical Name | Consequence |
|:------|:---------------|:------------|
| Detecting change where there is none | False alarm (Type I) | Acting unnecessarily |
| Not detecting a real change | Delayed detection (Type II) | Failing to react in time |

The decision threshold represents precisely this negotiation: how much evidence do we demand before declaring that the world has changed?

## Universality of the Problem

This structure appears across diverse domains:

| Domain | $\theta_0$ | $\theta_1$ | Question |
|:-------|:-----------|:-----------|:---------|
| Economics | Expansion | Recession | When did the cycle change? |
| Medicine | Stable patient | Deterioration | When did the crisis begin? |
| Finance | Stable market | Bubble/crash | When did the regime change? |
| Ecology | Balanced ecosystem | Disturbed | When did the collapse occur? |
| Manufacturing | Process under control | Defective | When did it go out of adjustment? |
| Climatology | Stationary climate | Climate change | When did the trend begin? |
| Epidemiology | Endemic | Outbreak | When did the epidemic start? |
| Electrical engineering | Normal operation | Failure/transient | When did the event occur? |

## Information and Surprise

The likelihood ratio:

$$s_i = \ln \left( \frac{p_{\theta_1}(y_i)}{p_{\theta_0}(y_i)} \right)$$

has a deep informational interpretation: it measures **how surprising** observation $y_i$ is under each hypothesis. If $s_i > 0$, the observation is "more expected" under $\theta_1$ than under $\theta_0$. It is, literally, a quantification of relative surprise.

\newpage

# Motivation

Changepoint detection is a fundamental problem in time series analysis with applications in finance, quality control, climatology, and genomics. While several R packages address this problem, practitioners frequently encounter difficulties with:

- **Autocorrelated data**: Most methods assume independence, leading to inflated false positive rates
- **Subtle changes**: Low SNR or small variance shifts often go undetected
- **Outliers and heavy tails**: Standard Gaussian assumptions break down with contaminated data
- **Method selection**: No single algorithm dominates across all scenarios

RegimeChange addresses these challenges through adaptive variance floors for numerical stability, optional pre-whitening for autocorrelated series, robust M-estimation for heavy-tailed data, and ensemble methods that leverage multiple algorithms.

# Competitive Positioning

## Current State of the Art

| Package | Language | Strengths | Limitations |
|:--------|:---------|:----------|:------------|
| `changepoint` | R (C backend) | Optimized PELT, stable, mature | Frequentist only, no online, limited uncertainty |
| `tidychangepoint` | R | Tidyverse interface, unifies methods | Wrapper, adds no new algorithms |
| `ruptures` | Python | Very complete for offline | Not Bayesian, not online |
| `bayesian_changepoint_detection` | Python | BOCPD with PyTorch | Bayesian only, doesn't integrate other methods |
| `Kats` (Meta) | Python | BOCPD, well documented | Meta ecosystem, heavy dependencies |

## Gaps Addressed

RegimeChange provides:

- Integration of frequentist AND Bayesian methods
- Online mode with streaming updates
- Rigorous uncertainty quantification
- Multivariate series support
- Detection of multiple types of change
- High-performance backend (Julia) with friendly API (R)
- Deep learning for complex patterns (optional)

## Value Proposition

1. **Unified paradigms**: frequentist, Bayesian, and deep learning in a coherent API
2. **Both modes**: offline for research, online for production
3. **Native uncertainty**: estimations come with confidence intervals
4. **Performance + Usability**: optional Julia backend, tidyverse-compatible R interface

\newpage

# Installation

```r
# Install from GitHub
# install.packages("devtools")
devtools::install_github("IsadoreNabi/RegimeChange")
```

# Quick Start

```r
library(RegimeChange)

# Generate data with a changepoint at t=200
set.seed(42)
data <- c(rnorm(200, 0, 1), rnorm(200, 2, 1))

# Detect using PELT
result <- detect_regimes(data, method = "pelt")
print(result)

# For autocorrelated data, enable AR correction
result_ar <- detect_regimes(data, method = "pelt", correct_ar = TRUE)

# For data with outliers or heavy tails, enable robust estimation
result_robust <- detect_regimes(data, method = "pelt", robust = TRUE)
```

\newpage

# Taxonomy of Changes

## Types of Detectable Changes

The library detects changes in different statistical properties:

- **Location**: Mean, Trend, Level
- **Dispersion**: Variance, Range
- **Dependence**: Autocorrelation, Cross-correlation, Structure
- **Distribution**: Shape, Tails, Mode

## Formal Specification

| Type | Parameter | Hypothesis | Example |
|:-----|:----------|:-----------|:--------|
| **Mean** | $\mu$ | $H_0: \mu = \mu_0$ vs $H_1: \mu = \mu_1$ | Level change in GDP |
| **Variance** | $\sigma^2$ | $H_0: \sigma^2 = \sigma_0^2$ vs $H_1: \sigma^2 = \sigma_1^2$ | Volatility increase |
| **Mean and Variance** | $(\mu, \sigma^2)$ | Joint change | Economic regime transition |
| **Trend** | $\beta$ (slope) | $H_0: \beta = 0$ vs $H_1: \beta \neq 0$ | Start of inflationary trend |
| **Autocorrelation** | $\rho$ | $H_0: \rho = \rho_0$ vs $H_1: \rho = \rho_1$ | Change in persistence |
| **Distribution** | $F$ | $H_0: F = F_0$ vs $H_1: F \neq F_0$ | General non-parametric change |

## Simple vs Multiple Changes

**Simple change (one point):**

A single changepoint $\tau$ divides the series into Regime A and Regime B.

**Multiple changes (k points):**

Multiple changepoints $\tau_1, \tau_2, \ldots, \tau_k$ divide the series into $k+1$ regimes.

## Detection Configuration

```r
# The user can specify what to look for
detect_regimes(
  data,
  type = c("mean", "variance", "both", "trend", "distribution"),
  n_changepoints = c("single", "multiple", "unknown"),
  min_segment_length = 30  # Minimum observations per regime
)
```

\newpage

# Algorithmic Architecture

## Method Families

The detection methods are organized into three main families:

1. **Frequentist**: PELT, BinSeg, CUSUM, Wild BinSeg, FPOP, E-Divisive, Kernel CPD
2. **Bayesian**: BOCPD (Adams-MacKay), Shiryaev-Roberts
3. **Deep Learning**: Autoencoders, TCN, Transformer, CPC, Ensemble

## Frequentist Methods

### CUSUM (Cumulative Sum)

The foundational method. Accumulates deviations from a target value.

**Statistic**:
$$S_n = \sum_{i=1}^{n} (x_i - \mu_0)$$

**Decision**: Alarm when $|S_n| > h$

**Complexity**: $O(n)$

**Use**: Mean change detection, single changepoint.

```r
detect_regimes(data, method = "cusum", threshold = 4)
```

### PELT (Pruned Exact Linear Time)

The gold standard for multiple changepoints.

**Idea**: Dynamic programming with pruning to find optimal segmentation.

**Objective function**:
$$\sum_{i=1}^{m+1} \left[ \mathcal{C}(y_{(\tau_{i-1}+1):\tau_i}) \right] + \beta m$$

where $\mathcal{C}$ is segment cost and $\beta$ is the penalty for number of changes.

**Complexity**: $O(n)$ in favorable cases, $O(n^2)$ worst case.

**Supported penalties**: AIC, BIC, MBIC, MDL, manual.

```r
# PELT (default) - O(n) optimal partitioning
detect_regimes(data, method = "pelt", penalty = "MBIC")

# With AR correction for autocorrelated data
detect_regimes(data, method = "pelt", correct_ar = TRUE)

# With robust estimation for outliers/heavy tails
detect_regimes(data, method = "pelt", robust = TRUE)
```

### Binary Segmentation

Recursive divide and conquer.

**Algorithm**:

1. Find the best changepoint in the entire dataset
2. If significant, split into two segments
3. Apply recursively to each segment

**Complexity**: $O(n \log n)$

**Limitation**: Does not guarantee global optimum (greedy).

```r
detect_regimes(data, method = "binseg", n_changepoints = 3)
```

### Wild Binary Segmentation (WBS)

Improvement on Binary Segmentation with random intervals.

**Idea**: Instead of searching the entire interval, search M random intervals and take the best.

**Advantage**: More robust to nearby changes.

```r
detect_regimes(data, method = "wbs", n_intervals = 100)
```

### FPOP (Functional Pruning Optimal Partitioning)

Optimized version of PELT with functional pruning.

**Complexity**: $O(n)$ guaranteed for certain cost functions.

```r
fpop_detect(data, penalty = "BIC")
```

### E-Divisive

Non-parametric method using energy distance.

**Idea**: Use energy distance to detect changes in complete distribution.

**Advantage**: No parametric distribution assumption.

```r
edivisive_detect(data, min_size = 30)
```

### Kernel-based CPD

**Idea**: Map data to Hilbert space and detect changes in that space.

**Advantage**: Captures changes in complex data structure.

```r
kernel_cpd_detect(data, kernel = "rbf")
```

## Bayesian Methods

### BOCPD (Bayesian Online Changepoint Detection)

**Reference**: Adams & MacKay (2007)

**Central idea**: Maintain a posterior distribution over the "run length" (time since last change).

**Joint distribution**:
$$P(r_t, x_{1:t}) = \sum_{r_{t-1}} P(x_t | r_t, x^{(r)}) \cdot P(r_t | r_{t-1}) \cdot P(r_{t-1}, x_{1:t-1})$$

**Components**:

- **Predictive**: $P(x_t | r_t, x^{(r)})$ — probability of datum given run length
- **Changepoint prior**: $P(r_t | r_{t-1})$ — prior on change occurrence (typically geometric)
- **Message**: $P(r_{t-1}, x_{1:t-1})$ — recursion from previous step

**Supported conjugate models**:

- Normal with known variance (Normal-Normal)
- Normal with unknown mean and variance (Normal-Gamma)
- Poisson (Gamma-Poisson)
- Multivariate (Normal-Wishart)

**Complexity per observation**: $O(t)$ naïve, $O(1)$ with truncation.

```r
# Bayesian Online Changepoint Detection
detect_regimes(data, method = "bocpd")

# Custom prior specification
prior <- normal_gamma(mu0 = 0, kappa0 = 0.1, alpha0 = 1, beta0 = 1)
detect_regimes(data, method = "bocpd", prior = prior)
```

### Shiryaev-Roberts

**Statistic**:
$$R_n = \sum_{k=1}^{n} \prod_{i=k}^{n} \frac{p_{\theta_1}(x_i)}{p_{\theta_0}(x_i)}$$

**Interpretation**: Sum of likelihood ratios from all possible past changepoints.

**Advantage**: Asymptotically optimal for minimizing detection delay.

```r
shiryaev_roberts(data, threshold = 100)
```

## Deep Learning Methods

*Note: These methods require optional dependencies (`keras`, `tensorflow`).*

### Autoencoders for Detection

**Idea**: Train autoencoder on "normal" data, detect change when reconstruction error increases.

```r
autoencoder_detect(data, window_size = 50, threshold = 2.0)
```

### TCN (Temporal Convolutional Network)

Convolutional architecture for sequence modeling.

```r
tcn_detect(data, window_size = 100)
```

### Transformer-based Detection

Attention-based architecture for capturing long-range dependencies.

```r
transformer_detect(data, window_size = 100)
```

### CPC (Contrastive Predictive Coding)

Self-supervised representation learning for change detection.

```r
cpc_detect(data, window_size = 50)
```

### Ensemble Deep Learning

Combine multiple deep learning detectors.

```r
ensemble_dl_detect(data, methods = c("autoencoder", "tcn"))
```

\newpage

# Operation Modes

## Offline Mode

**Context**: Retrospective analysis. Complete dataset is available.

**Question**: "When did the changes occur?"

**Characteristics**:

- Can look forward and backward
- Seeks globally optimal segmentation
- No temporal constraint

**Appropriate algorithms**: PELT, Binary Segmentation, WBS, FPOP, E-Divisive

**Use cases**:

- Historical analysis of economic series
- Study of past climate changes
- Scientific research in general

## Online Mode

**Context**: Real-time surveillance. Data arrives sequentially.

**Question**: "Is a change occurring now?"

**Characteristics**:

- Only sees past and present
- Must decide immediately
- Trade-off speed vs false alarms

**Appropriate algorithms**: CUSUM, BOCPD, Shiryaev-Roberts

**Key metrics**:

- **ARL$_0$**: Average Run Length under $H_0$ (average time to false alarm)
- **ARL$_1$**: Average Run Length under $H_1$ (average detection delay)

**Use cases**:

- Industrial monitoring
- Epidemiological surveillance
- Fraud detection
- Algorithmic trading

## Online Detection API

```r
# Initialize detector
detector <- regime_detector(method = "bocpd")

# Process streaming data
for (x in new_observations) {
  result <- update(detector, x)
  if (result$alarm) {
    message("Changepoint detected at time ", result$time)
    detector <- reset(detector)
  }
}
```

## Unified Interface

```r
# Offline mode (default)
result <- detect_regimes(data, mode = "offline", method = "pelt")

# Online mode via regime_detector
detector <- regime_detector(method = "bocpd", prior = normal_gamma())

for (x in data_stream) {
  result <- update(detector, x)
  
  if (result$alarm) {
    handle_regime_change(result)
  }
}
```

\newpage

# Uncertainty Quantification

## Fundamental Principle

> **Every changepoint estimate must be accompanied by an uncertainty measure.**

This is what distinguishes a scientifically rigorous library from a purely practical tool.

## Types of Uncertainty

### Location Uncertainty

How confident are we that the change occurred exactly at $\hat{\tau}$?

**Representation**:

- Confidence interval: $[\hat{\tau} - \delta, \hat{\tau} + \delta]$
- Complete posterior distribution: $P(\tau | \text{data})$

### Existence Uncertainty

How confident are we that there is a change at all?

**Representation**:

- p-value (frequentist)
- Bayes Factor (Bayesian)
- Posterior probability of change

### Number of Changes Uncertainty

How many changes are there really?

**Representation**:

- Distribution over number of changes
- Optimal penalty (BIC, MDL)

## Implementation

### Bootstrap for Frequentist Methods

```r
bootstrap_changepoint <- function(data, method, B = 1000) {
  
  estimates <- numeric(B)
  
  for (b in 1:B) {
    # Resample blocks to preserve dependence
    data_boot <- block_bootstrap(data)
    estimates[b] <- detect(data_boot, method)$changepoint
  }
  
  # Confidence interval
  ci <- quantile(estimates, c(0.025, 0.975))
  
  return(list(
    estimate = detect(data, method)$changepoint,
    ci = ci,
    se = sd(estimates),
    distribution = estimates
  ))
}
```

### Complete Posterior for Bayesian Methods

BOCPD naturally produces $P(r_t | x_{1:t})$, from which one can derive:

```r
# Probability of change at each point
prob_change <- bocpd_result$posterior[, 1]  # run length = 0

# Most probable changepoint
map_changepoint <- which.max(prob_change)

# Credible interval
credible_interval <- hpd_interval(prob_change, prob = 0.95)
```

## Result Structure

```r
# Every detection function returns:
result <- list(
  # Point estimates
  changepoints = c(45, 120, 380),
  
  # Location uncertainty
  confidence_intervals = list(
    c(42, 48),
    c(115, 125),
    c(375, 385)
  ),
  
  # Existence uncertainty
  existence_probability = c(0.95, 0.88, 0.72),
  
  # Segment information
  segments = list(
    list(start = 1, end = 45, params = list(mean = 2.3, var = 0.5)),
    list(start = 46, end = 120, params = list(mean = 5.1, var = 0.8))
    # ...
  ),
  
  # Diagnostics
  information_criterion = list(BIC = 234.5, AIC = 228.1),
  
  # Complete posterior (if applicable)
  posterior = matrix(...)  # P(change at t) for each t
)
```

\newpage

# Multivariate Series

## The Problem

With $d$ simultaneous series, the following can occur:

1. **Synchronous change**: All series change at the same point
2. **Asynchronous change**: Different series change at different points
3. **Correlation change**: Dependence structure changes, even if marginals don't

## General Model

Let $\mathbf{X}_t \in \mathbb{R}^d$ be a vector of observations at time $t$.

**Before change**: $\mathbf{X}_t \sim N(\boldsymbol{\mu}_0, \boldsymbol{\Sigma}_0)$

**After change**: $\mathbf{X}_t \sim N(\boldsymbol{\mu}_1, \boldsymbol{\Sigma}_1)$

The change can be in:

- $\boldsymbol{\mu}$ (mean vector)
- $\boldsymbol{\Sigma}$ (covariance matrix, including correlations)
- Both

## Multivariate Extensions

### Multivariate BOCPD

Use Normal-Wishart predictive distribution:

**Prior**: $(\boldsymbol{\mu}, \boldsymbol{\Sigma}) \sim \text{NIW}(\mu_0, \kappa_0, \nu_0, \boldsymbol{\Psi}_0)$

**Update**: Conjugate formulas for updating hyperparameters.

```r
prior <- normal_wishart(mu0 = rep(0, d), kappa0 = 1, 
                        nu0 = d + 1, Psi0 = diag(d))
result <- bocpd(data_matrix, prior = prior)
```

### Sparse Projection (Wang & Samworth)

For high dimension ($d >> n$), project to sparse directions before detecting.

**Idea**: Find direction $\mathbf{v}$ that maximizes change signal, with $||\mathbf{v}||_0 \leq s$.

```r
sparse_projection_cpd(data_matrix, sparsity = 5)
```

## Multivariate API

```r
# Data: n × d matrix
data <- matrix(...)

# Detect synchronous changes
result <- detect_regimes(data, 
                         type = "multivariate",
                         sync = TRUE)

# Detect changes per series
result <- detect_regimes(data,
                         type = "multivariate", 
                         sync = FALSE,
                         share_changepoints = FALSE)

# Detect correlation change
result <- detect_regimes(data,
                         type = "correlation",
                         method = "normal_wishart")
```

\newpage

# Empirical Performance

We conducted comprehensive benchmarks comparing RegimeChange against established R packages: `changepoint` (Killick & Eckley, 2014), `wbs` (Fryzlewicz, 2014), `not` (Baranowski et al., 2019), and `ecp` (James & Matteson, 2015). Tests were performed with 50 replications per scenario, using synthetic data under maximum difficulty conditions.

## Benchmark 1: Frequentist Methods (17 Scenarios)

17 scenarios $\times$ 50 reps = 850 tests. Tolerance = 15 observations.

### Overall Ranking (Mean F1 Score)

| Rank | Method | Mean F1 | Package |
|:-----|:-------|:--------|:--------|
| 1 | **RC_auto** | **0.804** | RegimeChange |
| 2 | ECP_pkg | 0.788 | ecp |
| 3 | CP_binseg | 0.777 | changepoint |
| 4 | RC_cusum | 0.761 | RegimeChange |
| 5 | NOT_pkg | 0.722 | not |
| 6 | CP_pelt | 0.720 | changepoint |
| 7 | RC_pelt | 0.719 | RegimeChange |
| 8 | RC_wbs_mean | 0.681 | RegimeChange |
| 9 | RC_wbs | 0.654 | RegimeChange |
| 10 | RC_binseg | 0.652 | RegimeChange |
| 11 | WBS_pkg | 0.634 | wbs |
| 12 | RC_ediv | 0.612 | RegimeChange |
| 13 | RC_not | 0.514 | RegimeChange |

RegimeChange's automatic method selector (`RC_auto`) achieves the highest overall F1 score, outperforming all reference packages.

### Performance by Scenario

| Scenario | Best RC | Best Reference | Winner |
|:---------|:--------|:---------------|:-------|
| Mean δ=3 (easy) | 1.000 | 1.000 | Tie |
| Mean δ=2 (medium) | 1.000 | 1.000 | Tie |
| Mean δ=1 (hard) | 0.980 | 0.980 | Tie |
| Mean δ=0.5 (very hard) | 0.773 | 0.760 | **RC** (+0.01) |
| Variance 4:1 | 0.980 | 0.980 | Tie |
| Variance 2:1 | 0.667 | 0.400 | **RC** (+0.27) |
| Multi-CP (3 changes) | 1.000 | 1.000 | Tie |
| Multi-CP (5 changes) | 1.000 | 1.000 | Tie |
| AR(0.3)+change | 1.000 | 1.000 | Tie |
| AR(0.5)+change | 1.000 | 0.983 | **RC** (+0.02) |
| AR(0.7)+change | 0.900 | 0.720 | **RC** (+0.18) |
| 2% outliers | 1.000 | 0.967 | **RC** (+0.03) |
| 5% outliers | 1.000 | 0.963 | **RC** (+0.04) |
| 10% outliers | 0.993 | 0.973 | **RC** (+0.02) |
| t-dist (df=5) | 1.000 | 1.000 | Tie |
| t-dist (df=3) | 1.000 | 0.993 | **RC** (+0.01) |
| t-dist (df=2) | 0.967 | 0.983 | Ref (+0.02) |

**Summary**: RegimeChange wins or ties in 16 of 17 scenarios. The only loss is marginal (0.017 in t-dist df=2).

### Key Advantages

| Scenario | RegimeChange | Reference | Improvement |
|:---------|:-------------|:----------|:------------|
| Subtle variance (2:1) | 0.667 | 0.400 | **+67%** |
| Strong AR (ρ=0.7) | 0.900 | 0.720 | **+25%** |
| 5% outliers | 1.000 | 0.963 | **+4%** |
| Heavy tails t(df=3) | 1.000 | 0.993 | **+1%** |

## Benchmark 2: Robustness to Outliers and Heavy Tails

10 scenarios $\times$ 50 reps = 500 tests. This benchmark specifically evaluates performance under data contamination.

### Robustness Results (F1 ± SD)

| Scenario | RC_pelt | CP_pelt | RC_robust | RC_ediv | ECP_pkg |
|:---------|:--------|:--------|:----------|:--------|:--------|
| Clean (baseline) | 1.00±0.00 | 1.00±0.00 | 0.98±0.08 | 0.77±0.20 | 0.99±0.07 |
| 2% outliers | 0.24±0.12 | 0.52±0.26 | **1.00±0.00** | 0.81±0.17 | 0.99±0.07 |
| 5% outliers | 0.14±0.04 | 0.23±0.12 | **0.99±0.05** | 0.82±0.17 | 0.99±0.07 |
| 10% outliers | 0.11±0.08 | 0.17±0.19 | **0.99±0.05** | 0.85±0.18 | 1.00±0.00 |
| 15% outliers | 0.12±0.09 | 0.17±0.25 | **1.00±0.00** | 0.75±0.31 | 0.96±0.12 |
| t-dist (df=5) | 0.89±0.19 | 0.97±0.12 | **1.00±0.00** | 0.78±0.18 | 1.00±0.00 |
| t-dist (df=3) | 0.50±0.21 | 0.72±0.26 | **1.00±0.00** | 0.78±0.17 | 0.97±0.12 |
| t-dist (df=2) | 0.26±0.17 | 0.38±0.32 | **1.00±0.00** | 0.82±0.18 | 0.97±0.12 |
| Mixture (10%) | 0.30±0.14 | 0.64±0.33 | **0.99±0.05** | 0.77±0.18 | 0.97±0.12 |
| 5% Cauchy | 0.41±0.20 | 0.51±0.22 | **1.00±0.00** | 0.77±0.19 | 0.97±0.10 |

### Robustness Ranking (Excluding Baseline)

| Rank | Method | Mean F1 |
|:-----|:-------|:--------|
| 1 | **RC_robust** | **0.998** |
| 2 | ECP_pkg | 0.979 |
| 3 | RC_ediv | 0.795 |
| 4 | CP_pelt | 0.479 |
| 5 | RC_pelt | 0.331 |

**Key Finding**: `RC_robust` achieves near-perfect performance (F1 = 0.998) across all contamination scenarios, with perfect scores (F1 = 1.00) in 6 of 9 challenging scenarios. This represents a **108% improvement** over the best reference package (`changepoint` PELT at F1 = 0.479) on contaminated data.

## Summary of Findings

**Where RegimeChange excels:**

- Contaminated data: RC_robust achieves F1 = 0.998 vs. CP_pelt at 0.479 (**+108%**)
- Subtle variance changes: RC achieves 0.667 vs. reference 0.400 (**+67%**)
- Autocorrelated series: RC achieves 0.900 vs. reference 0.720 for AR(0.7) (**+25%**)
- Overall ranking: RC_auto achieves highest mean F1 (0.804) across all methods

**Where RegimeChange ties:**

- Standard scenarios with clear mean shifts: Perfect detection (F1 = 1.00)
- Multiple changepoint scenarios: Excellent detection (F1 ≥ 0.995)

**Where references have slight advantage:**

- Very heavy tails (t-dist df=2): ECP achieves 0.983 vs. RC 0.967 (margin: 0.016)

## Configuration Guide

| Data Characteristics | Recommended Settings | Expected F1 |
|:---------------------|:---------------------|:------------|
| Clean iid data | default | 0.95+ |
| Mild contamination (2-5%) | `robust = TRUE` | 0.99+ |
| Heavy contamination (10%+) | `robust = TRUE` | 0.99+ |
| Heavy tails (t-dist, Cauchy) | `robust = TRUE` | 0.99+ |
| Autocorrelated (AR) | `correct_ar = TRUE` | 0.95+ |
| Subtle variance changes | `type = "both"` | 0.65+ |
| Unknown data quality | `detect_regimes(data, method = "auto")` | 0.80+ |

**Recommendation**: For real-world data where contamination or heavy tails are possible, `robust = TRUE` is strongly recommended. The computational overhead is modest, and the robustness gains are substantial.

## Reproducing the Benchmarks

```r
library(RegimeChange)
library(changepoint)

# Example 1: AR(0.7) scenario
set.seed(123)
ar_data <- as.numeric(arima.sim(list(ar = 0.7), n = 400))
ar_data[201:400] <- ar_data[201:400] + 2  # Add mean shift

cp_result <- cpt.meanvar(ar_data, method = "PELT", penalty = "MBIC")
rc_result <- detect_pelt(ar_data, "both", "MBIC", 5)
rc_result_cusum <- cusum(ar_data)

# RC_cusum typically closer to true changepoint at 200

# Example 2: Contaminated data (5% outliers)
set.seed(456)
clean_data <- c(rnorm(200, 0), rnorm(200, 2))
outlier_idx <- sample(400, 20)
contaminated <- clean_data
contaminated[outlier_idx] <- contaminated[outlier_idx] + 
                              sample(c(-10, 10), 20, replace = TRUE)

# Standard methods fail
cp_result <- cpts(cpt.mean(contaminated, method = "PELT", penalty = "MBIC"))
# Often misses the true changepoint or finds spurious ones

# Robust method succeeds
rc_result <- detect_pelt(contaminated, "mean", "MBIC", 5, robust = TRUE)
rc_result$changepoints  # Typically close to 200
```

\newpage

# Metrics and Evaluation

## Metrics for Offline Mode

### Localization Error

**Hausdorff Distance**:
$$d_H(\hat{\mathcal{T}}, \mathcal{T}) = \max\left( \max_{\hat{\tau} \in \hat{\mathcal{T}}} \min_{\tau \in \mathcal{T}} |\hat{\tau} - \tau|, \max_{\tau \in \mathcal{T}} \min_{\hat{\tau} \in \hat{\mathcal{T}}} |\tau - \hat{\tau}| \right)$$

**Interpretation**: Maximum distance between an estimated point and the nearest true one.

### Segmentation Metrics

**Rand Index**:
$$RI = \frac{TP + TN}{TP + TN + FP + FN}$$

where TP, TN, FP, FN are defined over pairs of points:

- TP: Same segment in truth and estimation
- TN: Different segment in truth and estimation
- FP: Same estimated segment, different true segment
- FN: Different estimated segment, same true segment

**Adjusted Rand Index**: Corrected for chance.

### Covering Metric

$$Cover(\hat{\mathcal{S}}, \mathcal{S}) = \frac{1}{n} \sum_{S \in \mathcal{S}} |S| \cdot \max_{\hat{S} \in \hat{\mathcal{S}}} \frac{|S \cap \hat{S}|}{|S \cup \hat{S}|}$$

**Interpretation**: Weighted average of IoU (Intersection over Union) for each segment.

### F1-Score with Tolerance

Given a tolerance margin $M$:

- **TP**: Change detected within $\pm M$ of a true one
- **FP**: Change detected with no nearby true one
- **FN**: True change with no nearby detection

$$F_1 = \frac{2 \cdot \text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}$$

## Metrics for Online Mode

### Average Run Length (ARL)

**ARL$_0$** (under $H_0$): Average time to false alarm. We want ARL$_0$ high.

**ARL$_1$** (under $H_1$): Average time from change to detection. We want ARL$_1$ low.

**Trade-off**: Increasing threshold $\rightarrow$ $\uparrow$ARL$_0$, $\uparrow$ARL$_1$

### Detection Probability

$$P_D = P(\text{detect change} | \text{change occurred})$$

Measured at different time horizons after the change.

### False Alarm Probability

$$P_{FA} = P(\text{detect change} | \text{no change occurred})$$

Per time window.

## Evaluation Framework

```r
# Evaluate result against ground truth
result <- detect_regimes(data, method = "pelt")
metrics <- evaluate(result, true_changepoints = c(100, 250), tolerance = 10)
print(metrics)

# Available metrics
hausdorff_distance(result$changepoints, true_cps)
f1_score(result$changepoints, true_cps, tolerance = 10)
rand_index(result, true_cps)
adjusted_rand_index(result, true_cps)
covering_metric(result, true_cps)
precision_score(result$changepoints, true_cps, tolerance = 10)
recall_score(result$changepoints, true_cps, tolerance = 10)
mean_absolute_error(result$changepoints, true_cps)
rmse_changepoints(result$changepoints, true_cps)
```

## Benchmarking

The library includes:

1. **Synthetic datasets** with known ground truth
2. **Real reference datasets** (economic, industrial, well-log)
3. **Comparison function** between methods

```r
# Compare multiple methods
benchmark <- compare_methods(
  data = benchmark_data,
  methods = c("pelt", "bocpd", "wbs", "cusum"),
  true_changepoints = true_cps
)

# Automatic visualization
plot(benchmark)
```

\newpage

# Technical Details

## Robust Estimation (`robust = TRUE`)

When enabled, RegimeChange uses:

- **Adaptive winsorization**: Outliers beyond 3 MADs are clipped
- **Huber M-estimation**: Loss function with 95% efficiency at normal
- **Qn scale estimator**: More efficient than MAD (82% vs 37%)
- **Iteratively reweighted least squares**: For joint location-scale estimation

This provides a breakdown point of 50% (maximum theoretical) while maintaining high efficiency for clean data.

## AR Correction (`correct_ar = TRUE`)

Pre-whitening transformation: $y'_t = y_t - \rho \cdot y_{t-1}$

- AR(1) coefficient estimated via Yule-Walker (or robust median-based method if `robust = TRUE`)
- Applied only when $|\rho| > 0.1$ and $n > 30$
- Changepoint indices automatically adjusted for the lost observation

\newpage

# Technical Architecture

## Design Philosophy

> "R Usability, Julia Performance, Statistical Rigor"

**Principles**:

1. Tidyverse-compatible R API
2. Optional intensive computations in Julia
3. Native R visualization (ggplot2)
4. Extensibility for new methods

## Package Structure

```
RegimeChange/
├── DESCRIPTION
├── NAMESPACE
├── LICENSE
├── README.md
├── R/
│   ├── data.R                  # Dataset documentation
│   ├── detect.R                # Main function detect_regimes()
│   ├── evaluation.R            # Metrics and benchmarking
│   ├── imports.R               # Package imports
│   ├── julia_backend.R         # Julia connection via JuliaCall
│   ├── methods_advanced.R      # FPOP, Kernel CPD, E-Divisive
│   ├── methods_bayesian.R      # BOCPD, Shiryaev-Roberts
│   ├── methods_deeplearning.R  # Autoencoder, TCN, Transformer
│   ├── methods_frequentist.R   # PELT, CUSUM, BinSeg, WBS
│   ├── priors.R                # Prior distributions
│   ├── visualization.R         # Plotting functions
│   └── zzz.R                   # Package initialization
├── inst/
│   └── julia/
│       └── RegimeChangeJulia.jl
├── man/
├── tests/
│   ├── testthat.R
│   └── testthat/
├── vignettes/
│   ├── introduction.Rmd
│   ├── offline-detection.Rmd
│   └── bayesian-methods.Rmd
└── data/
```

## Julia Backend

The optional Julia backend provides high-performance implementations:

```r
# Initialize Julia (optional, for better performance)
init_julia()

# Check availability
julia_available()
julia_status()

# Benchmark R vs Julia backends
benchmark_backends(data, method = "pelt")
```

**Julia module exports**: `pelt_detect`, `fpop_detect`, `bocpd_detect`, `cusum_detect`, `kernel_cpd_detect`, `wbs_detect`, `segneigh_detect`, `pelt_multivariate`

\newpage

# API Reference

## Main Function

```r
#' Detect regime changes in time series
#'
#' @param data Numeric vector, ts, or matrix for multivariate
#' @param method Algorithm: "pelt", "bocpd", "cusum", "wbs", 
#'               "shiryaev", etc.
#' @param type Change type: "mean", "variance", "both", "trend", 
#'             "distribution"
#' @param mode Operation mode: "offline" (default), "online"
#' @param penalty For offline methods: "BIC", "AIC", "MBIC", "MDL", 
#'                or numeric
#' @param min_segment Minimum segment length
#' @param prior List with prior specification (for Bayesian methods)
#' @param threshold Detection threshold (for online/CUSUM methods)
#' @param correct_ar Logical: apply AR(1) correction?
#' @param robust Logical: use robust estimation?
#' @param ... Additional method-specific arguments
#'
#' @return Object of class "regime_result"
detect_regimes <- function(data, method = "pelt", ...)
```

## Prior Functions (Bayesian)

```r
# Normal-Gamma prior for BOCPD
normal_gamma(mu0 = 0, kappa0 = 1, alpha0 = 1, beta0 = 1)

# Normal prior with known variance
normal_known_var(mu0 = 0, sigma0 = 1, known_var = 1)

# Normal-Wishart prior for multivariate
normal_wishart(mu0, kappa0, nu0, Psi0)

# Poisson-Gamma prior
poisson_gamma(alpha0 = 1, beta0 = 1)

# Inverse-Gamma prior for variance
inverse_gamma_var(alpha0 = 1, beta0 = 1)

# Hazard rate priors
geometric_hazard(lambda = 0.01)
constant_hazard(rate = 0.01)
negbin_hazard(alpha = 1, beta = 1)
```

## Online API

```r
# Create online detector
detector <- regime_detector(method = "bocpd", prior = normal_gamma())

# Update with new observation
result <- update(detector, x)

# Reset after detection
detector <- reset(detector)
```

## Visualization

```r
# Plot detection results
plot(result)                          # S3 method
plot(result, type = "posterior")      # Posterior probabilities
plot(result, type = "segments")       # Segment visualization

# Additional plots
plot_summary(result)
plot_interactive(result)              # Requires plotly
plot_compare(result1, result2)        # Compare methods
```

## Evaluation

```r
# Evaluate against ground truth
evaluate(result, true_changepoints, tolerance = 5)

# Individual metrics
f1_score(detected, true, tolerance)
hausdorff_distance(detected, true)
rand_index(result, true)
adjusted_rand_index(result, true)
covering_metric(result, true)
precision_score(detected, true, tolerance)
recall_score(detected, true, tolerance)

# Compare methods
compare_methods(data, methods = c("pelt", "bocpd"), 
                true_changepoints = true_cps)
```

\newpage

# Future Directions

## Planned: Integration with EmpiricalDynamics

RegimeChange is designed to complement equation discovery workflows:

| Library | Question |
|:--------|:---------|
| **RegimeChange** | When do the system's properties change? |
| **EmpiricalDynamics** | What is the equation governing the system? |

**Potential workflow**: Instead of searching for ONE equation for the entire dataset, discover different equations for each regime:

$$\text{Raw data} \rightarrow \text{RegimeChange} \rightarrow \text{Segments} \rightarrow \text{EmpiricalDynamics} \rightarrow \text{Switching system}$$

## Other Future Enhancements

- Graph-based changepoint detection (Sulem et al., 2024)
- Improved multivariate optimization for $p >> n$
- Additional deep learning architectures
- Real-time dashboard for online monitoring

\newpage

# Limitations

- **Very short series**: For $n < 100$, standard PELT implementations may be preferable
- **High-dimensional data**: Multivariate support is available but not yet optimized for $p >> n$
- **Computational cost**: Robust estimation is $O(n^2)$ vs $O(n)$ for standard PELT; disable for very large datasets
- **AR + heavy contamination**: When both issues are present, `robust = TRUE` alone typically works better than combining both corrections
- **Deep learning methods**: Require optional dependencies (keras, tensorflow) and may need tuning for specific domains

\newpage

# References

## Fundamental Methods

- Page, E. S. (1954). Continuous inspection schemes. *Biometrika*.
- Basseville, M., & Nikiforov, I. (1993). *Detection of Abrupt Changes: Theory and Application*.
- Killick, R., Fearnhead, P., & Eckley, I. A. (2012). Optimal detection of changepoints with a linear computational cost. *Journal of the American Statistical Association*, 107(500), 1590-1598.
- Killick, R., & Eckley, I. A. (2014). changepoint: An R package for changepoint analysis. *Journal of Statistical Software*, 58(3), 1-19.

## Bayesian Methods

- Adams, R. P., & MacKay, D. J. (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*.

## Deep Learning

- Li, J., Fearnhead, P., Fryzlewicz, P., & Wang, T. (2024). Automatic change-point detection in time series via deep learning. *JRSS-B*.
- Gupta, M., Wadhvani, R., & Rasool, A. (2022). Real-time change-point detection. *Expert Systems with Applications*.

## Robust Statistics

- Rousseeuw, P. J., & Croux, C. (1993). Alternatives to the median absolute deviation. *Journal of the American Statistical Association*, 88(424), 1273-1283.
- Huber, P. J. (1981). *Robust Statistics*. John Wiley & Sons.

## Other Packages

- Truong, C., Oudre, L., & Vayatis, N. (2020). ruptures: change point detection in Python.
- Baumer, B. et al. (2025). tidychangepoint: A Unified Framework. *R package*.
- Fryzlewicz, P. (2014). Wild binary segmentation for multiple change-point detection. *Annals of Statistics*, 42(6), 2243-2281.
- Baranowski, R., Chen, Y., & Fryzlewicz, P. (2019). Narrowest-over-threshold detection of multiple change points and change-point-like features. *Journal of the Royal Statistical Society: Series B*, 81(3), 649-672.
- James, N. A., & Matteson, D. S. (2015). ecp: An R package for nonparametric multiple change point analysis of multivariate data. *Journal of Statistical Software*, 62(7), 1-25.

\newpage

# Glossary

| Term | Definition |
|:-----|:-----------|
| **Changepoint** | Point in time where the statistical properties of a series change |
| **Regime** | Time period with homogeneous statistical properties |
| **Run length** | Time elapsed since the last changepoint |
| **ARL** | Average Run Length: average time until a decision |
| **Hazard rate** | Instantaneous probability that a change occurs |
| **Posterior** | Probability distribution after observing the data |
| **PELT** | Pruned Exact Linear Time: optimal segmentation algorithm |
| **BOCPD** | Bayesian Online Changepoint Detection |
| **CUSUM** | Cumulative Sum: cumulative statistic for detection |

\newpage

# Citation

```bibtex
@software{regimechange2024,
  title = {RegimeChange: Robust Changepoint Detection for Time Series},
  author = {Gómez Julián, José Mauricio},
  year = {2024},
  url = {https://github.com/IsadoreNabi/RegimeChange}
}
```

# License

MIT © José Mauricio Gómez Julián
