Model Comparison

Choosing an observation model amounts to picking the likelihood–prior pair that best matches your data. The following table summarises the trade-offs.

Model

Data assumptions

Robustness

Computational cost

Recommended use cases

Gaussian-NIG

Continuous, approximately Gaussian

Moderate (sensitive to outliers)

Fast (baseline)

Sensor drift, financial spreads, streaming KPIs

Student-t (fixed ν)

Continuous with occasional outliers

High (ν controls tail weight)

~20% slower than Gaussian

Industrial monitoring, heavy-tailed returns

Student-t (ν grid)

Continuous, unknown tail weight

Very high (adapts ν online)

6–7× slower (mixture update)

Critical systems where robustness outweighs speed

Poisson-Gamma

Non-negative integer counts

Moderate (handles overdispersion)

Comparable to Student-t

Event counts, telemetry hits, arrivals

Bernoulli-Beta

Binary indicators

High (exact conjugacy)

Fastest model

Success/failure streams, anomaly flags

Binomial-Beta

Counts out of N trials per time step

High

Midrange (binomial coefficients)

Conversion rates, aggregated proportions

Gamma-Gamma

Positive continuous values (rates/durations)

Moderate (depends on fixed shape k)

Slightly slower than Gaussian

Transaction sizes, waiting times

Hazard Interaction

The hazard controls how aggressively we expect changepoints. A low \(\lambda\) => frequent changes => shorter regimes. Observation models rely on sufficient statistics whose variance scales with run length. Consequently:

  • Heavy-tailed models (Student-t) tolerate higher \(\lambda\) (longer regimes) without false alarms.

  • Discrete models (Bernoulli/Binomial/Poisson) are less sensitive to the hazard choice because their statistics saturate quickly (counts).

Implementation Notes

  • Each Python class in fast_bocpd.models mirrors the C implementation to keep parameter names consistent between theory and code.

  • Hazard functions live in fast_bocpd.hazard; currently only the constant hazard is exposed, but the recursion supports any \(P(r_t \mid r_{t-1})\).

  • Switching models requires only substituting the Python wrapper; the C engine handles the rest via virtual dispatch.