Skip to main content

Embedding-based train/test splitting beyond random splits: adversarial, overlap, and distribution-balanced splits for robust model evaluation.

Project description

splytters

PyPI CI codecov Docs License: MIT Python Code style: Ruff

Train/test splitting algorithms for dataset partitioning — beyond random splits.

pip install splytters

A random split tells you how a model performs on data that looks just like its training set. Often that's not the question you're asking. This library provides splitters with three different objectives:

Objective Package module What it does Use it for
Adversarial splytters.adversarial Minimize train/test similarity Hard evaluation — measure generalization to unfamiliar data
Overlap splytters.overlap Maximize train/test similarity Easy evaluation — sanity checks, debugging, upper-bound estimates
Balanced splytters.balanced Match train/test distributions Fair evaluation — avoid accidental distribution shift

All splitters operate on embeddings (any (n_samples, dim) array — numpy, lists, pandas, or torch tensors) and return integer index arrays, so they work with any data you can embed: text, images, audio, tabular rows.

Installation

pip install splytters

The core install (numpy, scipy, scikit-learn) covers every splitter. Optional extras add modality-specific dependencies for the sorters and built-in embedders:

pip install "splytters[text]"       # text sorters (pysbd, transformers, wordfreq, ...)
pip install "splytters[image]"      # image sorters (pillow)
pip install "splytters[audio]"      # audio sorters (librosa)
pip install "splytters[tabular]"    # tabular sorters (pandas)
pip install "splytters[embedders]"  # built-in embedders (sentence-transformers, ...)
pip install "splytters[all]"        # all of the above

Requires Python 3.10+ (tested on 3.10–3.14).

Quickstart

import numpy as np
from splytters import cluster_split

embeddings = np.random.rand(500, 384)  # your embeddings here

train_idx, test_idx = cluster_split(embeddings, train_size=0.7)

Every splitter follows the same scikit-learn-style interface:

train_indices, test_indices = some_split(
    embeddings,        # (n_samples, embedding_dim) array-like
    train_size=0.7,    # fraction in (0, 1) OR an absolute count
    random_state=42,
)

A more realistic example with text:

from sentence_transformers import SentenceTransformer
from splytters import centroid_adversarial_split

texts = [...]  # your dataset
embeddings = SentenceTransformer("all-MiniLM-L6-v2").encode(texts)

train_idx, test_idx = centroid_adversarial_split(embeddings, train_size=0.7)
train = [texts[i] for i in train_idx]
test = [texts[i] for i in test_idx]

Works with scikit-learn, pandas, PyTorch & HF datasets

Drop a splitter into any scikit-learn workflow — as a cross-validator or a train_test_split replacement:

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_validate
from splytters import SplytterSplit, adversarial_train_test_split, cluster_split

# 1) as a CV object (single hard split) in cross_validate / GridSearchCV
cv = SplytterSplit(cluster_split, embeddings=X, n_clusters=10)
cross_validate(LogisticRegression(), X, y, cv=cv)

# 2) as a train_test_split drop-in (splits every array the same way)
X_tr, X_te, y_tr, y_te = adversarial_train_test_split(X, y, embeddings=X)

Native helpers for the rest of the ecosystem (heavy deps imported lazily):

from splytters import split_dataframe, to_torch_subsets, split_dataset

train_df, test_df   = split_dataframe(df, embeddings)              # pandas
train_ds, test_ds   = to_torch_subsets(torch_dataset, embeddings)  # PyTorch
dsdict              = split_dataset(hf_dataset, embeddings)        # → DatasetDict

How hard is my split? — split_report

from splytters import split_report, compare_splitters, random_split, cluster_split

compare_splitters(embeddings, {"random": random_split, "adversarial": cluster_split})
# {'random': {...}, 'adversarial': {'mmd_rbf': ..., 'energy_distance': ...,
#                                   'wasserstein_mean': ..., 'coverage': ..., ...}}

split_report quantifies how adversarial/overlapping/balanced a split actually is (centroid & nearest-train distance, coverage, cluster leakage, MMD, energy distance, mean 1-D Wasserstein/KS, and optional label-distribution shift).

Available splitters

Adversarial (minimize train/test similarity): cluster_split, centroid_adversarial_split, distance_adversarial_split, density_adversarial_split, outlier_adversarial_split, min_cut_split, normalized_cut_split, wasserstein_adversarial_split, mmd_maximized_split, minority_split, class_boundary_split

Adversarial splitters on 2D distributions

Overlap (maximize train/test similarity): cluster_leak_split, neighbor_coverage_split, centroid_matched_split, stratified_similarity_split, nearest_neighbor_split, duplicate_spread_split, max_coverage_split

Overlap splitters on 2D distributions

Balanced (match train/test distributions): distribution_matched_split, moment_matched_split, histogram_matched_split, stratified_random_split, density_balanced_split, mmd_minimized_split

Balanced splitters on 2D distributions

Supervised (label-aware — these take class labels y): class_boundary_split, minority_split, stratified_random_split, sorted_stratified_split, cluster_split(strategy="subset_sum")

Supervised splitters on labeled 2D distributions

A plain random_split baseline and utilities (compute_pairwise_distances, compute_split_similarity, cluster_embeddings, ...) are also exported from splytters.

The unsupervised figures are generated by demos/visualize_splits.py and the supervised one by demos/visualize_supervised_splits.py — each row is a 2D distribution (unimodal, moons, spirals, rings, ...), each column a splitter. Blue = train, orange = test; in the supervised figure marker shape encodes the class. (The supervised figure uses overlapping-class variants of the blob distributions so the label-aware splitters have a contested boundary to work with.)

Sorters

The companion splytters.sorters package ranks samples by interpretable difficulty/quality metrics — useful for curriculum-style splits ("train on easy, test on hard") or just inspecting your data:

  • embedding_sortersdistance_to_mean, distance_to_nearest_neighbor, local_density, outlier_score
  • text_sorters — length, readability, perplexity, lexical diversity, vocabulary rarity, sentence count
  • image_sorters — brightness, contrast, color variance, compression ratio, frequency content
  • audio_sorters — loudness, spectral features, MFCCs, rhythm, quality metrics
  • tabular_sorters — column- and row-level metrics, categorical handling, multi-column sorting
from splytters.sorters import distance_to_mean

ranked = distance_to_mean(embeddings)  # most typical → most atypical

Pair a sorter with sorted_stratified_split to turn that ranking into an actual curriculum split — within each class, the first train_size fraction (by the sort order) becomes train, the rest test:

from splytters.sorters import readability_score
from splytters import sorted_stratified_split

ranking = readability_score(texts)                 # easy → hard
train_idx, test_idx = sorted_stratified_split(ranking, y, train_size=0.7)
# largest_first=True flips it to "train on hard, test on easy"

See demos/demo.py for sorters on a real dataset (TREC questions), demos/im_demo.py for an image example with CLIP embeddings, and demos/trec_sorter_experiment.py for a TREC curriculum-split benchmark (text sorters + linear SVM) that quantifies which sorters capture a real difficulty axis.

Installation

git clone https://github.com/gxlarson/splytters
cd splytters
pip install -e .             # core splitters (numpy, scipy, scikit-learn)

Optional extras, depending on which sorters/demos you use:

pip install -e ".[text]"     # text sorters (torch, transformers, py-readability-metrics, wordfreq, pysbd)
pip install -e ".[audio]"    # audio sorters (librosa)
pip install -e ".[image]"    # image sorters (Pillow)
pip install -e ".[tabular]"  # tabular sorters (pandas)
pip install -e ".[ann]"      # approximate-NN backend for large datasets (pynndescent)
pip install -e ".[demo]"     # demos (sentence-transformers, datasets, matplotlib, umap-learn)
pip install -e ".[all]"      # everything

Note: sorter imports are lazy per modalityimport splytters.sorters pulls in no optional dependencies, and each extra is self-sufficient (e.g. [image] alone powers the image sorters). The core splytters install (numpy, scipy, scikit-learn) is all the splitters need.

Tests

pip install -e ".[dev]"
pytest

Project layout

splytters/                # the installable package
  adversarial.py / overlap.py / balanced.py / utils.py   # splitting algorithms
  sklearn_api.py          # SplytterSplit + *_train_test_split
  interop.py              # pandas / torch / HuggingFace adapters
  report.py               # split_report, compare_splitters
  embedders.py            # text / image embedders (TextEmbedder, CLIP, OpenAI)
  sorters/                # ranking metrics (text, image, audio, embedding, tabular)
tests/                    # pytest suite
test_data/                # sample audio/images/text used by tests
demos/                    # demo.py, im_demo.py (sorter demos: TREC text, CLIP images)
                          # visualize_splits.py + visualize_supervised_splits.py (README figures), visualize_text_splits.py
scripts/                  # generate_test_*.py (regenerate the test_data/ fixtures)
docs/                     # README figures

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

splytters-0.1.1.tar.gz (103.7 kB view details)

Uploaded Source

Built Distribution

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

splytters-0.1.1-py3-none-any.whl (74.4 kB view details)

Uploaded Python 3

File details

Details for the file splytters-0.1.1.tar.gz.

File metadata

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

File hashes

Hashes for splytters-0.1.1.tar.gz
Algorithm Hash digest
SHA256 537bf924e16ed035577d81719caf9c84b0b478f2ec6841d2ed307e36639569fc
MD5 846078b7aca49c51fafbc3db1fd5f65e
BLAKE2b-256 7381f14f7e16e60f957742c0b1f5cb93a358543dbaa662f7a45d4e216b826eac

See more details on using hashes here.

Provenance

The following attestation bundles were made for splytters-0.1.1.tar.gz:

Publisher: publish.yml on gxlarson/splytters

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

File details

Details for the file splytters-0.1.1-py3-none-any.whl.

File metadata

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

File hashes

Hashes for splytters-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c6613e830d4dee3fcfdaf59a5da1e8a08474227a7bf313c788e37c2098ce5964
MD5 671bf1f286534a0fee626d94f1cb7ef2
BLAKE2b-256 30aaba4b915474aa78296b74c5bd83a7e04bf48c17401e54b4b1e5f0b375412f

See more details on using hashes here.

Provenance

The following attestation bundles were made for splytters-0.1.1-py3-none-any.whl:

Publisher: publish.yml on gxlarson/splytters

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