Lightweight PELT changepoint detection for TypeScript. No dependencies. Pluggable cost functions.
npm install karaul
import { detectChangepoints } from 'karaul';
const dailyCalls = [820, 835, 791, 810, /* ... */ 1240, 1280, 1195]; // call volumes
const changepoints = detectChangepoints(dailyCalls);
// → [59] — volume spike starts at day 59karaul ships two cost functions and accepts any custom implementation.
For real-valued series where noise is approximately normally distributed — temperature readings, latency measurements, stock prices.
import { detectChangepoints, GaussianCost } from 'karaul';
const changepoints = detectChangepoints(series, new GaussianCost());For non-negative integer count data — daily call volumes, event counts, incident tallies. The Poisson model is the correct generative assumption for count data and outperforms Gaussian on series where values are bounded at zero.
import { pelt, estimatePenalty, PoissonCost } from 'karaul';
const costFn = new PoissonCost();
const penalty = estimatePenalty(series);
const changepoints = pelt(series, penalty, costFn);Implement the CostFunction interface:
import type { CostFunction, TimeSeries } from 'karaul';
class ExponentialCost implements CostFunction {
compute(series: TimeSeries, start: number, end: number): number {
// negative log-likelihood for your distribution
const n = end - start;
let sum = 0;
for (let i = start; i < end; i++) sum += series[i]!;
const lambda = sum / n; // MLE for rate parameter
return 2 * n * lambda - 2 * sum * Math.log(lambda + 1e-9);
}
}
const changepoints = detectChangepoints(series, new ExponentialCost());Core algorithm. Returns an array of changepoint indices.
| parameter | type | description |
|---|---|---|
| series | number[] |
Time series values |
| penalty | number |
BIC penalty per changepoint (use estimatePenalty) |
| costFn | CostFunction |
Cost function instance |
Estimates the BIC penalty from the series using robust sigma estimation (MAD of first differences, divisor 0.6745 × √2). Works well for most real-world series. Returns log(n) × log(max(σ², 1)).
Convenience wrapper: calls estimatePenalty then pelt. Defaults to GaussianCost if costFn is omitted.
karaul uses in-place active set pruning (splice, backwards iteration) instead of Array.filter(). This eliminates one temporary array allocation per outer loop iteration — n allocations avoided at n=5000. Raw throughput on structured series with real changepoints:
| series length | time |
|---|---|
| 365 points | ~3ms |
| 1000 points | ~11ms |
| 5000 points | ~124ms |
Performance degrades to O(n³) for truly stationary white-noise series where no pruning occurs. This is a property of PELT, not an implementation bug — on real data with structural shifts, the active set stays bounded.
See demo/ for a full call analytics pipeline using karaul on synthetic Glados Therapeutics call center data: topic extraction, daily volume aggregation, changepoint detection with PoissonCost, Jira correlation, and LLM causal reasoning.