Skip to main content

Robust anomaly detection on MAD and the Modified Z-Score, with seasonal baselines

Project description

madwatch

CI PyPI Python License: MIT

Robust anomaly detection that doesn't panic at paydays.

madwatch scores time-series values with the Modified Z-Score over MAD (Median Absolute Deviation) instead of mean/standard deviation. Medians don't care about your whale transaction: one huge outlier can't inflate the baseline and mask the next real anomaly.

demo

Install

pip install madwatch        # core, numpy only
pip install 'madwatch[cli]' # + CLI, pandas, matplotlib

Quickstart

from madwatch import RollingDetector

det = RollingDetector(window=40, threshold=3.5, min_samples=10)
for value in stream:
    score = det.update(value)
    if score.is_anomaly:
        alert(value, score.z)

Why MAD?

Standard deviation has a design flaw for anomaly detection: the anomaly you are trying to catch is inside the calculation. One spike inflates sigma, the threshold stretches, and the next three real anomalies walk through undetected. MAD is median-based, so a single outlier in the window barely moves the baseline. The 0.6745 constant makes the score comparable to a classic z-score on normal data, so the usual "flag at 3.5" rule still reads naturally.

Longer version: Why MAD instead of standard deviation?

Seasonal baselines

Mondays don't look like Saturdays and 9 AM doesn't look like midnight. Comparing a value against a global baseline produces false alarms at every weekly rhythm. SeasonalBaseline buckets history by day-of-week and/or hour and scores each point against its own bucket:

from madwatch import SeasonalBaseline

sb = SeasonalBaseline(granularity="dow_hour").fit(timestamps, values)
z = sb.score(new_timestamps, new_values)

In production use on financial streams, this combination cut false positives by roughly 60% compared to a naive z-score.

CLI

madwatch data.csv --column amount --window 40 --threshold 3.5 --plot out.png
madwatch data.csv --column amount --timestamp ts --seasonal dow_hour
where                        value         z
2026-02-07T12:00:00         512.00      9.41
1 anomalies in 312 points

API

Name What it does
mad(x) Median Absolute Deviation of an array
modified_zscore(x, scale=0.6745) Per-element robust z-score
RollingDetector(window, threshold, min_samples) Streaming detection over a trailing window
SeasonalBaseline(granularity) Per-bucket (dow/hour) baselines with global fallback

Behavior notes: constant windows (MAD = 0) score z = 0; the detector stays silent until min_samples values have arrived; NaN input raises ValueError (the CLI skips NaN rows with a warning).

Development

uv venv && uv pip install -e '.[dev]'
pytest
ruff check src tests

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

madwatch-0.1.0.tar.gz (64.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

madwatch-0.1.0-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file madwatch-0.1.0.tar.gz.

File metadata

  • Download URL: madwatch-0.1.0.tar.gz
  • Upload date:
  • Size: 64.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for madwatch-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bfecc93a4641c9c62ad53d10c9aa3be993a41890435802420f53a34d9db654da
MD5 9163d56a116cc3cbe1d4f07724544727
BLAKE2b-256 1bac7998719fc6624b0d0204939dd19adf88023f7297611567beff1a05d28ec7

See more details on using hashes here.

Provenance

The following attestation bundles were made for madwatch-0.1.0.tar.gz:

Publisher: release.yml on efekckk/madwatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file madwatch-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: madwatch-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for madwatch-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 67213c02ecd4cc3bef9c35d996c74122d527c5f02b63d066650e71365a63ade1
MD5 9e4c7e6268a320329545cd90e802d879
BLAKE2b-256 e551773b2b43ebaa68fb7e7bcf86371da27f8a83c75bf6c606bb6444cfd06a61

See more details on using hashes here.

Provenance

The following attestation bundles were made for madwatch-0.1.0-py3-none-any.whl:

Publisher: release.yml on efekckk/madwatch

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page