API reference¶
Auto-generated from the source docstrings. Everything below is also importable
directly from the top-level splytters namespace (e.g.
from splytters import cluster_split).
Adversarial splitters¶
adversarial ¶
Adversarial splitting algorithms that minimize train-test similarity.
These methods create "hard" evaluation sets where test samples are dissimilar from training samples, testing model generalization.
cluster_split ¶
cluster_split(embeddings: ArrayLike, train_size: float | int = 0.7, method: str = 'kmeans', n_clusters: int = 10, random_state: int = 42, *, strategy: str = 'size', y: ArrayLike | None = None, **cluster_kwargs: Any) -> tuple[np.ndarray, np.ndarray]
Split dataset by assigning entire clusters to train or test.
Prevents 'cluster leakage' where similar samples end up on both sides. The
embeddings are clustered (method), then whole clusters are assigned to
train/test according to strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
method
|
str
|
clustering algorithm, 'kmeans' or 'dbscan' |
'kmeans'
|
n_clusters
|
int
|
number of clusters (kmeans only) |
10
|
random_state
|
int
|
for reproducibility |
42
|
strategy
|
str
|
cluster -> train/test assignment policy:
- |
'size'
|
y
|
ArrayLike | None
|
class labels of shape (n_samples,). Required for |
None
|
**cluster_kwargs
|
Any
|
passed to the clustering algorithm |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Raises:
| Type | Description |
|---|---|
ValueError
|
on an unknown |
References
The "subset_sum" and "closest" strategies implement SUBSET-SUM-SPLIT
and CLOSEST-SPLIT from Züfle, Dankers & Titov (2023), "Latent Feature-based
Data Splits to Improve Generalisation Evaluation," GenBench Workshop @ EMNLP,
pp. 112-129. https://aclanthology.org/2023.genbench-1.9 . They cluster a
model's hidden representations and assign whole clusters to test to expose
latent-space "blind spots"; their analysis finds that the resulting
difficulty does not correlate with surface-level properties (e.g. length),
complementing the surface-based :mod:splytters.sorters.
The label-balanced idea behind "subset_sum" traces to the earlier
ClusterDataSplit of Wecker, Friedrich & Adel (2020), "ClusterDataSplit:
Exploring Challenging Clustering-Based Data Splits for Model Performance
Evaluation," Eval4NLP @ COLING, which clusters data into splits that
differ lexically from train while keeping the label distribution fixed.
https://aclanthology.org/2020.eval4nlp-1.15 . See :func:cluster_kfold
for their cross-validation variant.
Napoli & White (2025), "Clustering-Based Validation Splits for Model
Selection under Domain Shift," TMLR, ground this family theoretically:
maximizing the MMD between the two sets is equivalent to kernel k-means
clustering (k=2), and they replace ClusterDataSplit's greedy
class-balancing with a linear program (with convergence guarantees).
https://openreview.net/forum?id=Q692C0WtiD . See
:func:mmd_maximized_split for the MMD-maximizing objective.
Source code in splytters/adversarial.py
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
centroid_adversarial_split ¶
centroid_adversarial_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_clusters: int = 10, random_state: int = 42, **cluster_kwargs: Any) -> tuple[np.ndarray, np.ndarray]
Adversarial cluster split: assign clusters nearest to global centroid to train, furthest clusters to test.
Combines centroid-distance ranking with cluster-based splitting to maximize train-test dissimilarity while keeping similar samples together.
Thin wrapper over cluster_split(strategy="centroid").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_clusters
|
int
|
number of clusters |
10
|
random_state
|
int
|
for reproducibility |
42
|
**cluster_kwargs
|
Any
|
passed to KMeans |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/adversarial.py
cluster_kfold ¶
cluster_kfold(embeddings: ArrayLike, y: ArrayLike, n_folds: int = 5, method: str = 'kmeans', n_clusters: int | None = None, random_state: int = 42, **cluster_kwargs: Any) -> np.ndarray
Partition data into challenging, label-balanced cross-validation folds.
Clusters the embeddings, then assigns whole clusters to n_folds folds so
that each fold is lexically coherent -- similar samples share a fold, making
every fold differ from the data trained on for it -- while its label
distribution stays close to the global one. This yields a "challenging" CV:
each held-out fold is a cluster-based blind spot rather than a random sample.
The result is a per-sample fold id, usable directly with
:class:sklearn.model_selection.PredefinedSplit::
from sklearn.model_selection import PredefinedSplit, cross_validate
folds = cluster_kfold(embeddings, y, n_folds=5)
cross_validate(model, X, y, cv=PredefinedSplit(folds))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
y
|
ArrayLike
|
class labels of shape (n_samples,); used to balance folds by label |
required |
n_folds
|
int
|
number of cross-validation folds (default 5) |
5
|
method
|
str
|
clustering algorithm, 'kmeans' or 'dbscan' |
'kmeans'
|
n_clusters
|
int | None
|
number of clusters (kmeans only). Defaults to
|
None
|
random_state
|
int
|
for reproducibility |
42
|
**cluster_kwargs
|
Any
|
passed to the clustering algorithm |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
fold_ids |
ndarray
|
integer ndarray of shape (n_samples,), values in |
ndarray
|
|
|
ndarray
|
and the training set is the rest. |
Raises:
| Type | Description |
|---|---|
ValueError
|
on an unknown |
References
Implements the challenging clustering-based cross-validation of Wecker, Friedrich & Adel (2020), "ClusterDataSplit: Exploring Challenging Clustering-Based Data Splits for Model Performance Evaluation," Eval4NLP @ COLING -- folds that are lexically distinct from train while preserving label balance. https://aclanthology.org/2020.eval4nlp-1.15
Source code in splytters/adversarial.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | |
minority_split ¶
minority_split(embeddings: ArrayLike, y: ArrayLike, n_clusters: int = 10, method: str = 'kmeans', random_state: int = 42, **cluster_kwargs: Any) -> tuple[np.ndarray, np.ndarray]
Bias-amplified split via per-cluster minority labels.
Clusters the embeddings, then for each cluster routes instances whose label is the cluster's majority label to train (the "biased" majority a shortcut-learning model would exploit) and instances with any minority label to test (the "anti-biased" examples that defy the local pattern), yielding a hard, bias-amplified evaluation set.
Unlike the size-driven splitters, the train/test sizes are determined by the
data's bias structure (test = the minority-label instances), so this function
takes no train_size. It inspects per-cluster label counts only; it never
references global class frequencies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
y
|
ArrayLike
|
class labels of shape (n_samples,) |
required |
n_clusters
|
int
|
number of clusters (kmeans only) |
10
|
method
|
str
|
clustering algorithm, 'kmeans' or 'dbscan' |
'kmeans'
|
random_state
|
int
|
for reproducibility |
42
|
**cluster_kwargs
|
Any
|
passed to the clustering algorithm |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
majority-label ("biased") instances, pooled over clusters |
test_indices |
ndarray
|
minority-label ("anti-biased") instances, pooled over clusters |
Raises:
| Type | Description |
|---|---|
ValueError
|
on an unknown |
Warns:
| Type | Description |
|---|---|
UserWarning
|
if the test set is degenerately small (under 5% of the
samples). The clusters are then nearly label-pure, so the split has
too few minority examples to be informative -- prefer a different
|
References
Implements the "minority examples" notion of bias from Reif & Schwartz (2023), "Fighting Bias with Bias: Promoting Model Robustness by Amplifying Dataset Biases," Findings of ACL, pp. 13169-13189 (https://aclanthology.org/2023.findings-acl.833): cluster the data, treat each cluster's majority label as biased (train) and its minority labels as anti-biased (test). Their other two notions -- dataset cartography (training dynamics) and partial-input models -- are model-in-the-loop / task-specific and out of scope for a static splitter.
Source code in splytters/adversarial.py
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 | |
minority_grow_split ¶
minority_grow_split(embeddings: ArrayLike, y: ArrayLike, train_size: float | int = 0.7, n_clusters: int = 10, method: str = 'kmeans', metric: str = 'euclidean', random_state: int = 42, stratify: str = 'none', **cluster_kwargs: Any) -> tuple[np.ndarray, np.ndarray]
Region-grown variant of :func:minority_split with a target test size.
Seeds the test set with the per-cluster minority-label ("anti-biased")
instances found by :func:minority_split, then greedily grows it: at each
step the not-yet-test sample closest to the current test set (single-link
distance to its nearest test member) is moved to test, until the test set
reaches the train_size-implied target. This keeps the hard, bias-defying
flavor of the minority seed while letting you dial the test fraction --
unlike :func:minority_split, whose size is dictated by the data and is
often degenerately small.
Class balance is controlled by stratify (mirrors cluster_split's
strategy string convention):
"none"(default): growth is purely geometric and ignoresy, so the test set is generally not class-balanced -- it expands into one region and inherits that region's dominant class (same un-balanced behavior as :func:minority_split)."global": one shared proximity frontier, but each class is capped at its data-proportional share of the test set; a class drops out of the frontier once full. The test set stays one contiguous region whose label mix tracks the data."per_class": each class grows its own nearest-to-seed neighborhood up to its quota, independently. The test set is the union of these per-class regions (so not a single contiguous blob), but every class contributes its geometrically-tightest hard samples. A class with no seed member is anchored at its sample nearest the overall seed.
For both stratified modes the quotas are data-proportional and the minority seed is always kept, so a class whose seed already over-fills its quota may still exceed its share.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
y
|
ArrayLike
|
class labels of shape (n_samples,) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set;
sets the target test size ( |
0.7
|
n_clusters
|
int
|
number of clusters for the minority seed (kmeans only) |
10
|
method
|
str
|
clustering algorithm for the seed, 'kmeans' or 'dbscan' |
'kmeans'
|
metric
|
str
|
distance metric ( |
'euclidean'
|
random_state
|
int
|
for reproducibility of the clustering seed |
42
|
stratify
|
str
|
class-balance policy for growth, one of |
'none'
|
**cluster_kwargs
|
Any
|
passed to the seed clustering algorithm |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for the training set |
test_indices |
ndarray
|
ndarray of indices for the test set (the minority seed plus its grown neighborhood). If the seed already meets or exceeds the target size, it is returned as-is (test may exceed the target). Under a stratified mode the test set may fall short of the target if every class hits its quota first. |
Raises:
| Type | Description |
|---|---|
ValueError
|
on an unknown |
Warns:
| Type | Description |
|---|---|
UserWarning
|
if the resulting test set contains no samples for one or more
classes (those classes then can't be evaluated). Prefer a stratified
mode or a larger |
References
Extends the bias-amplified minority seed of Reif & Schwartz (2023); see
:func:minority_split. The proximity growth is the single-linkage region
growth also used by cluster_split(strategy="closest").
Source code in splytters/adversarial.py
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 | |
class_boundary_split ¶
class_boundary_split(embeddings: ArrayLike, y: ArrayLike, train_size: float | int = 0.7, metric: str = 'euclidean', reference: str = 'centroids', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Class-stratified adversarial split on per-class decision boundaries.
Within each class, ranks samples by how close they sit to other classes and routes the closest (most confusable, near-boundary) ones to the test set until that class's test quota is filled. The remaining, more class-typical samples go to train. Because the quota is applied per class, the test set stays label-balanced (stratified) while still being hard: it concentrates the examples a model is most likely to confuse across categories.
"Closeness to other classes" is measured by reference:
"centroids"(default): distance to the nearest other class centroid. Cheap -- O(n*K) for K classes."samples": nearest-enemy distance, i.e. distance to the single closest sample belonging to any other class. Sharper boundaries, but O(n^2).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
y
|
ArrayLike
|
class labels of shape (n_samples,) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count, applied per class to size each class's train portion. A fraction is recommended; an absolute count is clamped to each class's size. |
0.7
|
metric
|
str
|
distance metric passed to |
'euclidean'
|
reference
|
str
|
|
'centroids'
|
random_state
|
int
|
accepted for API consistency; this split is deterministic |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of class-typical (interior) sample indices |
test_indices |
ndarray
|
ndarray of near-boundary, cross-class-confusable indices |
Raises:
| Type | Description |
|---|---|
ValueError
|
on a |
References
A label-stratified, per-class variant of the adversarial-distance idea of
Søgaard, Ebert, Bastings & Filippova (2021), "We Need to Talk About
Random Splits," EACL (https://aclanthology.org/2021.eacl-main.156): hard
splits push dissimilar samples into test. Here "dissimilar" is measured
toward other classes rather than toward the training set, yielding a
boundary-focused test set; contrast :func:distance_adversarial_split
(unsupervised, distance from the global centroid).
Source code in splytters/adversarial.py
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 | |
decision_boundary_split ¶
decision_boundary_split(embeddings: ArrayLike, y: ArrayLike, train_size: float | int = 0.7, *, model: str = 'linear_svc', score: str = 'confidence', stratify: str = 'per_class', cv: int = 5, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Supervised adversarial split on a learned decision boundary.
Fits a fast linear classifier under cross-validation and routes the samples
a real model finds hardest -- those closest to the learned decision boundary
-- to the test set, leaving the confident, class-interior samples for train.
The learned-boundary counterpart to :func:class_boundary_split (which
instead measures geometric distance to other classes).
Hardness is scored out of fold via
:class:~sklearn.model_selection.StratifiedKFold: each sample's margin comes
from a model that did not train on it, so the score reflects genuine
difficulty rather than in-sample memorization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim). |
required |
y
|
ArrayLike
|
class labels of shape (n_samples,). |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count. With
|
0.7
|
model
|
str
|
surrogate classifier -- |
'linear_svc'
|
score
|
str
|
hardness measure. |
'confidence'
|
stratify
|
str
|
|
'per_class'
|
cv
|
int
|
number of cross-validation folds, clamped to the smallest class count. Each fold supplies the out-of-fold margins for its held-out samples. |
5
|
random_state
|
int
|
seeds the fold shuffling and the classifier; the split is deterministic (ties broken by index). |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of confident, class-interior sample indices. |
test_indices |
ndarray
|
ndarray of near-boundary, model-confusable indices. |
Raises:
| Type | Description |
|---|---|
ValueError
|
on a |
References
Uncertainty / margin sampling from active learning (Lewis & Gale, 1994;
Scheffer et al., 2001; Settles, 2009). Like :func:class_boundary_split,
a label-aware variant of the hard-split motivation of Soegaard et al.
(2021), "We Need to Talk About Random Splits"
(https://aclanthology.org/2021.eacl-main.156), but using a learned
boundary rather than embedding geometry.
Source code in splytters/adversarial.py
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 | |
distance_adversarial_split ¶
distance_adversarial_split(embeddings: ArrayLike, train_size: float | int = 0.7, metric: str = 'euclidean', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Adversarial split based on individual sample distance from centroid.
Samples closest to centroid go to train, furthest go to test. Unlike cluster-based methods, this operates on individual samples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
metric
|
str
|
distance metric |
'euclidean'
|
random_state
|
int
|
accepted for API consistency; this split is deterministic |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/adversarial.py
density_adversarial_split ¶
density_adversarial_split(embeddings: ArrayLike, train_size: float | int = 0.7, metric: str = 'euclidean', k: int = 10, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Adversarial split based on local density.
Samples in dense regions go to train, isolated samples go to test.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
metric
|
str
|
distance metric |
'euclidean'
|
k
|
int
|
number of neighbors for density estimation |
10
|
random_state
|
int
|
accepted for API consistency; this split is deterministic |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/adversarial.py
outlier_adversarial_split ¶
outlier_adversarial_split(embeddings: ArrayLike, train_size: float | int = 0.7, contamination: float = 0.1, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Adversarial split using outlier detection.
Normal samples go to train, outliers go to test.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
contamination
|
float
|
expected proportion of outliers |
0.1
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/adversarial.py
min_cut_split ¶
min_cut_split(embeddings: ArrayLike, train_size: float | int = 0.7, similarity_threshold: float | None = None, metric: str = 'euclidean', method: str = 'spectral', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Adversarial split using graph min-cut.
Builds a similarity graph and finds a partition that minimizes edges (similarity) crossing the train/test boundary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
similarity_threshold
|
float | None
|
only connect samples above this kernel similarity (default: median similarity) |
None
|
metric
|
str
|
distance metric |
'euclidean'
|
method
|
str
|
'spectral' (fast, approximate) or 'stoer_wagner' (exact, slow) |
'spectral'
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/adversarial.py
1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 | |
normalized_cut_split ¶
normalized_cut_split(embeddings: ArrayLike, train_size: float | int = 0.7, metric: str = 'euclidean', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Adversarial split using normalized graph cut.
Normalized cut balances the cut value with partition sizes, avoiding trivially small partitions.
NCut(A,B) = cut(A,B)/vol(A) + cut(A,B)/vol(B)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
metric
|
str
|
distance metric |
'euclidean'
|
random_state
|
int
|
accepted for API consistency; this split is deterministic |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/adversarial.py
wasserstein_adversarial_split ¶
wasserstein_adversarial_split(embeddings: ArrayLike, train_size: float | int = 0.7, leaf_size: int = 40, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Adversarial split via Wasserstein nearest-neighbors.
Treats each embedding row as a 1-D distribution and selects, as the test
set, the n_test points nearest (in 1-D Wasserstein / earth-mover
distance) to a random anchor — yielding a tight, hard-to-generalize-to test
neighborhood.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
leaf_size
|
int
|
BallTree leaf size (higher = slower but less memory) |
40
|
random_state
|
int
|
for reproducibility (anchor sampling) |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
References
Adapted from the adversarial split of Søgaard, Ebert, Bastings & Filippova (2021), "We Need to Talk About Random Splits," EACL, which constructs hard splits by (approximately) maximizing the Wasserstein distance between train and test. https://aclanthology.org/2021.eacl-main.156
Source code in splytters/adversarial.py
mmd_maximized_split ¶
mmd_maximized_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_iterations: int = 500, kernel: str = 'rbf', gamma: float | None = None, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Maximize Maximum Mean Discrepancy (MMD) between train and test.
The adversarial dual of :func:splytters.mmd_minimized_split: instead of
matching the two distributions, it pushes them as far apart as possible in
kernel/MMD terms, yielding a hard, domain-shifted held-out set. Motivated by
model selection under domain shift -- a maximally-shifted validation set
tends to select more robust hyperparameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_iterations
|
int
|
number of optimization iterations |
500
|
kernel
|
str
|
kernel type ('rbf' or 'linear') |
'rbf'
|
gamma
|
float | None
|
RBF kernel parameter (default: 1/n_features) |
None
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
References
Implements the objective of Napoli & White (2025), "Clustering-Based
Validation Splits for Model Selection under Domain Shift," TMLR
(https://openreview.net/forum?id=Q692C0WtiD): the train/validation split
should maximize the MMD between the two sets. NOTE: this captures that
objective via swap optimization (like mmd_minimized_split); it does
not implement their full method -- a constrained kernel k-means (max-MMD
is shown equivalent to kernel k-means with k=2) solved by linear
programming to preserve class/group balance, with convergence guarantees
and Nyström scaling. For class-balanced clustering splits, see
cluster_split(strategy="subset_sum") and :func:cluster_kfold.
Source code in splytters/adversarial.py
1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 | |
get_cluster_info ¶
get_cluster_info(embeddings: ArrayLike, train_indices: ArrayLike, test_indices: ArrayLike, n_clusters: int = 10, random_state: int = 42) -> dict[str, Any]
Utility to analyze cluster distribution across train/test split.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict with cluster statistics including leakage info |
Source code in splytters/adversarial.py
Overlap splitters¶
overlap ¶
High-overlap splitting algorithms that maximize train-test similarity.
These methods create "easy" evaluation sets where test samples are similar to training samples, useful for sanity checks and debugging.
cluster_leak_split ¶
cluster_leak_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_clusters: int = 10, random_state: int = 42, **cluster_kwargs: Any) -> tuple[np.ndarray, np.ndarray]
Split clusters across train/test to maximize similarity.
Instead of assigning entire clusters to one set (adversarial), this splits each cluster proportionally between train and test, ensuring similar samples appear in both sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_clusters
|
int
|
number of clusters |
10
|
random_state
|
int
|
for reproducibility |
42
|
**cluster_kwargs
|
Any
|
passed to KMeans |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/overlap.py
neighbor_coverage_split ¶
neighbor_coverage_split(embeddings: ArrayLike, train_size: float | int = 0.7, k: int = 5, metric: str = 'euclidean', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Ensure each test sample has k similar samples in train.
Iteratively assigns samples to test only if they have enough similar samples already in train, maximizing coverage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
k
|
int
|
minimum number of similar train samples for each test sample |
5
|
metric
|
str
|
distance metric |
'euclidean'
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/overlap.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
centroid_matched_split ¶
centroid_matched_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_iterations: int = 100, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Minimize distance between train and test centroids.
Uses iterative optimization to find a split where the centroids of train and test sets are as close as possible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_iterations
|
int
|
number of swap iterations |
100
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/overlap.py
stratified_similarity_split ¶
stratified_similarity_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_bins: int = 10, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Stratify by distance from centroid, ensuring similar distribution in both sets.
Bins samples by their distance from the centroid and samples proportionally from each bin for both train and test.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_bins
|
int
|
number of distance bins for stratification |
10
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/overlap.py
nearest_neighbor_split ¶
nearest_neighbor_split(embeddings: ArrayLike, train_size: float | int = 0.7, metric: str = 'euclidean', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
For each test sample, ensure its nearest neighbor is in train.
Greedily builds test set by moving points whose nearest neighbor is already confirmed in train. Uses sklearn's NearestNeighbors which auto-selects the best algorithm (kd-tree, ball tree, or brute force) based on dimensionality.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
metric
|
str
|
distance metric |
'euclidean'
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/overlap.py
duplicate_spread_split ¶
duplicate_spread_split(embeddings: ArrayLike, train_size: float | int = 0.7, similarity_threshold: float | None = None, metric: str = 'euclidean', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Intentionally put near-duplicates in both train and test.
Identifies clusters of near-duplicates and ensures at least one sample from each cluster appears in both sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
similarity_threshold
|
float | None
|
distance threshold for near-duplicates (default: 10th percentile of distances) |
None
|
metric
|
str
|
distance metric |
'euclidean'
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/overlap.py
max_coverage_split ¶
max_coverage_split(embeddings: ArrayLike, train_size: float | int = 0.7, radius: float | None = None, metric: str = 'euclidean', random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Maximize the coverage of test set by train set.
Coverage = fraction of test samples with at least one train sample within radius.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
radius
|
float | None
|
distance threshold for coverage (default: median distance) |
None
|
metric
|
str
|
distance metric |
'euclidean'
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/overlap.py
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 | |
Balanced splitters¶
balanced ¶
Balanced splitting algorithms that match distributions between train/test.
These methods create splits where train and test have similar statistical properties, useful for fair evaluation without distribution shift.
distribution_matched_split ¶
distribution_matched_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_iterations: int = 1000, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Minimize distribution divergence between train and test.
Uses iterative optimization to match the marginal distributions of each feature dimension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_iterations
|
int
|
number of optimization iterations |
1000
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/balanced.py
moment_matched_split ¶
moment_matched_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_iterations: int = 1000, match_variance: bool = True, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Match mean (and optionally variance) between train and test.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_iterations
|
int
|
number of optimization iterations |
1000
|
match_variance
|
bool
|
if True, also match variance |
True
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/balanced.py
histogram_matched_split ¶
histogram_matched_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_bins: int = 10, n_iterations: int = 1000, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Match feature histograms between train and test.
Minimizes the sum of histogram differences across all dimensions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_bins
|
int
|
number of histogram bins per dimension |
10
|
n_iterations
|
int
|
number of optimization iterations |
1000
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/balanced.py
stratified_random_split ¶
stratified_random_split(embeddings: ArrayLike, y: ArrayLike, train_size: float | int = 0.7, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Standard stratified split maintaining label proportions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
y
|
ArrayLike
|
array of labels for stratification (aligned with sklearn's |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/balanced.py
density_balanced_split ¶
density_balanced_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_bins: int = 10, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Balance local density distribution between train and test.
Bins samples by local density and samples proportionally from each bin for both sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_bins
|
int
|
number of density bins |
10
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/balanced.py
mmd_minimized_split ¶
mmd_minimized_split(embeddings: ArrayLike, train_size: float | int = 0.7, n_iterations: int = 500, kernel: str = 'rbf', gamma: float | None = None, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Minimize Maximum Mean Discrepancy between train and test.
MMD is a kernel-based measure of distribution difference. Lower MMD indicates more similar distributions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
n_iterations
|
int
|
number of optimization iterations |
500
|
kernel
|
str
|
kernel type ('rbf' or 'linear') |
'rbf'
|
gamma
|
float | None
|
RBF kernel parameter (default: 1/n_features) |
None
|
random_state
|
int
|
for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
References
The adversarial dual -- maximizing train/validation MMD for robust
model selection under domain shift -- is studied by Napoli & White
(2025), "Clustering-Based Validation Splits for Model Selection under
Domain Shift," TMLR (https://openreview.net/forum?id=Q692C0WtiD). See
:func:splytters.mmd_maximized_split.
Source code in splytters/balanced.py
Curriculum splits¶
curriculum ¶
Curriculum / ordering-driven splitting.
Unlike the embedding-based splitters (adversarial / overlap / balanced), these
take an explicit sort order — typically a :mod:splytters.sorters ranking — and
partition each class along it. The classic use is a "train on easy, test on
hard" curriculum split: sort by a difficulty metric, then take the first
train_size fraction of each class as train.
sorted_stratified_split ¶
sorted_stratified_split(order: Sequence[Any], y: ArrayLike, train_size: float | int = 0.7, *, largest_first: bool = False) -> tuple[np.ndarray, np.ndarray]
Per-class curriculum split driven by a sort order.
Within each class, samples are visited in the given order and the first
train_size fraction (or absolute count) go to train, the rest to test.
Pair it with a :mod:splytters.sorters ranking to split along an
interpretable difficulty axis — e.g. "train on easy, test on hard".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
Sequence[Any]
|
the sort order over samples — either a flat sequence of sample
indices, or a sorter result (sequence of |
required |
y
|
ArrayLike
|
class labels aligned to the original samples (length n_samples). The split is performed independently within each class. |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count, applied per class. |
0.7
|
largest_first
|
bool
|
if True, reverse |
False
|
Returns:
| Type | Description |
|---|---|
tuple[ndarray, ndarray]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
Source code in splytters/curriculum.py
scikit-learn compatibility¶
sklearn_api ¶
scikit-learn compatibility layer for splytters.
Provides:
- :class:
SplytterSplit— a single-split cross-validator wrapping any splytter splitting function, usable ascv=in :func:sklearn.model_selection.cross_validateand :class:~sklearn.model_selection.GridSearchCV. *_train_test_splitconvenience functions mirroring :func:sklearn.model_selection.train_test_splitfor drop-in swaps.
The functional splitters in :mod:splitters remain the source of truth; these
wrappers are purely additive.
SplytterSplit ¶
Bases: BaseCrossValidator
A scikit-learn cross-validator backed by a splytter splitting function.
Produces n_splits train/test partitions (one by default, like
:class:~sklearn.model_selection.PredefinedSplit). Each partition is the
output of splitter(embeddings, train_size=..., random_state=...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
splitter
|
Splitter
|
any splytter splitter taking
|
cluster_split
|
embeddings
|
Any
|
array-like of shape (n_samples, n_features), optional.
Embeddings to split on. If |
None
|
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set (default 0.7). |
0.7
|
random_state
|
int | None
|
base seed (default 42); the i-th repeat uses
|
42
|
n_splits
|
int
|
number of (repeated) partitions to yield (default 1). |
1
|
**splitter_kwargs
|
Any
|
extra keyword arguments forwarded to |
{}
|
Examples:
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.model_selection import cross_validate
>>> cv = SplytterSplit(embeddings=X)
>>> cross_validate(LogisticRegression(), X, y, cv=cv)
Source code in splytters/sklearn_api.py
splytter_train_test_split ¶
splytter_train_test_split(*arrays: Any, splitter: Splitter = cluster_split, embeddings: Any = None, train_size: float | int = 0.7, random_state: int | None = 42, **splitter_kwargs: Any) -> list[Any] | tuple[np.ndarray, np.ndarray]
Split arrays into train/test subsets using a splytter splitter.
Mirrors :func:sklearn.model_selection.train_test_split: for inputs
a, b returns [a_train, a_test, b_train, b_test].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*arrays
|
Any
|
array-likes (numpy, list, pandas, etc.) to split, all of the same length (n_samples). |
()
|
splitter
|
Splitter
|
splytter splitting function. Defaults to
:func: |
cluster_split
|
embeddings
|
Any
|
array-like to compute the split on, optional. Defaults to the first array. |
None
|
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set. |
0.7
|
random_state
|
int | None
|
seed forwarded to |
42
|
**splitter_kwargs
|
Any
|
extra keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
list[Any] | tuple[ndarray, ndarray]
|
|
list[Any] | tuple[ndarray, ndarray]
|
are passed, returns |
Source code in splytters/sklearn_api.py
Framework interop¶
interop ¶
Framework interop helpers for splytters.
Thin adapters that take an embedding-based splitter and return native objects for popular ecosystems. Heavy dependencies (pandas, torch, datasets) are imported lazily inside each function, so importing this module never requires them — only calling the relevant helper does.
split_dataframe ¶
split_dataframe(df: Any, embeddings: Any, splitter: Splitter = cluster_split, *, train_size: float | int = 0.7, random_state: int | None = 42, **splitter_kwargs: Any) -> tuple[Any, Any]
Split a pandas DataFrame into (train_df, test_df) by position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
Any
|
pandas |
required |
embeddings
|
Any
|
array-like of shape (n_samples, n_features). |
required |
splitter
|
Splitter
|
splytter splitting function. |
cluster_split
|
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set. |
0.7
|
random_state
|
int | None
|
seed forwarded to |
42
|
**splitter_kwargs
|
Any
|
extra keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
|
Any
|
index labels are preserved). |
Source code in splytters/interop.py
to_torch_subsets ¶
to_torch_subsets(dataset: Any, embeddings: Any, splitter: Splitter = cluster_split, *, train_size: float | int = 0.7, random_state: int | None = 42, **splitter_kwargs: Any) -> tuple[Any, Any]
Split a torch Dataset into (train_subset, test_subset).
Returns two :class:torch.utils.data.Subset views over dataset.
Source code in splytters/interop.py
split_dataset ¶
split_dataset(ds: Any, embeddings: Any, splitter: Splitter = cluster_split, *, train_size: float | int = 0.7, random_state: int | None = 42, **splitter_kwargs: Any) -> Any
Split a HuggingFace datasets.Dataset into a DatasetDict.
Returns DatasetDict({"train": ds.select(train_idx),
"test": ds.select(test_idx)}).
Source code in splytters/interop.py
Split-quality reporting¶
report ¶
Split-quality reporting.
Quantifies how adversarial, overlapping, or balanced a produced train/test
split actually is — so users can compare splitters and so papers can report a
single, interpretable table. Builds on :func:splytters.utils.compute_split_similarity
and :func:splytters.adversarial.get_cluster_info, adding distribution-distance
metrics (MMD, energy distance, mean 1-D Wasserstein / KS) and label balance.
split_report ¶
split_report(embeddings: ArrayLike, train_indices: ArrayLike, test_indices: ArrayLike, y: ArrayLike | None = None, *, texts: list[str] | None = None, metric: str = 'euclidean', n_clusters: int = 10, max_samples: int | None = 2000, random_state: int | None = 42) -> dict[str, float]
Summarize how similar/dissimilar a train/test split is.
Larger distribution distances (mmd_rbf, energy_distance,
wasserstein_mean, ks_mean) and mean_cross_distance indicate a
more adversarial (harder) split; values near a random split indicate a
balanced split; near-zero indicates overlap.
Alongside these train↔test distance metrics, train_diversity and
test_diversity report the within-split spread (mean distance to each
side's own centroid) — useful for spotting when a split concentrates one
side (e.g. an adversarial test drawn from a single outlier cluster).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, n_features). |
required |
train_indices
|
ArrayLike
|
integer index array for the training split. |
required |
test_indices
|
ArrayLike
|
integer index array for the test split. |
required |
y
|
ArrayLike | None
|
array-like of labels, optional. If given, adds
|
None
|
texts
|
list[str] | None
|
per-sample raw strings, optional and aligned with |
None
|
metric
|
str
|
distance metric (default |
'euclidean'
|
n_clusters
|
int
|
clusters used for the leakage statistic (default 10). |
10
|
max_samples
|
int | None
|
cap per side for the O(n²) distribution metrics
(subsampled for speed); |
2000
|
random_state
|
int | None
|
seed for subsampling (default 42). |
42
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dict of structural, geometric, distributional, diversity, and |
dict[str, float]
|
(optional) label metrics. |
Source code in splytters/report.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
compare_splitters ¶
compare_splitters(embeddings: ArrayLike, splitters: dict[str, Splitter], y: ArrayLike | None = None, *, train_size: float | int = 0.7, random_state: int | None = 42, **report_kwargs: Any) -> dict[str, dict[str, float]]
Run several splitters and return {name: split_report(...)}.
Handy for generating a comparison table (e.g. random vs adversarial vs balanced) in one call.
Source code in splytters/report.py
Sorters¶
Ranking functions that order samples by interpretable difficulty/quality
metrics, grouped by modality. Pair a ranking with
sorted_stratified_split for a curriculum split. Each
modality imports its heavy dependency lazily, so importing a sorter module needs
no optional extra.
Embedding sorters¶
embedding_sorters ¶
Sorting algorithms for adversarial dataset partitioning based on embeddings.
These functions rank samples by embedding-based criteria (distance to centroid, nearest neighbors, density, outlier scores) to enable train-test splits that maximize dissimilarity.
dist_euclidean ¶
distance_to_mean ¶
distance_to_mean(embeddings: ndarray, distance: Callable[[ndarray, ndarray], float] = dist_euclidean) -> list[tuple[int, float]]
Sort samples by distance from the dataset centroid.
Samples closest to the centroid (most "typical") appear first. Useful for adversarial splits: assign nearby samples to train, distant samples to test.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ndarray
|
np.array of shape (n_samples, embedding_dim) |
required |
distance
|
Callable[[ndarray, ndarray], float]
|
distance function taking two vectors, default Euclidean |
dist_euclidean
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, distance) tuples sorted by distance ascending. |
Source code in splytters/sorters/embedding_sorters.py
distance_to_nearest_neighbor ¶
distance_to_nearest_neighbor(embeddings: ArrayLike, metric: str = 'euclidean') -> list[tuple[int, float]]
Sort samples by distance to their nearest neighbor.
Samples in dense regions (close to neighbors) appear first. Isolated samples (far from all neighbors) appear last.
Useful for adversarial splits: train on samples in dense clusters, test on isolated/unique samples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
np.array of shape (n_samples, embedding_dim) |
required |
metric
|
str
|
distance metric for cdist (default 'euclidean') |
'euclidean'
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, distance) tuples sorted by nearest neighbor distance ascending. |
Source code in splytters/sorters/embedding_sorters.py
local_density ¶
local_density(embeddings: ArrayLike, radius: float | None = None, metric: str = 'euclidean', low_first: bool = True) -> list[tuple[int, int]]
Sort samples by local density (number of neighbors within radius).
Samples in dense regions have many neighbors; isolated samples have few.
Useful for adversarial splits: train on high-density regions, test on sparse/low-density regions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
np.array of shape (n_samples, embedding_dim) |
required |
radius
|
float | None
|
distance threshold for counting neighbors. If None, uses median pairwise distance. |
None
|
metric
|
str
|
distance metric for cdist (default 'euclidean') |
'euclidean'
|
low_first
|
bool
|
if True, sparse/isolated samples first; if False, dense samples first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, int]]
|
List of (index, neighbor_count) tuples sorted by density. |
Source code in splytters/sorters/embedding_sorters.py
outlier_score ¶
outlier_score(embeddings: ArrayLike, method: str = 'isolation_forest', low_first: bool = True, **kwargs: Any) -> list[tuple[int, float]]
Sort samples by anomaly/outlier score.
Higher outlier scores indicate samples that are unusual or don't fit the overall data distribution.
Useful for adversarial splits: train on normal/typical samples, test on outliers/anomalies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
np.array of shape (n_samples, embedding_dim) |
required |
method
|
str
|
outlier detection algorithm, one of: - 'isolation_forest': Isolation Forest (fast, good for high dimensions) - 'lof': Local Outlier Factor (density-based) |
'isolation_forest'
|
low_first
|
bool
|
if True, normal/inlier samples first; if False, outliers/anomalies first |
True
|
**kwargs
|
Any
|
additional arguments passed to the outlier detector |
{}
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, outlier_score) tuples sorted by outlier score. |
list[tuple[int, float]]
|
For isolation_forest: more negative = more normal, more positive = more outlier. |
list[tuple[int, float]]
|
For lof: scores > 1 indicate outliers, < 1 indicate inliers. |
Source code in splytters/sorters/embedding_sorters.py
Text sorters¶
text_sorters ¶
Sorting algorithms for adversarial text dataset partitioning.
These functions rank text samples by various criteria (length, complexity, perplexity, readability, vocabulary) to enable train-test splits that maximize dissimilarity.
simple_tokenizer ¶
character_length ¶
Sort texts by character count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
list of strings |
required |
low_first
|
bool
|
if True, shortest texts first; if False, longest first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, int]]
|
List of (index, character_count) tuples sorted by length. |
References
Splitting by length to test generalization (put long texts in test): - Søgaard, Ebert, Bastings & Filippova (2021), "We Need to Talk About Random Splits," EACL. Heuristic splits via a sentence-length threshold. https://aclanthology.org/2021.eacl-main.156 - Varis & Bojar (2021), "Sequence Length is a Domain: Length-based Overfitting in Transformer Models," EMNLP. https://aclanthology.org/2021.emnlp-main.650 - Lake & Baroni (2018), "Generalization without Systematicity," ICML, introduced the SCAN length split (train short, test long) -- a canonical length-generalization test (on output sequence length, synthetic data). https://arxiv.org/abs/1711.00350 - Søgaard et al. trace the idea of testing generalization to longer sequences back to Siegelmann & Sontag (1992), "On the Computational Power of Neural Nets," COLT, pp. 440-449. https://doi.org/10.1145/130385.130432
Source code in splytters/sorters/text_sorters.py
tokens_length ¶
tokens_length(texts: list[str], low_first: bool = True, tokenizer: Callable[[str], list[str]] = simple_tokenizer) -> list[tuple[int, int]]
Sort texts by token count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
list of strings |
required |
low_first
|
bool
|
if True, fewest tokens first; if False, most tokens first |
True
|
tokenizer
|
Callable[[str], list[str]]
|
function that splits text into tokens, default whitespace split |
simple_tokenizer
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, int]]
|
List of (index, token_count) tuples sorted by token count. |
References
A token-count variant of length-based splitting; see character_length
for the motivation and references (Søgaard et al. 2021, EACL; Varis &
Bojar 2021, EMNLP; Lake & Baroni 2018, ICML; Siegelmann & Sontag 1992,
COLT).
Source code in splytters/sorters/text_sorters.py
sentence_count ¶
sentence_count(texts: list[str], language: str = 'en', low_first: bool = True) -> list[tuple[int, int]]
Sort texts by number of sentences.
Uses pysbd for robust sentence boundary detection across languages.
Useful for adversarial splits: train on single-sentence texts, test on multi-sentence/complex texts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
list of strings |
required |
language
|
str
|
language code for sentence segmentation (default 'en') |
'en'
|
low_first
|
bool
|
if True, fewer sentences first; if False, more sentences first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, int]]
|
List of (index, sentence_count) tuples sorted by sentence count. |
Source code in splytters/sorters/text_sorters.py
lexical_diversity ¶
lexical_diversity(texts: list[str], tokenizer: Callable[[str], list[str]] = simple_tokenizer, low_first: bool = True) -> list[tuple[int, float]]
Sort texts by lexical diversity (type-token ratio).
Type-token ratio = unique tokens / total tokens. Higher values indicate more diverse vocabulary; lower values indicate more repetitive text.
Useful for adversarial splits: train on repetitive/simple vocabulary, test on diverse/rich vocabulary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
list of strings |
required |
tokenizer
|
Callable[[str], list[str]]
|
function that splits text into tokens |
simple_tokenizer
|
low_first
|
bool
|
if True, repetitive texts first; if False, diverse texts first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, ttr) tuples sorted by type-token ratio. |
list[tuple[int, float]]
|
Texts with no tokens receive a score of 0. |
Source code in splytters/sorters/text_sorters.py
vocabulary_rarity ¶
vocabulary_rarity(texts: list[str], language: str = 'en', tokenizer: Callable[[str], list[str]] = simple_tokenizer, low_first: bool = True) -> list[tuple[int, float]]
Sort texts by average word rarity.
Uses word frequency data to score each word's rarity. Rarer words have lower frequency, so we use (1 - frequency) as the rarity score.
Useful for adversarial splits: train on common vocabulary, test on rare/specialized vocabulary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
list of strings |
required |
language
|
str
|
language code for frequency lookup (default 'en') |
'en'
|
tokenizer
|
Callable[[str], list[str]]
|
function that splits text into tokens |
simple_tokenizer
|
low_first
|
bool
|
if True, common vocabulary first; if False, rare vocabulary first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, avg_rarity) tuples sorted by average word rarity. |
list[tuple[int, float]]
|
Texts with no tokens receive a score of 0. |
Source code in splytters/sorters/text_sorters.py
perplexity_score ¶
perplexity_score(texts: list[str], model: GPT2LMHeadModel | None = None, tokenizer: GPT2TokenizerFast | None = None, low_first: bool = True, scoring: str = 'perplexity') -> list[tuple[int, float]]
Sort texts by how typical a language model finds them.
A causal LM is run over each text to measure how "surprised" it is. Two
scoring modes are offered (see scoring); both rank the same way — typical
text is ordered before unusual text — but they put different things in the
difficult tail, so the returned score values differ.
Useful for adversarial / curriculum splits: train on typical text, test on
unusual text. Pair with :func:splytters.sorted_stratified_split to build
"Likelihood Splits" (train on the high-likelihood head, test on the tail).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
list of strings to score |
required |
model
|
GPT2LMHeadModel | None
|
HuggingFace causal LM (defaults to GPT-2 if None) |
None
|
tokenizer
|
GPT2TokenizerFast | None
|
HuggingFace tokenizer (defaults to GPT-2 if None) |
None
|
low_first
|
bool
|
if True, typical/predictable texts first; if False, unusual/surprising texts first. The ordering is by typicality, so this holds for both scoring modes despite their opposite raw-value directions. |
True
|
scoring
|
str
|
how to score each text.
- |
'perplexity'
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of |
list[tuple[int, float]]
|
ordered by typicality per |
list[tuple[int, float]]
|
the most-difficult sentinel ( |
list[tuple[int, float]]
|
log-likelihood), so they always land in the tail. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
References
Godbole & Jia (2023), "Benchmarking Long-tail Generalization with
Likelihood Splits," Findings of the ACL: EACL, pp. 963-983 —
https://aclanthology.org/2023.findings-eacl.71 . They split datasets by
LM likelihood (head -> train, tail -> test). Note they deliberately use
total log-likelihood rather than perplexity: perplexity normalizes for
length and over-corrects toward short examples in the tail (their fn. 3).
Use scoring="log_likelihood" to reproduce their choice.
Source code in splytters/sorters/text_sorters.py
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | |
readability_score ¶
readability_score(texts: list[str], metric: str = 'flesch_kincaid', low_first: bool = True) -> list[tuple[int, float]]
Sort texts by readability score.
Readability scores estimate the education level required to understand text. Higher scores generally indicate more complex/difficult text.
Useful for adversarial splits: train on simple text, test on complex text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
list of strings to score |
required |
metric
|
str
|
readability formula to use, one of: - 'flesch_kincaid': Flesch-Kincaid Grade Level - 'flesch': Flesch Reading Ease (higher = easier, inverted scale) - 'gunning_fog': Gunning Fog Index - 'coleman_liau': Coleman-Liau Index - 'dale_chall': Dale-Chall Readability Score - 'ari': Automated Readability Index - 'linsear_write': Linsear Write Formula - 'smog': SMOG Index |
'flesch_kincaid'
|
low_first
|
bool
|
if True, easier/simpler texts first; if False, harder/complex texts first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, score) tuples sorted by readability. |
list[tuple[int, float]]
|
Texts too short to score receive a score of infinity. |
Source code in splytters/sorters/text_sorters.py
Image sorters¶
image_sorters ¶
Sorting algorithms for adversarial image dataset partitioning.
These functions rank images by visual properties (brightness, contrast, color, complexity) to enable train-test splits that maximize dissimilarity.
All functions accept either file paths or PIL Image objects.
mean_brightness ¶
Sort images by mean brightness (average pixel intensity).
Converts images to grayscale and computes mean intensity (0-255 scale).
Useful for adversarial splits: train on mid-tone images, test on very dark or very bright images.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images
|
list[ImageInput]
|
list of file paths or PIL Image objects |
required |
low_first
|
bool
|
if True, darker images first; if False, brighter images first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, brightness) tuples sorted by mean brightness. |
Source code in splytters/sorters/image_sorters.py
contrast ¶
Sort images by contrast (standard deviation of pixel intensities).
Higher contrast means more variation between light and dark regions. Lower contrast means flatter, more uniform images.
Useful for adversarial splits: train on normal contrast images, test on very flat or very high contrast images.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images
|
list[ImageInput]
|
list of file paths or PIL Image objects |
required |
low_first
|
bool
|
if True, low contrast (flat) images first; if False, high contrast images first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, contrast) tuples sorted by contrast. |
Source code in splytters/sorters/image_sorters.py
color_variance ¶
Sort images by color variance (spread across RGB channels).
Measures how much the RGB channels differ from each other. Higher variance indicates more colorful/saturated images. Lower variance indicates grayscale-like or desaturated images.
Useful for adversarial splits: train on neutral/desaturated images, test on highly colorful/saturated images.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images
|
list[ImageInput]
|
list of file paths or PIL Image objects |
required |
low_first
|
bool
|
if True, desaturated images first; if False, colorful images first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, color_variance) tuples sorted by color variance. |
Source code in splytters/sorters/image_sorters.py
dominant_color ¶
dominant_color(images: list[ImageInput], n_clusters: int = 5, target_color: list[int] | None = None, low_first: bool = True, random_state: int = 42) -> list[tuple[int, float]]
Sort images by their dominant color.
Uses k-means clustering to find dominant colors, then sorts by distance from a target color (default: neutral gray).
Useful for adversarial splits: train on images with one color palette, test on images with different dominant colors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images
|
list[ImageInput]
|
list of file paths or PIL Image objects |
required |
n_clusters
|
int
|
number of color clusters to find (default 5) |
5
|
target_color
|
list[int] | None
|
RGB tuple to measure distance from (default [128, 128, 128] gray) |
None
|
low_first
|
bool
|
if True, images closest to target color first; if False, images furthest from target first |
True
|
random_state
|
int
|
seed for the pixel subsample and k-means (default 42) |
42
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, distance) tuples sorted by distance from target color. |
Source code in splytters/sorters/image_sorters.py
compression_ratio ¶
compression_ratio(images: list[ImageInput], quality: int = 85, low_first: bool = True) -> list[tuple[int, float]]
Sort images by JPEG compression ratio.
Simple/uniform images compress well (high ratio). Complex/detailed images compress poorly (low ratio).
Useful for adversarial splits: train on simple/compressible images, test on complex/incompressible images.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images
|
list[ImageInput]
|
list of file paths or PIL Image objects |
required |
quality
|
int
|
JPEG quality setting for compression test (default 85) |
85
|
low_first
|
bool
|
if True, complex images first (low ratio); if False, simple images first (high ratio) |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, ratio) tuples sorted by compression ratio. |
list[tuple[int, float]]
|
Ratio = uncompressed size / compressed size. |
Source code in splytters/sorters/image_sorters.py
frequency_content ¶
Sort images by high-frequency content (texture/detail level).
Uses FFT to analyze frequency distribution. Higher values indicate more high-frequency content (edges, textures, fine details). Lower values indicate smoother, low-frequency images.
Useful for adversarial splits: train on smooth/simple images, test on textured/detailed images.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
images
|
list[ImageInput]
|
list of file paths or PIL Image objects |
required |
low_first
|
bool
|
if True, smooth/low-frequency images first; if False, textured/high-frequency images first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, high_freq_ratio) tuples sorted by frequency content. |
Source code in splytters/sorters/image_sorters.py
Audio sorters¶
audio_sorters ¶
Sorting algorithms for adversarial audio dataset partitioning.
These functions rank audio samples by various criteria (loudness, spectral features, rhythm, timbre) to enable train-test splits that maximize dissimilarity.
All functions accept either file paths or pre-loaded audio arrays. Requires librosa for audio processing.
duration ¶
duration(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by total duration in seconds.
Useful for adversarial / curriculum splits: train on short utterances,
test on long ones (or vice versa). Pair with
:func:splytters.sorted_stratified_split for a length-based curriculum.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, shortest audio first; if False, longest first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, seconds) tuples sorted by duration. |
References
Liu, Spence & Prud'hommeaux (2023), "Investigating data partitioning strategies for crosslinguistic low-resource ASR evaluation," EACL, pp. 123-131. https://aclanthology.org/2023.eacl-main.10 . They build heuristic test sets from per-utterance features -- utterance duration, average intensity, average pitch, transcript token count and perplexity -- and find utterance duration and intensity to be the most predictive factors of word-error-rate variability across data splits. Caveat: in their very low-resource setting, heuristic and adversarial splits behaved much like random splits, which were the more reliable choice under data sparsity -- so prefer random/averaged splits when data is scarce.
Source code in splytters/sorters/audio_sorters.py
mean_amplitude ¶
mean_amplitude(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by mean absolute amplitude.
Useful for adversarial splits: train on mid-volume audio, test on very quiet or very loud audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, quieter audio first; if False, louder first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, amplitude) tuples sorted by mean amplitude. |
References
A loudness/intensity feature; see :func:duration for the
Liu, Spence & Prud'hommeaux (2023) ASR data-partitioning study, which
uses average intensity as a heuristic split feature.
Source code in splytters/sorters/audio_sorters.py
rms_energy ¶
rms_energy(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by root mean square energy.
RMS energy is a common measure of audio loudness that better correlates with perceived volume than simple amplitude.
Useful for adversarial splits: train on consistent energy levels, test on very quiet or very loud audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, lower energy first; if False, higher energy first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, rms) tuples sorted by RMS energy. |
References
RMS energy is an "average intensity" measure; see :func:duration for
the Liu, Spence & Prud'hommeaux (2023) ASR data-partitioning study, which
found intensity among the most predictive features of WER variability.
Source code in splytters/sorters/audio_sorters.py
dynamic_range ¶
dynamic_range(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by dynamic range (difference between max and min amplitude).
Higher dynamic range indicates more variation between loud and quiet parts. Lower dynamic range indicates more compressed/consistent audio.
Useful for adversarial splits: train on compressed audio, test on highly dynamic audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, compressed audio first; if False, dynamic audio first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, range) tuples sorted by dynamic range. |
Source code in splytters/sorters/audio_sorters.py
peak_to_average_ratio ¶
peak_to_average_ratio(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by peak-to-average ratio (crest factor).
Higher values indicate peaky/transient audio (e.g., percussion). Lower values indicate more sustained/compressed audio.
Useful for adversarial splits: train on sustained sounds, test on transient/percussive sounds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, sustained audio first; if False, peaky audio first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, ratio) tuples sorted by peak-to-average ratio. |
Source code in splytters/sorters/audio_sorters.py
spectral_centroid ¶
spectral_centroid(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by spectral centroid ("center of mass" of the spectrum).
Higher values indicate brighter, treble-heavy audio. Lower values indicate darker, bass-heavy audio.
Useful for adversarial splits: train on mid-frequency audio, test on very bright or very dark audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, darker audio first; if False, brighter audio first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, centroid_hz) tuples sorted by spectral centroid. |
Source code in splytters/sorters/audio_sorters.py
spectral_bandwidth ¶
spectral_bandwidth(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by spectral bandwidth (spread around the centroid).
Higher values indicate wider frequency spread (broadband). Lower values indicate narrower frequency content (tonal).
Useful for adversarial splits: train on narrow-band audio, test on broadband audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, narrow-band first; if False, broadband first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, bandwidth_hz) tuples sorted by spectral bandwidth. |
Source code in splytters/sorters/audio_sorters.py
spectral_rolloff ¶
spectral_rolloff(audios: list[AudioInput], sr: int = 22050, roll_percent: float = 0.85, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by spectral rolloff frequency.
The rolloff frequency is the frequency below which a specified percentage of total spectral energy lies.
Higher values indicate more high-frequency content. Lower values indicate more low-frequency content.
Useful for adversarial splits: train on one frequency profile, test on different frequency profiles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
roll_percent
|
float
|
energy percentage threshold (default 0.85) |
0.85
|
low_first
|
bool
|
if True, low rolloff first; if False, high rolloff first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, rolloff_hz) tuples sorted by spectral rolloff. |
Source code in splytters/sorters/audio_sorters.py
spectral_flatness ¶
spectral_flatness(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by spectral flatness (Wiener entropy).
Values close to 1 indicate noise-like audio (flat spectrum). Values close to 0 indicate tonal audio (peaked spectrum).
Useful for adversarial splits: train on tonal audio, test on noisy audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, tonal audio first; if False, noisy audio first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, flatness) tuples sorted by spectral flatness. |
Source code in splytters/sorters/audio_sorters.py
zero_crossing_rate ¶
zero_crossing_rate(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by zero crossing rate.
Higher values indicate noisier or more percussive audio. Lower values indicate smoother, more tonal audio.
Useful for adversarial splits: train on smooth audio, test on noisy/percussive audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, smooth audio first; if False, noisy audio first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, zcr) tuples sorted by zero crossing rate. |
Source code in splytters/sorters/audio_sorters.py
fundamental_frequency ¶
fundamental_frequency(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by estimated fundamental frequency (pitch).
Uses pYIN algorithm for robust pitch estimation.
Useful for adversarial splits: train on mid-pitch audio, test on very high or very low pitch audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, lower pitch first; if False, higher pitch first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, f0_hz) tuples sorted by fundamental frequency. |
list[tuple[int, float]]
|
Audio with no detectable pitch receives f0 of 0. |
References
Pitch is one of the per-utterance heuristic split features in
Liu, Spence & Prud'hommeaux (2023); see :func:duration for the full
citation.
Source code in splytters/sorters/audio_sorters.py
mfcc_mean ¶
mfcc_mean(audios: list[AudioInput], sr: int = 22050, n_mfcc: int = 13, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by mean of first MFCC coefficient.
The first MFCC coefficient relates to overall energy/loudness. Higher MFCCs capture timbral characteristics.
Useful for adversarial splits: separate by timbral characteristics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
n_mfcc
|
int
|
number of MFCC coefficients (default 13) |
13
|
low_first
|
bool
|
if True, lower mean MFCC first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, mfcc_mean) tuples sorted by mean of first MFCC. |
Source code in splytters/sorters/audio_sorters.py
mfcc_variance ¶
mfcc_variance(audios: list[AudioInput], sr: int = 22050, n_mfcc: int = 13, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by variance of MFCC coefficients over time.
Higher variance indicates more timbral variation over time. Lower variance indicates more consistent timbre.
Useful for adversarial splits: train on stable timbre, test on varying timbre.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
n_mfcc
|
int
|
number of MFCC coefficients (default 13) |
13
|
low_first
|
bool
|
if True, stable timbre first; if False, varying timbre first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, mfcc_var) tuples sorted by MFCC variance. |
Source code in splytters/sorters/audio_sorters.py
tempo ¶
Sort audio by estimated tempo (beats per minute).
Useful for adversarial splits: train on mid-tempo audio, test on very slow or very fast audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, slower tempo first; if False, faster tempo first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, bpm) tuples sorted by tempo. |
Source code in splytters/sorters/audio_sorters.py
beat_strength ¶
beat_strength(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by beat/onset strength.
Higher values indicate more percussive, rhythmically strong audio. Lower values indicate more ambient, less rhythmic audio.
Useful for adversarial splits: train on strong beat audio, test on ambient/weak beat audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, weak beats first; if False, strong beats first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, strength) tuples sorted by beat strength. |
Source code in splytters/sorters/audio_sorters.py
harmonic_ratio ¶
harmonic_ratio(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by harmonic-to-percussive ratio.
Higher values indicate more melodic/harmonic content. Lower values indicate more percussive/rhythmic content.
Useful for adversarial splits: train on melodic audio, test on percussive audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, percussive audio first; if False, harmonic audio first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, ratio) tuples sorted by harmonic ratio. |
Source code in splytters/sorters/audio_sorters.py
compression_ratio ¶
compression_ratio(audios: list[AudioInput], sr: int = 22050, low_first: bool = True) -> list[tuple[int, float]]
Sort audio by compression ratio (uncompressed / compressed size).
Simple/repetitive audio compresses well (high ratio). Complex/varied audio compresses poorly (low ratio).
Useful for adversarial splits: train on simple audio, test on complex audio.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
audios
|
list[AudioInput]
|
list of file paths or (samples, sr) tuples |
required |
sr
|
int
|
sample rate for loading files (default 22050) |
22050
|
low_first
|
bool
|
if True, complex audio first; if False, simple audio first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[int, float]]
|
List of (index, ratio) tuples sorted by compression ratio. |
Source code in splytters/sorters/audio_sorters.py
Tabular sorters¶
tabular_sorters ¶
Sorting algorithms for adversarial tabular dataset partitioning.
These functions rank rows in tabular data (pandas DataFrames) by various criteria (missing values, outliers, sparsity, column values) to enable train-test splits that maximize dissimilarity.
All functions accept pandas DataFrames and return sorted index lists.
column_value ¶
Sort rows by values in a specified column.
Generic sorting function that allows users to sort by any column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
column
|
str
|
column name to sort by |
required |
low_first
|
bool
|
if True, lowest values first; if False, highest values first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, Any]]
|
List of (index, value) tuples sorted by column value. |
list[tuple[Hashable, Any]]
|
NaN values are placed at the end. |
Source code in splytters/sorters/tabular_sorters.py
column_rank ¶
Sort rows by percentile rank within a column.
Useful when you want to split by relative position rather than absolute values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
column
|
str
|
column name to rank by |
required |
low_first
|
bool
|
if True, lowest percentile first; if False, highest first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, percentile) tuples sorted by percentile rank (0-100). |
Source code in splytters/sorters/tabular_sorters.py
column_zscore ¶
Sort rows by z-score (standard deviations from mean) in a column.
Useful for finding outliers in a specific column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
column
|
str
|
column name to compute z-scores for |
required |
low_first
|
bool
|
if True, lowest z-scores first (below mean); if False, highest z-scores first (above mean) |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, zscore) tuples sorted by z-score. |
list[tuple[Hashable, float]]
|
NaN values receive z-score of infinity. |
Source code in splytters/sorters/tabular_sorters.py
column_absolute_zscore ¶
column_absolute_zscore(df: DataFrame, column: str, low_first: bool = True) -> list[tuple[Hashable, float]]
Sort rows by absolute z-score (distance from mean) in a column.
Useful for adversarial splits: train on typical values (low |z|), test on extreme values (high |z|).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
column
|
str
|
column name to compute z-scores for |
required |
low_first
|
bool
|
if True, typical values first; if False, extreme values first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, abs_zscore) tuples sorted by absolute z-score. |
Source code in splytters/sorters/tabular_sorters.py
missing_value_ratio ¶
Sort rows by proportion of missing (NaN/None) values.
Useful for adversarial splits: train on complete rows, test on rows with missing data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
low_first
|
bool
|
if True, complete rows first; if False, sparse rows first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, missing_ratio) tuples sorted by missing ratio (0-1). |
Source code in splytters/sorters/tabular_sorters.py
row_sparsity ¶
row_sparsity(df: DataFrame, zero_threshold: float = 1e-10, low_first: bool = True) -> list[tuple[Hashable, float]]
Sort rows by proportion of zero (or near-zero) values.
Useful for adversarial splits: train on dense rows, test on sparse rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame (numeric columns only) |
required |
zero_threshold
|
float
|
values with abs < threshold are considered zero |
1e-10
|
low_first
|
bool
|
if True, dense rows first; if False, sparse rows first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, sparsity) tuples sorted by sparsity ratio (0-1). |
Source code in splytters/sorters/tabular_sorters.py
outlier_score ¶
outlier_score(df: DataFrame, method: str = 'isolation_forest', low_first: bool = True, **kwargs: Any) -> list[tuple[Hashable, float]]
Sort rows by anomaly/outlier score.
Uses outlier detection algorithms to score how unusual each row is.
Useful for adversarial splits: train on normal/typical rows, test on outliers/anomalies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame (numeric columns only, NaN will be filled with median) |
required |
method
|
str
|
outlier detection algorithm, one of: - 'isolation_forest': Isolation Forest (fast, good for high dimensions) - 'lof': Local Outlier Factor (density-based) - 'zscore': Mean absolute z-score across columns |
'isolation_forest'
|
low_first
|
bool
|
if True, normal rows first; if False, outliers first |
True
|
**kwargs
|
Any
|
additional arguments passed to the outlier detector |
{}
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, outlier_score) tuples sorted by outlier score. |
Source code in splytters/sorters/tabular_sorters.py
numerical_range_score ¶
Sort rows by how extreme their numerical values are (distance from median).
Computes the mean percentile distance from 50th percentile across all numeric columns.
Useful for adversarial splits: train on typical values, test on extreme values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
low_first
|
bool
|
if True, typical rows first; if False, extreme rows first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, extremity) tuples sorted by extremity score (0-50). |
Source code in splytters/sorters/tabular_sorters.py
categorical_rarity ¶
categorical_rarity(df: DataFrame, column: str, low_first: bool = True) -> list[tuple[Hashable, float]]
Sort rows by rarity of categorical value in a column.
Useful for adversarial splits: train on common categories, test on rare categories.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
column
|
str
|
categorical column name |
required |
low_first
|
bool
|
if True, common categories first; if False, rare categories first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, frequency) tuples sorted by category frequency. |
list[tuple[Hashable, float]]
|
Higher frequency = more common. |
Note
Unlike most sorters (whose score is a difficulty/rarity measure
where low_first orders ascending), the score here is frequency
(high = common). low_first=True therefore orders by commonness —
common categories first — which is the natural "train on common,
test on rare" direction for an adversarial split.
Source code in splytters/sorters/tabular_sorters.py
feature_entropy ¶
Sort rows by entropy of categorical features.
Rows with rare combinations of categorical values have higher "entropy" in the sense that they're less predictable.
Approximated by summing -log(freq) for each categorical value.
Useful for adversarial splits: train on common patterns, test on rare patterns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
low_first
|
bool
|
if True, common patterns first; if False, rare patterns first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, neg_log_freq) tuples sorted by rarity score. |
Source code in splytters/sorters/tabular_sorters.py
row_distance_to_mean ¶
Sort rows by Euclidean distance from the column-wise mean.
Similar to embedding distance_to_mean but operates directly on tabular features.
Useful for adversarial splits: train on typical rows, test on atypical rows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame (numeric columns only) |
required |
low_first
|
bool
|
if True, typical rows first; if False, atypical rows first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, distance) tuples sorted by distance from mean. |
Source code in splytters/sorters/tabular_sorters.py
multi_column_sort ¶
multi_column_sort(df: DataFrame, columns: list[str], weights: list[float] | None = None, low_first: bool = True) -> list[tuple[Hashable, float]]
Sort rows by weighted combination of multiple columns.
Useful when you want to combine multiple criteria into a single sort.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
pandas DataFrame |
required |
columns
|
list[str]
|
list of column names to sort by |
required |
weights
|
list[float] | None
|
list of weights for each column (default: equal weights) Positive weight = higher value increases score Negative weight = higher value decreases score |
None
|
low_first
|
bool
|
if True, lowest combined score first |
True
|
Returns:
| Type | Description |
|---|---|
list[tuple[Hashable, float]]
|
List of (index, combined_score) tuples sorted by combined score. |
Source code in splytters/sorters/tabular_sorters.py
Utilities¶
utils ¶
Shared utilities for splitting algorithms.
to_numpy ¶
Best-effort conversion of framework tensors to numpy.
Handles PyTorch tensors (including those on GPU / requiring grad) by
detaching, moving to CPU, and converting. Any other input is returned
unchanged so the caller's downstream check_array/np.asarray can
handle lists, numpy arrays, pandas DataFrames, etc.
Source code in splytters/utils.py
validate_split_inputs ¶
validate_split_inputs(embeddings: ArrayLike, train_size: float | int, min_samples: int = 2) -> np.ndarray
Validate and coerce inputs shared by every splitting function.
Accepts any array-like (numpy, list, pandas, torch tensor), converts it to
a finite 2-D float ndarray, and validates train_size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, n_features). |
required |
train_size
|
float | int
|
fraction in the open interval (0, 1), or an absolute count
in |
required |
min_samples
|
int
|
minimum number of samples required to form a split (default 2). |
2
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The validated, finite, float embeddings as an ndarray of shape |
ndarray
|
(n_samples, n_features). |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
Source code in splytters/utils.py
resolve_n_train ¶
Resolve train_size (fraction or absolute count) to an int count.
A fractional train_size is clamped to [1, n_samples - 1] so the
resolved count never collapses one side of the split to empty (e.g.
n_samples=2, train_size=0.3 would otherwise truncate to 0).
Source code in splytters/utils.py
apportion_train ¶
Split n_train_total train slots across bins via largest-remainder.
Each bin gets floor(size * n_train_total / total) slots, then the
leftover slots go to the bins with the largest fractional remainders. The
per-bin counts sum exactly to n_train_total (clamped to [0, total]),
avoiding both the per-bin int() truncation bias that undershoots the
requested train fraction and the all-to-train rounding that can empty the
test set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bin_sizes
|
list[int]
|
number of samples in each (non-empty) bin. |
required |
n_train_total
|
int
|
total samples to assign to train across all bins. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Integer ndarray of per-bin train counts, aligned with |
Source code in splytters/utils.py
as_index_array ¶
compute_pairwise_distances ¶
Compute pairwise distance matrix.
.. warning:: Materializes a full O(n²) matrix. For large datasets, prefer NearestNeighbors or chunked computation.
Source code in splytters/utils.py
compute_centroid ¶
compute_split_centroids ¶
compute_split_centroids(X: ArrayLike, train_indices: ArrayLike, test_indices: ArrayLike) -> tuple[np.ndarray | None, np.ndarray | None]
Compute centroids of train and test sets.
Source code in splytters/utils.py
cluster_embeddings ¶
cluster_embeddings(X: ArrayLike, n_clusters: int = 10, method: str = 'kmeans', random_state: int = 42, **kwargs: Any) -> tuple[np.ndarray, dict[int, list[int]], np.ndarray]
Cluster embeddings and return labels and cluster info.
Returns:
| Name | Type | Description |
|---|---|---|
labels |
ndarray
|
cluster label for each sample |
cluster_to_indices |
dict[int, list[int]]
|
dict mapping cluster_id to list of indices |
cluster_centers |
ndarray
|
cluster centroids (for kmeans) |
Source code in splytters/utils.py
random_split ¶
random_split(embeddings: ArrayLike, train_size: float | int = 0.7, random_state: int = 42) -> tuple[np.ndarray, np.ndarray]
Simple random train/test split (baseline).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ArrayLike
|
array-like of shape (n_samples, embedding_dim) |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
0.7
|
random_state
|
int
|
int, RandomState, or None for reproducibility |
42
|
Returns:
| Name | Type | Description |
|---|---|---|
train_indices |
ndarray
|
ndarray of indices for training set |
test_indices |
ndarray
|
ndarray of indices for test set |
Source code in splytters/utils.py
compute_split_similarity ¶
compute_split_similarity(X: ArrayLike, train_indices: ArrayLike, test_indices: ArrayLike, metric: str = 'euclidean') -> dict[str, float]
Compute similarity metrics between train and test splits.
Returns dict with
- centroid_distance: distance between train/test centroids
- mean_cross_distance: mean distance from test to nearest train
- coverage: fraction of test samples with train neighbor within median distance
Source code in splytters/utils.py
optimized_split ¶
optimized_split(embeddings: ndarray, train_size: float | int, n_iterations: int, score_fn: Callable[[ndarray, list[int], list[int]], float], random_state: int = 42, minimize: bool = True) -> tuple[np.ndarray, np.ndarray]
Iterative swap-optimization to find a split that optimizes a score.
Starts from a random split and repeatedly swaps one train/test pair, keeping the swap only if the score improves.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
embeddings
|
ndarray
|
array of shape (n_samples, embedding_dim), already np.ndarray |
required |
train_size
|
float | int
|
fraction in (0, 1) or absolute count for the training set |
required |
n_iterations
|
int
|
number of swap attempts |
required |
score_fn
|
Callable[[ndarray, list[int], list[int]], float]
|
callable(embeddings, train_indices, test_indices) -> float |
required |
random_state
|
int
|
int, RandomState, or None for reproducibility |
42
|
minimize
|
bool
|
if True, accept swaps that lower the score; if False, accept swaps that raise it |
True
|
Source code in splytters/utils.py
greedy_assign_to_target ¶
greedy_assign_to_target(items_with_sizes: list[tuple[int, int]], target_size: int) -> tuple[list[int], list[int]]
Greedily assign items to reach target size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items_with_sizes
|
list[tuple[int, int]]
|
list of (item_id, size) tuples |
required |
target_size
|
int
|
target total size |
required |
Returns:
| Name | Type | Description |
|---|---|---|
selected |
list[int]
|
list of item_ids assigned |
remaining |
list[int]
|
list of item_ids not assigned |
Source code in splytters/utils.py
Embedders¶
embedders ¶
Embedder ¶
Bases: ABC
Base class for all embedders.
Source code in splytters/embedders.py
embed
abstractmethod
¶
TextEmbedder ¶
Bases: Embedder
Embed text using a SentenceTransformer model.
Source code in splytters/embedders.py
CLIPTextEmbedder ¶
Bases: Embedder
Embed text using a CLIP model.
Source code in splytters/embedders.py
CLIPImageEmbedder ¶
Bases: Embedder
Embed images using a CLIP model.
Source code in splytters/embedders.py
OpenAIEmbedder ¶
Bases: Embedder
Embed text using the OpenAI embeddings API.
Source code in splytters/embedders.py
list_embedders ¶
Return the names of the available concrete embedder classes.
These live in :mod:splytters.embedders (the [embedders] extra). The
underlying model libraries (sentence-transformers, transformers, openai)
are imported lazily only when an embedder is constructed, so listing pulls
in no optional dependency.
Returns:
| Type | Description |
|---|---|
list[str]
|
Embedder class names, e.g. ``["TextEmbedder", "CLIPTextEmbedder", |
list[str]
|
"CLIPImageEmbedder", "OpenAIEmbedder"]``. |
Source code in splytters/embedders.py
Introspection¶
list_splitters ¶
Return the names of all available splitter functions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
by_family
|
bool
|
if True, return a dict mapping each family ("adversarial", "overlap", "balanced", "baseline") to its list of splitter names. If False (default), return a flat list of every splitter name, in family order. |
False
|
Returns:
| Type | Description |
|---|---|
list[str] | dict[str, list[str]]
|
A flat list of names, or a dict of family -> names. |
Source code in splytters/__init__.py
list_sorters ¶
Return the names of all available sorters.
Listing pulls in no optional dependency — only the names are returned; the sorters themselves stay lazily imported until first accessed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
by_modality
|
bool
|
if True, return a dict mapping each modality ("embedding", "text", "image", "audio", "tabular") to its sorted list of sorter names. If False (default), return a flat sorted list of every sorter name. |
False
|
Returns:
| Type | Description |
|---|---|
list[str] | dict[str, list[str]]
|
A sorted list of names, or a dict of modality -> sorted names. |
Source code in splytters/sorters/__init__.py
list_embedders ¶
Return the names of the available concrete embedder classes.
These live in :mod:splytters.embedders (the [embedders] extra). The
underlying model libraries (sentence-transformers, transformers, openai)
are imported lazily only when an embedder is constructed, so listing pulls
in no optional dependency.
Returns:
| Type | Description |
|---|---|
list[str]
|
Embedder class names, e.g. ``["TextEmbedder", "CLIPTextEmbedder", |
list[str]
|
"CLIPImageEmbedder", "OpenAIEmbedder"]``. |