ml-xray
Framework-agnostic ML model & dataset analysis across the training lifecycle.
ml-xray inspects the things that actually move a model's quality —
leakage, drift, label noise, class imbalance, outliers before training,
and where a trained model fails and how its embeddings shifted after
training. It takes arrays and DataFrames, never model objects, so it drops into
any stack (scikit-learn, XGBoost, LightGBM, PyTorch, or just a CSV of
predictions) with zero coupling.
→ Project site & showcase · GitHub
pip install ml-xray
# Gate a training pipeline on data quality (non-zero exit on ERROR findings)
ml-xray lint data.csv --target y --split split --fail-on error --html report.html
import pandas as pd
from ml_xray.lint import Linter
df = pd.read_csv("data.csv")
report = Linter(target="y").run(df, split=df["split"])
print(report.worst(5)) # the 5 most severe findings
report.to_html("report.html")
if not report: # False if any ERROR finding exists
raise SystemExit("data quality gate failed")
Scope — what ml-xray is not
- Not a training framework. It never trains your model for you; it analyzes data and predictions you already have.
- Not experiment tracking. MLflow / Weights & Biases / Aim own that.
- Not schema matching. Valentine owns that.
- Not a generic dataset profiler. ydata-profiling owns generic profiling.
ml-xrayis opinionated toward decisions that affect a model — leakage, drift, label noise, and where a trained model fails — not exhaustive column statistics.
Inputs are always arrays / DataFrames (y_true, y_pred, y_proba, a feature
DataFrame, or plain embedding matrices). ml-xray never imports or requires a
model object.
Capabilities
| Stage | Module | What it does |
|---|---|---|
| Pre-training | ml_xray.lint |
Dataset QA: leakage, drift, label noise, duplicates, imbalance, outliers. |
| Post-training | ml_xray.slices |
Error analysis / slice discovery — find where a model underperforms. |
| Post-training | ml_xray.embed |
Embedding diff — compare two embedding spaces. |
All three modules are implemented, return structured result objects, and emit
self-contained HTML report sections that ml-xray report stitches into one file.
ml_xray.lint — dataset QA (Phase 1)
Each check is a pluggable Check subclass and emits structured Findings with a
severity (INFO / WARN / ERROR), the offending column, backing numbers, and
row indices where applicable.
- leakage — feature/target correlation ≈ 1, features that deterministically predict the target, and duplicate rows shared across a train/test split (the most common silent leak).
- drift — per-column train-vs-test distribution drift via PSI, the Kolmogorov–Smirnov test (numeric), and Jensen–Shannon divergence (categorical).
- label_noise — mislabel candidates from out-of-fold predictions
(confidently-wrong / low-margin rows); delegates to
cleanlabwhen the[noise]extra is installed. - duplicates — exact duplicate rows and near-duplicate clusters via MinHash + LSH.
- imbalance — class imbalance ratio, rare categorical levels, single-value columns.
- outliers — robust-z / IQR numeric outliers, high-null-fraction columns, and constant columns.
- temporal_leakage — given a time column (
Linter.run(..., time="ts")or--time), flags training rows dated after a later split (future leaking into train) and features that are near-monotonic proxies for time.
ml_xray.slices — slice discovery (Phase 2)
Automatically find where a trained model underperforms — e.g. "accuracy is 0.49
on region=EU (n=632) vs 0.84 overall". Continuous features are discretized,
slices up to max_depth conjunctions are enumerated with Apriori-style pruning,
each is scored and tested for significance with a Benjamini–Hochberg correction
across all slices tested, and the survivors are ranked by
|underperformance| × log(support).
from ml_xray.slices import SliceFinder
report = SliceFinder(metric="auto", max_depth=2, min_support=30).fit(
X, y_true, y_pred, y_proba
).report()
for s in report.slices[:5]:
print(s.describe(), s.support, s.metric_value, s.delta, s.p_value)
ml_xray.embed — embedding diff (Phase 3)
Compare two embedding spaces — v1 vs v2, or embeddings over time — to see what moved: k-NN Jaccard neighbor overlap (local structure), per-point drift (which items moved), and cluster stability via Adjusted Rand Index (global structure). Spaces of different dimensionality are aligned with orthogonal Procrustes for the projection scatter; the overlap metrics are dimension-free.
from ml_xray.embed import EmbeddingDiff
report = EmbeddingDiff(k=10).fit(emb_a, emb_b, ids=ids).report()
print(report.neighbor_overlap, report.cluster_stability)
print(report.movers[:10]) # ids whose neighborhoods changed most
Using it in CI
Configuration (ml-xray.toml or [tool.ml-xray])
Pin which checks run and how severe their findings are, without touching code:
# ml-xray.toml
checks = ["leakage", "drift", "duplicates", "imbalance"] # omit to run all
disable = ["outliers"]
seed = 7
[severity]
duplicates = "info" # downgrade every duplicates finding
"imbalance.rare_levels" = "ignore" # silence a specific kind
"drift.numeric" = "error" # escalate another
[check_args.outliers]
ranges = { age = [0, 120] } # declared valid ranges
ml-xray lint data.csv --target y --config ml-xray.toml --fail-on error
Baseline / regression tracking
Gate CI on new problems a change introduces, not on pre-existing debt:
# once: snapshot the current state
ml-xray lint data.csv --target y --save-baseline .ml-xray-baseline.json
# in CI: fail only if the change adds a new ERROR-level finding
ml-xray lint data.csv --target y \
--baseline .ml-xray-baseline.json --fail-on-new error
from ml_xray import Linter, diff_reports
from ml_xray.lint.linter import LintReport
baseline = LintReport.from_json(".ml-xray-baseline.json")
current = Linter(target="y").run(df)
diff = diff_reports(baseline, current)
print(diff.counts()) # {'new': ..., 'resolved': ..., ...}
if not diff.is_clean(): # any NEW error-level finding?
raise SystemExit("new data-quality regressions")
pre-commit hook
repos:
- repo: https://github.com/OwenDinsmore/ml-xray
rev: v0.2.0
hooks:
- id: ml-xray-lint
args: [data/train.csv, --target, y, --fail-on, error]
Design principles
- Framework-agnostic array / DataFrame contracts.
- Deterministic & reproducible — everything is seeded; the same input produces the same report.
- Report-first — every module returns a structured result object and can emit a self-contained HTML report section.
- Statistically honest — findings carry support size and a significance/effect estimate; slice discovery corrects for multiple comparisons.
- Lazy optional deps —
umap-learn,riskplot/plotly, andcleanlablive behind extras and degrade gracefully when absent.
Installation extras
pip install ml-xray # core: numpy, pandas, scikit-learn, jinja2
pip install "ml-xray[embeddings]" # umap-learn projections for embed-diff
pip install "ml-xray[viz]" # riskplot / plotly rich charts
pip install "ml-xray[noise]" # cleanlab confident-learning label noise
pip install "ml-xray[all]" # everything
CLI
ml-xray lint DATA --target y [--split col] [--time col] [--config ml-xray.toml] \
[--baseline base.json] [--save-baseline base.json] \
[--fail-on error] [--fail-on-new error] [--html out.html] [--json out.json]
ml-xray slices PREDS [--features cols] [--metric auto|accuracy|f1|mse|mae|roc_auc|log_loss] \
[--html out.html] [--interactive]
ml-xray embed-diff A.npy B.npy [--ids ids.csv] [-k 10] [--backend auto|exact|approx] \
[--html out.html] [--interactive]
ml-xray report --lint DATA --target y --slices PREDS --embed A.npy B.npy --html out.html
--interactive embeds a self-contained plotly chart (needs ml-xray[viz]);
--backend approx uses pynndescent (ml-xray[embeddings]) for large embedding
sets. Both degrade gracefully when the optional dependency is absent.
PREDS is a CSV with y_true,y_pred[,y_proba] columns plus the feature columns
to slice on.
Roadmap
- Phase 1 — done:
ml_xray.lint+ HTML report +ml-xray lintCLI. - Phase 2 — done:
ml_xray.slices+ml-xray slices. - Phase 3 — done:
ml_xray.embed+ml-xray embed-diff. - Phase 4 — done: unified
ml-xray reportstitching all sections into one self-contained HTML file, with a matplotlib viz backend. - v0.2 — done: TOML config (check selection + severity overrides), baseline
snapshots and regression diffing,
--fail-on-newgate, probability-aware slice metrics (ROC-AUC, log-loss), apre-commithook, a temporal-leakage check, numeric-range slice predicates (tenure < 6), an approximate k-NN backend (pynndescent) for large embedding sets, and interactive plotly charts in the slice/embed HTML reports.
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ml_xray-0.2.0.tar.gz.
File metadata
- Download URL: ml_xray-0.2.0.tar.gz
- Upload date:
- Size: 75.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25831a0a97df0e1bdd914ba7668a89cc4f8463bba46ab13a35ddcd84bd493276
|
|
| MD5 |
5e3d0a94300143a405fd25dc716806e7
|
|
| BLAKE2b-256 |
b6526ce4401094e6876f44f136ff82c81c8be09a233c7206f7f37ad3def2fd1c
|
Provenance
The following attestation bundles were made for ml_xray-0.2.0.tar.gz:
Publisher:
release.yml on OwenDinsmore/ml-xray
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ml_xray-0.2.0.tar.gz -
Subject digest:
25831a0a97df0e1bdd914ba7668a89cc4f8463bba46ab13a35ddcd84bd493276 - Sigstore transparency entry: 2342042679
- Sigstore integration time:
-
Permalink:
OwenDinsmore/ml-xray@27394c44b7d10cda701b491e0afff496f6e2edbd -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/OwenDinsmore
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@27394c44b7d10cda701b491e0afff496f6e2edbd -
Trigger Event:
push
-
Statement type:
File details
Details for the file ml_xray-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ml_xray-0.2.0-py3-none-any.whl
- Upload date:
- Size: 64.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f28bccd4f8b0ec0e18ee94ffa64bb03f3f29de3596e3b87b70b8878cd03ebbe
|
|
| MD5 |
114b88d67db9da29cec627a8552beba3
|
|
| BLAKE2b-256 |
f16588bb86715058c6513030029a6ba5c5216a54b5a47f0191bdb21f1ed1cb4c
|
Provenance
The following attestation bundles were made for ml_xray-0.2.0-py3-none-any.whl:
Publisher:
release.yml on OwenDinsmore/ml-xray
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ml_xray-0.2.0-py3-none-any.whl -
Subject digest:
3f28bccd4f8b0ec0e18ee94ffa64bb03f3f29de3596e3b87b70b8878cd03ebbe - Sigstore transparency entry: 2342042688
- Sigstore integration time:
-
Permalink:
OwenDinsmore/ml-xray@27394c44b7d10cda701b491e0afff496f6e2edbd -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/OwenDinsmore
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@27394c44b7d10cda701b491e0afff496f6e2edbd -
Trigger Event:
push
-
Statement type: