Skip to main content

splitscope

Does your training set actually cover your eval set?

splitscope clusters a train dataset, projects an eval dataset onto those clusters, and tells you how well the eval examples fit the train distribution. Use it to spot distribution gaps, decide how aggressively you can filter or subsample training data, and understand which parts of your data matter for evaluation.

  • Fast CPU features: hashed word + char n-grams, TF-IDF, optional LSA. No GPU needed.
  • LLM embeddings: pooled hidden states from any Hugging Face model (mean or last pooling).
  • Multiple clustering backends: KMeans, MiniBatchKMeans, FAISS (CPU or GPU).
  • Library and CLI.

Installation

pip install splitscope                # CPU features + sklearn KMeans / MiniBatchKMeans
pip install "splitscope[faiss]"       # + FAISS (CPU)
pip install "splitscope[faiss-gpu]"   # + FAISS (CUDA)
pip install "splitscope[llm]"         # + LLM embeddings (torch, transformers)
pip install "splitscope[all]"         # everything (CPU FAISS)

Optional dependencies are never installed automatically at import time. If a backend is missing, you get a clear error with the install command.

Quick start

Python

import splitscope as ss

train = ss.load_texts("train.jsonl")
ev = ss.load_texts("test.jsonl")

result = ss.analyze(
    train, ev,
    features="hashing",       # "hashing" | "llm"
    clusterer="minibatch",    # "kmeans" | "minibatch" | "faiss"
    n_clusters=150,
)
print(result.summary())

Command line

# CPU only
splitscope --train train.jsonl --eval test.jsonl --clusterer minibatch

# LLM embeddings + FAISS
splitscope --train train.jsonl --eval test.jsonl \
           --features llm --model Qwen/Qwen3-4B \
           --llm-max-length auto --clusterer faiss

Input format

.jsonl (one JSON object per line) or .json (a list of objects). By default, each record's text is built from these fields:

Field Role
instruct prompt part 1
input prompt part 2
output completion

Text = instruct + input + output. If neither prompt field exists, all values in the record are concatenated. Change the field names with TextConfig or the --instruction-col, --input-col and --output-col flags:

cfg = ss.TextConfig(instruction_col="question", input_col=None, output_col="answer")
train = ss.load_texts("train.jsonl", cfg)

How it works

  1. Featurize train texts (hashing or LLM embeddings).
  2. Cluster the train set only.
  3. Project eval texts into the same feature space and assign each to its nearest train centroid.
  4. Compare each eval point's distance to its centroid against that cluster's train distance distribution:
    • 1σ band: within mean ± std of train distances.
    • Robust band: within a MAD-based robust z-score of 3.5.
  5. Recommend a data-reduction strategy based on how much of eval falls inside the train core.

Output

analyze() returns an AnalysisResult:

Attribute Meaning
train_labels cluster id for each train example
eval_labels nearest train cluster for each eval example
eval_min_dists distance from each eval example to its centroid
stats per-cluster ClusterStats (size, mean/std/median/MAD, representative index)
frac_one_sigma fraction of eval inside the train 1σ band
frac_robust fraction of eval inside the train robust band
summary() human-readable report
recommendation() suggested filtering strategy

Advanced usage

Fit once, analyze several eval sets:

from splitscope import ClusterAnalyzer

analyzer = ClusterAnalyzer(
    features="llm",
    clusterer="faiss",
    n_clusters=200,
    features_kwargs=dict(model_name="Qwen/Qwen3-4B", pooling="last",
                         max_length="auto", batch_size=8),
)
analyzer.fit(train)
res_a = analyzer.analyze(eval_a)
res_b = analyzer.analyze(eval_b)

Inspect a cluster:

cid = res_a.eval_cluster_counts.most_common(1)[0][0]
s = res_a.stats[cid]
print(s.n, s.mean, s.std)
print(train[s.representative_idx])

Choosing a backend

Goal Suggestion
Small or medium data, best quality clusterer="kmeans"
Large data on CPU clusterer="minibatch" + features="hashing"
Very large data or many clusters clusterer="faiss" (uses GPU automatically if available)
Semantic similarity rather than lexical features="llm"

Notes:

  • hashing features default to LSA (96 dims). Without it, centroids are dense over a very wide space, which is slow and memory-hungry.
  • With features="llm", max_length="auto" uses the P95 token length of the train texts. Longer examples are truncated.
  • LLM features are pooled hidden states from a base model, not retrieval-trained embeddings. A dedicated embedding model usually gives sharper clusters.

CLI reference

Flag Default Description
--train, --eval required dataset paths (.json / .jsonl)
--n-clusters 150 number of clusters
--clusterer kmeans kmeans, minibatch, faiss
--features hashing hashing, llm
--no-lsa off disable LSA for hashing features
--model none Hugging Face model (required for llm)
--pooling last mean or last
--llm-batch-size 8 embedding batch size
--llm-max-length 2048 token limit, or auto for P95
--max-train, --max-eval none cap number of samples
--instruction-col, --input-col, --output-col instruct, input, output field names

Development

git clone https://github.com/antony-ro/splitscope
cd splitscope
pip install -e ".[dev]"
pytest

License

MIT

Metadata

Release files for splitscope 0.2.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 splitscope 0.2.0
File Size Uploaded Provenance
splitscope-0.2.0.tar.gz 16.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for splitscope 0.2.0
File Interpreter ABI Platform
splitscope-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 38.1 kB

Release files / splitscope-0.2.0.tar.gz

Download URL splitscope-0.2.0.tar.gz
Size 16.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a94ff63f9c244961b6ed7f880faae41e8b1e940f02fdb29f38b8fcc28fde7d0e
BLAKE2b-256 checksum
How to use checksums
43d72d95b6f7c2ca373941549dea1fee2e594f4068d128679cf9fd50b3517737
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / splitscope-0.2.0-py3-none-any.whl

Download URL splitscope-0.2.0-py3-none-any.whl
Size 21.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
979c45e5d1084e90edf30396e3c5e24cdb2dc6e21e4ca04c25fd6eca3c6b6015
BLAKE2b-256 checksum
How to use checksums
6f40630463b8116600636cd94dae35da73e0f9a4277614116b7a90945353b237
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Security for splitscope 0.2.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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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