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 (
meanorlastpooling). - 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
- Featurize train texts (hashing or LLM embeddings).
- Cluster the train set only.
- Project eval texts into the same feature space and assign each to its nearest train centroid.
- 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.
- 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:
hashingfeatures 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.1.1
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 | |
|---|---|---|---|---|
| splitscope-0.1.1.tar.gz | 11.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| splitscope-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.4 kB
Release files / splitscope-0.1.1.tar.gz
| Download URL | splitscope-0.1.1.tar.gz |
|---|---|
| Size | 11.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a35351bfc028f7e39206cbc0b8d4435084ad7c8e9441762f1934403c9c9d180c
|
|
BLAKE2b-256 checksum How to use checksums |
13666b8fb168cd79202f9eeb72aeb9000f035a72472e77ae995125fd6f0e8792
|
| 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.1.1-py3-none-any.whl
| Download URL | splitscope-0.1.1-py3-none-any.whl |
|---|---|
| Size | 15.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4ef48916c9465f206e980f56a0146dcf78134640c2462fe18272977aed5aa531
|
|
BLAKE2b-256 checksum How to use checksums |
2295f23aa36398b8026264131d0dec42f8eda77e218540cf69cb7218d4927f7f
|
| 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.1.1
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.