|
| 1 | +# Penalized L1 Potts segmentation (`L1Potts`) |
| 2 | + |
| 3 | +## Description |
| 4 | + |
| 5 | +The method is implemented in [`L1Potts`][ruptures.detection.l1potts.L1Potts]. |
| 6 | +It computes the global minimizer of the **L1 Potts functional** for piecewise constant 1D signals: |
| 7 | + |
| 8 | +$$ |
| 9 | +\min_{u \in \mathbb{R}^N} \;\; \gamma \sum_{i=1}^{N-1} \mathbb{1}(u_i \neq u_{i+1}) \;+\; \sum_{i=1}^{N} w_i \, |f_i - u_i| |
| 10 | +$$ |
| 11 | + |
| 12 | +where $f$ is the observed signal, $w$ are non-negative per-sample weights, and $\gamma > 0$ is the jump penalty. |
| 13 | + |
| 14 | +The L1 fit makes the estimator robust to heavy-tailed noise and outliers, in contrast to the L2 Potts model used by other ruptures detectors (`Pelt(model="l2")`, `Dynp(model="l2")`). |
| 15 | + |
| 16 | +The implementation is **Algorithm 1 of [[Storath2017]](#Storath2017)**, which solves the problem exactly in $\mathcal{O}(KN)$ time, where $N$ is the number of samples and $K$ the number of distinct values in the signal. The algorithm is much faster than `Pelt(model="l1")` (a 20–30× speedup is typical on a few-thousand-sample noisy signal). It uses a Viterbi-type dynamic program over (level, sample) pairs, where the candidate levels are the unique observed values — the optimal segment level is always one of them, since the weighted L1 median lies in the data. |
| 17 | + |
| 18 | +`L1Potts` accepts only 1D signals. Penalty-only mode (`predict(pen=...)`) is the only supported prediction mode; `n_bkps` and `epsilon` are not. |
| 19 | + |
| 20 | + |
| 21 | +## Usage |
| 22 | + |
| 23 | +```python |
| 24 | +import numpy as np |
| 25 | +import matplotlib.pylab as plt |
| 26 | +import ruptures as rpt |
| 27 | + |
| 28 | +# creation of data with heavy-tailed (Laplace) noise |
| 29 | +n, sigma = 500, 1.0 |
| 30 | +n_bkps = 3 |
| 31 | +signal, bkps = rpt.pw_constant(n, 1, n_bkps, noise_std=sigma) |
| 32 | +signal = signal.ravel() + np.random.default_rng(0).laplace(scale=sigma, size=n) |
| 33 | + |
| 34 | +# change point detection |
| 35 | +algo = rpt.L1Potts().fit(signal) |
| 36 | +my_bkps = algo.predict(pen=3.0) |
| 37 | + |
| 38 | +# show results |
| 39 | +rpt.show.display(signal, bkps, my_bkps, figsize=(10, 6)) |
| 40 | +plt.show() |
| 41 | +``` |
| 42 | + |
| 43 | +To downweight known outlier samples, pass per-sample weights to `fit`: |
| 44 | + |
| 45 | +```python |
| 46 | +weights = np.ones(n) |
| 47 | +weights[outlier_indices] = 1e-3 # near-zero weight effectively ignores those samples |
| 48 | +my_bkps = rpt.L1Potts().fit(signal, weights=weights).predict(pen=3.0) |
| 49 | +``` |
| 50 | + |
| 51 | +`fit_predict` is also available: |
| 52 | + |
| 53 | +```python |
| 54 | +my_bkps = rpt.L1Potts().fit_predict(signal, pen=3.0, weights=weights) |
| 55 | +``` |
| 56 | + |
| 57 | + |
| 58 | +## References |
| 59 | + |
| 60 | +<a id="Storath2017">[Storath2017]</a> |
| 61 | +Storath, M., Weinmann, A., & Unser, M. (2017). Jump-penalized least absolute values estimation of scalar or circle-valued signals. *Information and Inference: A Journal of the IMA*. Preprint: <https://bigwww.epfl.ch/preprints/storath1602p.pdf> |
0 commit comments