madwatch
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.
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
Metadata
Release files for madwatch 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| madwatch-0.1.0.tar.gz | 64.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| madwatch-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 72.7 kB
Release files / madwatch-0.1.0.tar.gz
| Download URL | madwatch-0.1.0.tar.gz |
|---|---|
| Size | 64.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bfecc93a4641c9c62ad53d10c9aa3be993a41890435802420f53a34d9db654da
|
|
BLAKE2b-256 checksum How to use checksums |
1bac7998719fc6624b0d0204939dd19adf88023f7297611567beff1a05d28ec7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logRelease files / madwatch-0.1.0-py3-none-any.whl
| Download URL | madwatch-0.1.0-py3-none-any.whl |
|---|---|
| Size | 8.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
67213c02ecd4cc3bef9c35d996c74122d527c5f02b63d066650e71365a63ade1
|
|
BLAKE2b-256 checksum How to use checksums |
e551773b2b43ebaa68fb7e7bcf86371da27f8a83c75bf6c606bb6444cfd06a61
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency log