Permutations define the difference between two splits. This package writes those permutations down as a graph, breaks it into cycles and leftovers (single entities that move from one fold to another). Apply the first k operations, and an intermediate split is achieved. Apply all of them, and one lands exactly on the second end split. Choosing k to hit a target leakage is what the package does for you.
A "split" is a directory with one file per fold (both .csv or .tsv formats):
my_split/
train.csv
val.csv
test.csv
Sweep the molecule axis from one ligand split to another:
import continuous_datasail as cds
summary = cds.continuous_split_1D(
start_dir="splits/I1L",
end_dir="splits/S1L",
out_dir="out/f_axis",
datatype="M", id_col="molecule ID", value_col="SMILES",
percentages=[0, 25, 50, 75, 100],
similarity="ecfp",
folds=["train", "val", "test"],
ranking_method="random", ranking_seed=0,
plots=False,
)You get out/f_axis/pct_0/, pct_25/, … each holding train.csv, val.csv, test.csv,
plus leakage_summary.csv and a leakage_vs_k.png curve. Every input row appears in
exactly one fold of every percentage.
Interpolate proteins and molecules at the same time, then keep the interactions where the two axes agree:
summary = cds.continuous_split_2D(
e_start_dir="splits/I1P", e_end_dir="splits/S1P", # protein axis
f_start_dir="splits/I1L", f_end_dir="splits/S1L", # molecule axis
out_dir="out/2d",
e_datatype="P", e_id_col="Uniprot ID", e_value_col="Sequence",
f_datatype="M", f_id_col="molecule ID", f_value_col="SMILES",
percentages=[0, 25, 50, 75, 100],
e_similarity="diamond", f_similarity="ecfp",
e_num_clusters=1500,
ranking_method="random", ranking_seed=0,
)An interaction is kept only when its protein and its molecule land in the same fold, so
the result is a true two-dimensional split — but you will lose rows. The function reports
coverage (the fraction kept) at every percentage. Expect somewhere between 50 % and 80 %.
Sweeps one axis, proteins or molecules, from one split to another and writes a full set
of fold files for each requested percentage. Every input row lands in exactly one fold at
every step, so there is no data loss anywhere along the sweep. Returns a summary
DataFrame and writes it to leakage_summary.csv.
| argument | what it does |
|---|---|
start_dir, end_dir |
the two splits to interpolate between, as directories of fold files |
out_dir |
where the results go, one pct_<n>/ subdirectory per percentage |
datatype |
"P" for proteins, "M" for molecules |
id_col, value_col |
which columns hold the entity id and the format similarity is computed on |
percentages |
the points you want along the sweep, e.g. [0, 25, 50, 75, 100] |
similarity |
how similarity is measured: "ecfp" for molecules, "diamond" for proteins |
folds |
fold names, defaults to ["train", "val", "test"], must match your filenames |
plots |
True writes a t-SNE picture per percentage. Slow on big datasets |
curve |
True writes leakage_vs_k.png, the leakage against every value of k |
weights |
how much each entity counts for |
num_clusters |
group entities into this many clusters before interpolating |
ranking_method |
"leakage" puts the highest-impact moves first, so leakage changes fast at the start and slowly later. "random" shuffles them, which gives a straighter, more even sweep |
ranking_seed |
seed for "random". Set it, or every run gives a different sweep |
cache_path |
where to pickle the similarity matrix. When set, second run skips the most expensive part of similarity matrix computing |
k_method |
"leakage" searches for the k whose leakage matches your target (search on the y-axis). "linear" just takes k = n × pct / 100 (search on the x-axis). The second method is faster. |
dataset |
a similarity dataset which eas already built, to reuse it across several runs in one process instead of writing it to disk |
Sweeps both axes at once. It runs continuous_split_1D on the protein axis and on the
molecule axis separately, then keeps the interactions whose two axes agree on a fold so
every output split is two-dimensional, at the cost of dropping the rows where
they disagree. Before doing any of that, it checks that all four inputs are real
entity-level splits covering the same interactions, and refuses to run if they aren't.
| argument | what it does |
|---|---|
e_start_dir, e_end_dir |
the two endpoints for the protein axis |
f_start_dir, f_end_dir |
the two endpoints for the molecule axis |
out_dir |
resulting directory, plus an e_axis/ and f_axis/ subdirectory holding each axis's own interpolation |
e_datatype, e_id_col, e_value_col |
the protein side: "P", and the two column names |
f_datatype, f_id_col, f_value_col |
the molecule side: "M", and the two column names |
percentages |
the points you want along the sweep, e.g. [0, 25, 50, 75, 100] |
e_similarity, f_similarity |
usually "diamond" and "ecfp" |
folds |
fold names, defaults to ["train", "val", "test"] |
e_num_clusters, f_num_clusters |
cluster each axis before interpolating |
ranking_method, ranking_seed, k_method, cache_path |
as in continuous_split_1D, passed straight through to both axes |
Returns one number: the share of all similarity in the dataset that sits between different folds rather than inside one, following the structure of datasail's own leakage function.
The same quantity, but reported separately for each pair of folds — train–test, train–val, val–test.
plot_leakage_curve(start_split, end_split, ranked_operations, names, metric, out_path, weights=None, max_points=300)
Draws leakage against the number of operations applied and saves it to out_path, with
dashed lines marking the two endpoints. max_points caps how many points get
computed, since each one costs a full pass over the similarity matrix.
Checks that every entity in a split lives in exactly one fold, and returns how many are in
each. It compares on value_col rather than the id on purpose. With strict=False
it warns instead of raising.
Turns the difference between two splits into a directed graph, with one edge per entity
that changes fold. Raises an error if either split is missing an entity from names.
Breaks that graph into cycles and leftovers, and returns both. Cycles rotate folds among a
group of entities and leave the fold sizes alone, whereas leftovers are single moves that change
them. If leftover comes back empty, two endpoints have matching fold sizes. This decomposition is based on
Hierholzer's algorithm for Eulerian circuits, which is deterministic yet finds only one decomposition among many.
Orders the moves by how much each one changes leakage, smallest first. The deltas are all measured against the starting split.
Shuffles the moves instead of ranking them. Set seed for a reproducible sweep — without
it, every run walks a different path from start to end.
Applies a single move to a split and returns the new one — rotating folds around a cycle, or sending one leftover entity to its destination fold.
Applies the first k moves in order and returns the split you end up with. Each entity is
touched by at most one move, so the order doesn't affect where you finish.
Finds the k whose leakage sits closest to percentage of the way from the first split's
leakage to the second's, and returns a SplitResult with the split, the k, and both the
requested and achieved percentages. It searches by halving the range, which assumes leakage
moves steadily with k.
Same signature, but skips the search and takes k = n × percentage / 100 directly. Much
cheaper, and roughly as good when the moves were ordered randomly.
A small record holding the resulting split, the k used, the starting, ending, and
achieved leakage, and the requested versus achieved percentage.
Asks DataSAIL for the all-against-all similarity matrix over your entities. Give it a
cache_path and it pickles the result, so the next run loads in seconds instead of
recomputing.
cluster_within_groups(names, metric, label_start, label_end, total_clusters=50, linkage="average", entity_weights=None)
Reduces a large set of entities to a smaller set of clusters so the interpolation stays
fast. It clusters within groups of entities that share the same start fold and end fold,
which guarantees no cluster straddles a fold boundary. Pass entity_weights so a cluster's
weight is the sum of its members' interaction counts rather than a plain head count.
Runs DataSAIL's agglomerative clustering on a similarity matrix you already have, and returns the cluster names, the entity→cluster mapping, and the cluster-level matrix.
Looks for <fold>.csv then <fold>.tsv in a directory and returns the path and the right
separator, or (None, None) if neither exists. This is the single place file format is
decided; .csv wins if both are present.
Reads a split directory and returns two dictionaries: each entity's value (its sequence or
SMILES) and its fold. When an entity appears in more than one fold, it takes the most
common one. Running assert_entity_pure first is recommended.