Skip to main content
sorethumb — unsupervised anomaly detection for tabular data

PyPI CI codecov Python Ruff uv License

sorethumb takes a dataset it knows nothing about, works out how to treat every column, fits an ensemble of detectors, ranks the records that stand out, and explains why each one stands out in terms of the original columns — so the result is actionable without a background in machine learning.

It ranks; it does not count. The result is an ordering of records by statistical oddity, plus a flag decision on top of it: a shortlist for a person to review. The flagged count is never a measurement of how many anomalies your data holds, and by default (scoring.contamination = "auto") nobody chose its size: each detector flags by its own boundary, and a row is flagged only if all of them flag it. So read the ranking first. Every run ends with its top-ranked rows, flagged or not, and a first run says all this prominently.

It runs on one machine (Linux, macOS or Windows) and is measured up to 1,000,000 rows (scale guide).

Status: alpha (0.1.0). The API and on-disk formats may still change between minor versions. See Limitations for what it does not do.


Installation

pip install sorethumb-ml

That installs the sorethumb command and the sorethumb_ml Python package. (The sorethumb package on PyPI is unrelated.)

To install from a clone instead (to run the benchmarks or contribute), see CONTRIBUTING.md.

The core install is everything the quickstart below, the sorethumb command, and the CSV/HTML reports need. Optional extras add to it:

  • pip install "sorethumb-ml[explain]" adds the SHAP package for TreeSHAP and KernelSHAP attributions. Without it those explanations fall back to a finite-difference method and say so with a warning.
  • pip install "sorethumb-ml[benchmark]" adds what the benchmark harness needs (pandas, pyarrow and Hugging Face datasets).

60-second quickstart

Two ways in: the sorethumb command on a file you already have, or the Python API. Both work with the core install alone.

From the command line

No config file is needed. Pass a data file and sorethumb runs with its defaults, writing everything under one ./sorethumb-workspace/ directory in the directory you run it from:

cd /path/to/my-analysis
sorethumb run /path/to/data.csv
sorethumb anomalies --top 10      # the flagged rows, with reasons
sorethumb anomalies --ranked      # the top of the ranking, flagged or not
sorethumb report                  # the HTML report

For full control, sorethumb init writes a commented sorethumb.toml; set its source and check the column plan before running:

[source]
uri = "/absolute/path/to/your/data.csv"    # also .parquet, .json, and http(s):// URLs
sorethumb inspect                 # how each column will be treated; no models trained
sorethumb run

The workspace is executable, not just data. Its models/ directory holds fitted estimators as joblib/pickle files, which run arbitrary code when loaded. Only open a workspace you created or fully trust. See SECURITY.md.

Getting started walks through the command line step by step, with the output you will see and every file format it reads.

From Python

No network access and nothing beyond sorethumb's own core dependencies — the dataset is a small synthetic table generated in-process, with 30 of its 2,000 rows nudged far outside the normal range so there's something for the detectors to actually find:

import tempfile
from pathlib import Path

import numpy as np
import polars as pl

from sorethumb_ml import Config, SourceConfig, run_detection

# A fresh directory every run -- sorethumb's runs are idempotent/resumable
# (it skips a group it's already completed for a given config+data), so a
# fixed path would make a second copy-paste of this snippet report 0 newly
# succeeded groups instead of re-running the demo.
tmp_dir = Path(tempfile.mkdtemp(prefix="sorethumb_quickstart_"))

rng = np.random.default_rng(0)
n_rows, n_anomalies = 2_000, 30
amount = rng.normal(loc=50.0, scale=10.0, size=n_rows)
latency_ms = rng.normal(loc=200.0, scale=30.0, size=n_rows)
region = rng.choice(["us", "eu", "apac"], size=n_rows)

# Push a random slice of rows far outside the normal range.
anomaly_idx = rng.choice(n_rows, size=n_anomalies, replace=False)
amount[anomaly_idx] = rng.uniform(500.0, 1000.0, size=n_anomalies)
latency_ms[anomaly_idx] = rng.uniform(2000.0, 5000.0, size=n_anomalies)

df = pl.DataFrame({"amount": amount, "latency_ms": latency_ms, "region": region})
df.write_csv(tmp_dir / "quickstart.csv")

config = Config(
    source=SourceConfig(uri=str(tmp_dir / "quickstart.csv")),
    run={"workdir": str(tmp_dir / "workspace")},
)
result = run_detection(config, no_report=True)
print(f"Flagged {result.n_anomalies} rows for review across {result.n_succeeded} group(s)")

# The ranking is the result; the flagged rows are a shortlist drawn from it.
for row in result.groups[0].top_ranked[:5]:
    flag = "flagged" if row["flagged"] else "not flagged"
    print(row["score_rank"], f"{row['composite_score']:.4f}", flag, row["values"])

It flags more rows than the 30 it planted (52 when this was written): at the default scoring.contamination = "auto" nobody chose that count. The planted rows are what lead the ranking, which is why the ranking, not the count, is the result to read.


What it does not do

The short version; every point is expanded, with where it is measured, in Limitations.

  • It ranks; it does not count. The flagged number is a review shortlist, not an estimate of how many anomalies your data holds. By default its size is set by the detectors' own boundaries, not by you.
  • It is unsupervised. Detectors fit on the same data they score, with no held-out normal reference, and the slower ones fit on a row sample.
  • Scores are rankings, not probabilities, and two independently fitted runs are not on a common scale. Reuse a fitted run (sorethumb score --from-run), or set an anchor run for sorethumb history, to compare on one scale.
  • Explanations are exact only for ECOD and HBOS. The rest are model-specific or heuristic, labelled as such, and a missing explain extra downgrades some.
  • A workspace is executable, not just data. Persisted models are pickles; open only a workspace you created or fully trust.
  • One machine, local storage. It runs on Linux, macOS and Windows, keeps its workspace on a local drive (not a network share or a synced folder), and is measured up to 1,000,000 rows; larger datasets are untested.

Is it the right tool?

Use it where a ranked shortlist for a person to review is the goal: on a single machine (Linux, macOS or Windows, see Supported platforms), over data that fits the scale guide, with someone able to judge whether a flagged record is actually interesting.

  • Do not use it as the only control where a missed anomaly is unacceptable. It is unsupervised and ranks statistical oddity, so it can miss exactly the record you care about. Where missing one has serious consequences, use a labelled, supervised or domain-specific control and treat this as an extra signal at most.
  • Validate before you rely on error rates. If a decision depends on how many flagged records are false positives, or how many true anomalies are missed, measure that on labelled data and have a domain reviewer check the flagged records. Scores and flagged counts are not calibrated risk probabilities and are not a prevalence estimate, and should not be presented as either.
  • Compare over time on one scale. For an absolute comparison across periods, fit one accepted reference run and score later data against it with sorethumb score --from-run RUN_ID, or set it as history.anchor_run so sorethumb history measures every period against it. Without one, treat backfill and independent-run history as relative diagnostics only.
  • Only reuse models from a workspace you made or fully trust. A workspace is executable, not just data: do not accept a downloaded, shared or externally supplied workspace. See SECURITY.md.

Which setup?

You want to Do this
Look at a new dataset Run it with the defaults and read the top-ranked rows; the flagged rows are where the detectors agree.
Review a fixed number of rows Set a review budget: scoring.contamination to that share, with the composite-agreement policy. See Recommended setup if you have a review budget.
Know which policy works on your data Run sorethumb backtest against labelled rows of your own data.
Compare periods on one scale Fit one accepted run, then sorethumb score --from-run RUN_ID, or set it as history.anchor_run. See trends.
Change how a column is treated sorethumb inspect first, then the settings in Adapting to your data.

Supported platforms

OS Python Tested in CI on every change
Linux (x86-64) 3.11–3.14 unit, contract, integration, property, packaging (ubuntu-latest)
macOS (Apple silicon) 3.11–3.14 unit, contract, integration (macos-latest)
Windows 10/11 (x64) 3.11–3.14 unit, contract, integration, and installing the built wheel and sdist and running them from PowerShell (windows-latest)

Unit and contract tests run on every listed Python version; the integration suite runs on 3.12 in the release gate and on 3.11 and 3.14 daily. Supported platforms has the detail, and what is not supported (a workspace on a network share or synced folder, among others).


Documentation

The documentation index is the map. The pages a new user needs first:

Metadata

Release files for sorethumb-ml 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sorethumb-ml 0.1.0
File Size Uploaded Provenance
sorethumb_ml-0.1.0.tar.gz 1.2 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for sorethumb-ml 0.1.0
File Interpreter ABI Platform
sorethumb_ml-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.6 MB

Release files / sorethumb_ml-0.1.0.tar.gz

Download URL sorethumb_ml-0.1.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
746141f00b1fa175ddf7c1c95f197e7adc45ef6c5c0898f9244cd4d05550e15e
BLAKE2b-256 checksum
How to use checksums
5a1e969d993058c8b43cbc4f4f88234223e701cc8fbbe2026b52cb5275e405ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / sorethumb_ml-0.1.0-py3-none-any.whl

Download URL sorethumb_ml-0.1.0-py3-none-any.whl
Size 387.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4ae48a6ebda2187f73886b9ce6c73175e1e06ae893f230caac67fed260fe6351
BLAKE2b-256 checksum
How to use checksums
e953ec36ea1398b5bebaa54b505b7a1181af3e1c6764188623dd48ba61ca43a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for sorethumb-ml 0.1.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page