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 Facedatasets).
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 asjoblib/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 forsorethumb 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
explainextra 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 ashistory.anchor_runsosorethumb historymeasures every period against it. Without one, treatbackfilland 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:
- Getting started: the command line, step by step.
- Adapting to your data: what to change when the defaults do not fit.
- Limitations: the full list.
- Ensemble policy: what decides which rows are flagged, and the evidence.
- Explanations: what each reason means.
- Benchmark results and the scale guide: how well it works, and what it costs.
- CLI reference and configuration reference.
- Security policy and contributing.
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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sorethumb_ml-0.1.0.tar.gz | 1.2 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.