Visakh Unni.

tsanomaly: A Generic Framework for Time-Series Anomaly Detection

Visakh Unni8 min read
A 3D landscape of blue time-series ridges with anomalous stretches rising in orange

These are my notes on tsanomaly - a Python library I recently open-sourced for autonomous, explainable, real-time anomaly detection on time-series metrics. Notes on the mechanisms behind it, and on the concepts from the Anodot patents that inspired it.


The Curse of Low Dimensionality

I have spent a good part of my career building analytics and data science solutions on top of machine data - sensor readings, industrial telemetry, operational metrics. Time series from machines have a property that quietly breaks most of the modeling playbook: they carry very little information per observation. An image has thousands of correlated pixels; a document has a vocabulary. A metric has a timestamp and one number. That is the entire feature space.

With so few dimensions, forecasting and anomaly detection models have almost nothing to hold on to. Static thresholds drown you in false alarms the moment a metric has a daily rhythm. Classical models want per-series tuning, which is unthinkable when you monitor thousands of metrics. And every metric has its own personality - a spiky network counter and a smooth temperature sensor should not be judged by the same yardstick.

Years ago, while digging through this problem space, I came across the patents of Anodot, an Israeli company that had built a generic anomaly-detection product for exactly this kind of data. I really liked the way they framed the solution: learn a baseline for every metric autonomously, score anomalies against that metric's own history rather than a global rule, detect seasonality automatically, and condense concurrent anomalies across metrics into single incidents [1] [2]. That framing stuck with me. tsanomaly is my take on the same problem - inspired by those ideas, implemented with different, more recent statistical machinery.

What tsanomaly Is

tsanomaly watches any number over time, learns what normal looks like for that specific series, and reports genuinely unusual events with a calibrated 0-100 score and a full explanation:

pip install tsanomaly
import tsanomaly as tsa

det = tsa.Detector.auto()
det.fit(history_df)               # learn normal, per metric
result = det.detect(new_df)       # scored, explained anomalies

for anomaly in result.alerts(min_score=70):
    print(anomaly.explain().headline)

Here is real output on real data - NYC taxi ridership from the Numenta Anomaly Benchmark [8]. The detector learned the daily and weekly rhythm on four months of history, then found the documented disruptions on its own:

`nyc.taxi.passengers` dropped to 7076 (expected 18060 to 25606)
    for 33.0 h starting 2014-11-27 05:30 UTC - score 100.   # Thanksgiving
`nyc.taxi.passengers` spiked to 23848 (expected 15488 to 19792)
    for 6.0 h starting 2015-01-18 09:30 UTC - score 100.    # MLK weekend
`nyc.taxi.passengers` dropped to 570 (expected 15629 to 22110)
    for 39.0 h starting 2015-01-26 11:30 UTC - score 100.   # blizzard travel ban
NYC taxi ridership with the learned envelope; the MLK weekend surge and the January 2015 blizzard travel ban flagged in red with score 100

That last red block is the January 2015 North American blizzard, when New York banned road travel and city-wide taxi ridership collapsed to almost nothing for 39 hours. Nobody told the model about blizzards.

How It Works

The tsanomaly pipeline: ingest, clean, profile, seasonality, and baseline feed a self-calibrating envelope into detection, scoring, incidents, and explanation

Every metric flows through the same loop: learn normal behavior, predict a corridor of expected values, detect departures, score their severity, explain them. A few of the stages carry most of the intelligence:

  • Seasonality is proposed, then verified. A Lomb-Scargle periodogram [11] proposes candidate periods (robust to missing data, easily fooled by noise); autocorrelation at the period and its multiples verifies them against a block-shuffle null (hard to fool). The shortest verified period wins, gets subtracted, and the search repeats - so a metric with both daily and weekly rhythm gets both, and a weekly harmonic never masquerades as the real cycle.
  • The envelope is measured, not assumed. The corridor of normal around the prediction is calibrated by adaptive conformal inference [6]: the width follows the measured error distribution, and a feedback loop keeps actual coverage at the 99.5% target. If the metric gets noisier, the envelope widens itself; anomalous points are never allowed to widen their own envelope.
  • Rarity comes from extreme value theory. How far outside the envelope a point landed is converted to a probability with a generalized Pareto tail model [7] - because Gaussian tails badly overstate how rare large deviations are on real metrics.
  • Detection works on episodes, not points. A single point outside a 99.5% envelope is expected once in 200 samples, so consecutive excursions are grouped with hysteresis and scored as one event, from three kinds of evidence: magnitude, duration, and persistence, combined with a harmonic-mean p-value [9] that stays valid when the evidence is correlated.
  • A new normal is not an anomaly. When a deploy drops latency permanently, Bayesian online changepoint detection [5] resolves the shift into a single “regime change” finding and re-anchors the baseline - one notification instead of days of alerts.
  • Concurrent anomalies become one incident. When episodes on different metrics overlap far more than chance predicts, they merge into an incident whose members are ordered by who moved first - a built-in root-cause hint, with the effective number of independent signals computed properly [10] so correlated members are never double-counted.

And everything explains itself: every anomaly carries its expected range, its score decomposition, the model that produced it, and a counterfactual - “no alert would have fired for values between X and Y at that time.”

Prior Works

The architecture - autonomous per-metric baselines, scoring against a metric's own history, automatic seasonality, incident grouping - is the shape Anodot's patents describe [1] [2] [3]. The mechanisms inside each box are deliberately different, and in most cases benefit from statistical tools published after those patents were filed:

  • Envelope widths come from adaptive conformal inference (2021) with a measured coverage guarantee, rather than distributional assumptions around the baseline.
  • Seasonality uses periodogram-plus-verification with a permutation null, rather than the patents' geometrically-sampled autocorrelation scan [2].
  • Tail rarity uses extreme value theory (the DSPOT lineage [7]) rather than learned distributions of past anomaly intensities [1].
  • Regime changes are resolved by Bayesian online changepoint detection with explicit stationarity gates, and scores are damped while the re-anchored baseline re-learns - the system knows when it does not yet know.
  • Evidence combination uses the harmonic-mean p-value (2019) and incident significance uses Galwey's effective number of tests, replacing heuristic score combination.

None of this is a claim of superiority over a product that has been refined in production for a decade - it is a design lineage, acknowledged openly in the architecture doc, with the mechanisms swapped for ones I could build honestly in the open, on published research.

Proving It on Machine Data

Given where this started for me, the test I cared most about was industrial sensor data. The Bosch CNC Machining dataset [12] contains tri-axial vibration from a production milling machine, recorded over two and a half years, with each machining cycle labeled good or bad by process experts. I reduced each cycle to per-second vibration energy, fit on the 2019 cycles, and detected from 2020 on:

CNC spindle vibration across 24 machining cycles; the two cycles Bosch labeled bad are flagged at scores 87 and 100, with indigo bars marking the ground-truth labels

The two cycles that alert - scores 87 and 100 - are exactly the two cycles Bosch labeled bad (the indigo bars). The 22 good cycles produce zero false alerts. And the training window itself contained two mildly anomalous cycles that the robust learners simply absorbed without being told. That, in one chart, is the property I wanted: judgment calibrated to each machine's own normal, with the receipts to explain every call.

Try It

The library is on PyPI (pip install tsanomaly, Python 3.9+) and the source is on GitHub, with a usage guide, the full architecture doc, and runnable examples.

Issues and pull requests are welcome.


References

  1. US 10,061,632 - System and method for transforming observed metrics into detected and scored anomalies. Google Patents
  2. US 10,061,677 - Fast automated detection of seasonal patterns in time series data. Google Patents
  3. US 2016/0210556 A1 - Heuristic inference of topological representation of metric relationships. Google Patents
  4. US 12,101,343 - Event-based machine learning for a time-series metric. Google Patents
  5. Adams RP, MacKay DJC (2007). “Bayesian Online Changepoint Detection.” arXiv:0710.3742
  6. Gibbs I, Candès E (2021). “Adaptive Conformal Inference Under Distribution Shift.” NeurIPS 2021. arXiv:2106.00170
  7. Siffer A, Fouque P-A, Termier A, Largouët C (2017). “Anomaly Detection in Streams with Extreme Value Theory.” KDD 2017. DOI
  8. Lavin A, Ahmad S (2015). “Evaluating Real-Time Anomaly Detection Algorithms - the Numenta Anomaly Benchmark.” NAB corpus
  9. Wilson DJ (2019). “The harmonic mean p-value for combining dependent tests.” PNAS 116(4). DOI
  10. Galwey NW (2009). “A new measure of the effective number of tests.” Genetic Epidemiology 33(7). DOI
  11. VanderPlas JT (2018). “Understanding the Lomb-Scargle Periodogram.” ApJS 236. DOI
  12. Tnani M-A, Feil M, Diepold K (2022). “Smart Data Collection System for Brownfield CNC Milling Machines.” Procedia CIRP 107. Bosch CNC Machining dataset

More from the blogs