Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

nanoom

Multimodal-multitask-balanced dataset splitting: cluster-based separation and task-stratification/balancing across splits.

Design rationale is in ARCHITECTURE.md, the task list in DEVELOPMENT.md, setup and conventions in CONTRIBUTING.md.

Install

pip install nanoom

Core dependencies: polars, pulp, and scikit-learn.

Optional extras:

  • nanoom[chem] - rdkit/bblean-based clustering methods for molecular data
  • nanoom[tutorial] - marimo, for the notebooks in examples/
  • nanoom[plotting] - reserved for future split-quality visualization (not yet implemented)

Splitting

nanoom.split takes a dataframe with a pre-computed cluster column (see Clustering below) and returns a leakage-safe, task-balanced train/val/test assignment.

import nanoom

# balances by exactly solving a linear program (Tricarico et al., ported from
# https://github.com/sohviluukkonen/gbmt-splits); task columns may be a mix of
# regression, classification, and string columns, balanced jointly
out = nanoom.split(
    df,
    y_cols=["pchembl_value_mean", "target_id"],
    cluster_col="cluster",
    n_splits=3,
    method="tricarico",
)

# or: a lighter/faster fallback via sklearn's StratifiedGroupKFold
out = nanoom.split(
    df,
    y_cols="ic50",
    cluster_col="cluster",
    n_splits=3,
    method="sklearn",
)

Both methods return df with a split column added, holding 0..n_splits-1 per row (rename it with split_col=). The cluster column stays in the frame, so the per-cluster assignment is out.group_by("cluster").agg(pl.col("split").first()).

Splitting without clusters

cluster_col is optional. Passing cluster_col=None splits rows directly with no leakage constraint — each row becomes its own cluster, so the LP balances rows and sklearn falls back to StratifiedKFold:

out = nanoom.split(df, y_cols="ic50", n_splits=3, method="tricarico")

A cluster column of row indices is added so grouping and the audit functions below work the same in both modes. Note that the LP carries one binary variable per cluster per split, so cluster-free mode on a large frame gets slow; nanoom warns past 1000 clusters and suggests relative_gap for a fast near-optimal solve.

For regression task columns, method="tricarico" bins values before balancing (binning_approach=, default "qcut"): "qcut" targets roughly equal n_datapoints per bin; "gbmt_splits" instead bins the distinct values (matching gbmt-splits' behaviour). for repeated values this can skew row counts across bins. Neither option fully resolves a very-repeated value, since those rows can't be split across a bin boundary either way.

Clustering

nanoom.cluster produces the cluster_col that split() expects, decoupled from splitting itself so you can bring your own cluster assignment instead if you prefer.

clusters = nanoom.cluster(descriptors, method="kmeans", n_clusters=20)

Supports kmeans, dbscan, hdbscan, random, and hash_dummy out of the box; sphere_exclusion, bitbirch, maxmin, and leader_picker additionally require nanoom[chem]. An auto mode that adaptively tunes cluster granularity to trade off leakage-avoidance against task-balance quality is planned for a later iteration.

Auditing a split

nanoom.eval answers "is my split actually leakage-free and balanced?":

from nanoom.eval import check_no_group_overlap, check_distribution_y_similar, min_distances_splits

check_no_group_overlap(out["cluster"], out["split"])   # raises if any cluster spans >1 split
check_distribution_y_similar(out["ic50"], out["split"])  # raises if a split's y-mean drifts >10%
min_distances_splits(descriptors, out["split"], metric="euclidean")  # inter/intra-split distance stats

All three take per-row arrays and raise on a length mismatch rather than silently scoring the wrong subset.

Metadata

Release files for nanoom 0.1.0a20260815

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nanoom 0.1.0a20260815
File Size Uploaded Provenance Actions
nanoom-0.1.0a20260815.tar.gz 28.3 kB Without provenance Details for nanoom-0.1.0a20260815.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for nanoom 0.1.0a20260815
File Interpreter ABI Platform Actions
nanoom-0.1.0a20260815-py3-none-any.whl Python 3 none any Details for nanoom-0.1.0a20260815-py3-none-any.whl

Total release size: 58.3 kB

Release files / nanoom-0.1.0a20260815.tar.gz

Download URL nanoom-0.1.0a20260815.tar.gz
Size 28.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d7f3a8391d2342d5c5607c0d03a8b9c875692f4c23e28fa8ee90605754c99c7b
BLAKE2b-256 checksum
How to use checksums
b84aaa4d6c5943e6e2a0305b34d73a93c7cdec11c3d36fa90c29cdc04a95c04f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / nanoom-0.1.0a20260815-py3-none-any.whl

Download URL nanoom-0.1.0a20260815-py3-none-any.whl
Size 30.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c59911f7b66124b500154e1483dfcc84879cb0c9147c0c0fcea7e888a433c1ad
BLAKE2b-256 checksum
How to use checksums
36641be9542dab75017b0796673b6f5fda9d8ffc9ef1f05a380bc4e4199bc4e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for nanoom 0.1.0a20260815

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page