Skip to main content

Partition-then-fit machine learning: cluster your data into datalets, run a model tournament per datalet, route every prediction to the right specialist. scikit-learn compatible.

Project description

datalets ๐Ÿงฉ

Partition-then-fit machine learning, scikit-learn compatible.

Many tiles, one picture. Cluster your data into datalets, run a model tournament per datalet, route every prediction to the right specialist.

Real-world datasets rarely follow one pattern. A single global model fits the average of all the patterns in your data โ€” datalets instead discovers the subpopulations (unsupervised, on X only), crowns the best classifier for each one, and routes every prediction to the specialist that owns it.

            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   X โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚  clusterer   โ”‚โ”€โ”€โ”€โ”€โ–บ datalet 1 โ”€โ”€โ–บ tournament โ”€โ”€โ–บ ๐Ÿ† XGBoost      (t=0.31)
            โ”‚  (any of 12) โ”‚โ”€โ”€โ”€โ”€โ–บ datalet 2 โ”€โ”€โ–บ tournament โ”€โ”€โ–บ ๐Ÿ† LogReg       (t=0.55)
            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”€โ”€โ”€โ”€โ–บ datalet 3 โ”€โ”€โ–บ tournament โ”€โ”€โ–บ ๐Ÿ† RandomForest (t=0.48)
                                  too small โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ global fallback

Install

pip install datalets                # core (scikit-learn models only)
pip install "datalets[boosters]"    # + XGBoost, LightGBM, CatBoost
pip install -e ".[dev]"             # from a clone, for development

Named after the data slices it carves โ€” and pip install datalets, import datalets: one name everywhere.

Quickstart

from datalets import PartitionedClassifier

model = PartitionedClassifier(
    clusterer="auto",           # searches kmeans/gmm x k (up to 12) + hdbscan,
                                #   scored by predictive utility โ€” fully automatic
    candidates="default",       # or "fast", "all", or your own list of classifiers
    class_weight="balanced",    # automated imbalance handling (weights, no resampling)
    routing="soft",             # blend specialists by P(cluster | x)
)
model.fit(X_train, y_train)     # no cluster tags needed โ€” the partition is discovered

model.predict(X_test)          # routed to each point's specialist
model.predict_cluster(X_test)  # which datalet does each point belong to?
print(model.auto_search_)      # every partition tried, scored + silhouette/BIC
print(model.report())          # per-cluster: size, base rate, champion, lift
print(model.summary())         # verdict: does the partition earn its keep?
print(model.tree())            # the finish tree: the whole fit, one picture

Prefer manual control? clusterer="kmeans", n_clusters=6 (or "gmm", "hdbscan", โ€ฆ or any clusterer instance) pins the partition yourself.

fit narrates itself: numbered stages, a live progress bar over every (tournament, candidate) pair in interactive sessions, and the finish tree at the end โ€” how the data was carved up, the clustering quality (sampled silhouette), which champion serves each segment at what threshold, which algorithms dominated, and the verdict:

datalets ยท PartitionedClassifier ยท fit in 3.4s
โ”œโ”€ data       1,200 rows ร— 5 features ยท 2 classes ยท positives 42.3%
โ”œโ”€ clustering kmeans โ†’ 3 clusters ยท silhouette 0.29 ยท sizes 398โ€“402 (median 400)
โ”œโ”€ tournament 4 slices ร— 2 candidates ยท metric f1 ยท 8 CV races
โ”œโ”€ segments
โ”‚  โ”œโ”€ cluster 0 ยท n=402 (34%) ยท pos 45.0% โ†’ โ˜… logistic_regression ยท f1 0.850 ยท thr 0.47 ยท lift +0.018
โ”‚  โ”œโ”€ cluster 1 ยท n=400 (33%) ยท pos 45.8% โ†’ โ˜… decision_tree ยท f1 0.667 ยท thr 0.02 ยท lift +0.002
โ”‚  โ””โ”€ cluster 2 ยท n=398 (33%) ยท pos 36.2% โ†’ โ˜… logistic_regression ยท f1 0.923 ยท thr 0.43 ยท lift +0.085
โ”œโ”€ fallback   decision_tree ยท f1 0.772 ยท safety net only
โ”œโ”€ champions  logistic_regression ร—2 ยท decision_tree ร—1
โ””โ”€ verdict    โœ“ partition earns its keep ยท routed OOF f1 0.803 vs global 0.772 ยท lift +0.0311

Non-interactive runs (logs, CI) get a few plain stage lines instead; progress=False or DATALETS_QUIET=1 silences everything.

What you get

  • Clusterer zoo โ€” all 12 scikit-learn clustering families by name (kmeans, minibatch_kmeans, bisecting_kmeans, gmm, bayesian_gmm, birch, agglomerative, spectral, hdbscan, dbscan, meanshift, optics), or bring your own instance.
  • Universal routing โ€” clusterers that cannot label new points (Agglomerative, Spectral, DBSCAN, HDBSCAN, OPTICS) are made inductive with a KNN gate. DBSCAN/HDBSCAN noise points are served by the global model.
  • Model tournament per cluster โ€” every candidate is scored with stratified out-of-fold CV inside the cluster; a one-standard-error rule breaks ties toward the simpler model. Candidate zoo spans scikit-learn's classifiers plus XGBoost / LightGBM / CatBoost when installed.
  • Per-cluster decision thresholds โ€” clusters with different positive rates want different cutoffs; datalets tunes each cluster's threshold for your metric (F1 by default). This alone often lifts F1 on heterogeneous data.
  • Imbalance handled, two automated modes โ€” thresholds alone are the F1-first mode; class_weight="balanced" adds inverse-frequency weights through fitting, thresholds and scoring (no resampling) for the recall-first mode: on a 0.4%-positive benchmark it catches 97% of positives with better ranking (ROC-AUC 0.999) than tuned boosters.
  • Soft routing โ€” predictions blend all specialists by P(cluster | x), so nothing jumps discontinuously at cluster boundaries. routing="hard" if you want one specialist per point.
  • Global safety net โ€” a tournament-selected global model serves clusters that are too small, too class-starved, or labelled as noise.
  • The honesty report โ€” report() shows, per cluster, whether the specialist actually beats the global model on the same rows, and summary() gives a verdict. If the partition doesn't earn its keep, datalets says so instead of letting you ship it.
  • A fit you can watch โ€” stage-by-stage narration with a live progress bar, and tree(): the post-fit finish tree showing the partition, the clustering quality, every segment's champion and the final verdict at a glance.

Is the partition real? Prove it.

The in-fit report reuses training folds, so treat it as a screen. For a leakage-free comparison, everything (clustering, tournaments, thresholds) is refit inside every fold:

from datalets import honest_cv
print(honest_cv(X, y, datalets=PartitionedClassifier(n_clusters=6, random_state=0)))
#                          f1_mean  f1_std  roc_auc_mean  ...
# datalets                    0.912    0.011      0.965
# tournament_global         0.887    0.014      0.951
# hist_gradient_boosting    0.879    0.012      0.949

TournamentClassifier (the model-selection engine without clustering) is also exported โ€” it is both the library's global fallback and the honest single-model baseline.

Regression too

PartitionedRegressor is the continuous-target twin: same clusterer zoo, same per-datalet tournaments (over a regressor zoo: Ridge/Lasso, KNN, forests, boosters, SVR, MLP + XGBoost/LightGBM/CatBoost), same soft/hard routing and honesty report โ€” no thresholds, since there is nothing to threshold. selection_metric is one of "r2" (default), "mse", "rmse", "mae".

from datalets import PartitionedRegressor, honest_cv_regression

reg = PartitionedRegressor(n_clusters=6, candidates="default").fit(X_train, y_train)
print(reg.report())            # per-cluster: champion, cv_score, lift
print(honest_cv_regression(X, y, datalets=reg))

When does datalets help?

Partition-then-fit wins when your data genuinely contains subpopulations with different Xโ†’y rules โ€” customer segments, operating regimes, sensor types, geographies. It will not beat a tuned gradient booster on homogeneous data (boosters already partition internally); the report will tell you when that is the case. What you always keep, even at parity: interpretable segments, one readable specialist per segment, and per-segment thresholds and diagnostics.

sklearn-compatible, for real

PartitionedClassifier and TournamentClassifier pass scikit-learn's check_estimator suite (with the same single documented exemption as sklearn's own TunedThresholdClassifierCV: tuned thresholds mean predict is not argmax(predict_proba)). Pipelines, GridSearchCV, cross_val_score, cloning and pickling all work:

GridSearchCV(
    PartitionedClassifier(),
    {"n_clusters": [4, 6, 8], "clusterer": ["kmeans", "gmm"]},
    scoring="f1",
)

Lineage

datalets stands on a 45-year research line: clusterwise regression (Spรคth 1979), mixtures of experts (Jacobs, Jordan, Nowlan & Hinton 1991), finite mixtures of regressions (DeSarbo & Cron 1988; R's flexmix), local learning (Bottou & Vapnik 1992), and the cluster-then-predict practitioner pattern. See examples/demo.py for a dataset where the approach provably shines โ€” and the report that tells you when it doesn't.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

datalets-0.5.1.tar.gz (59.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

datalets-0.5.1-py3-none-any.whl (49.2 kB view details)

Uploaded Python 3

File details

Details for the file datalets-0.5.1.tar.gz.

File metadata

  • Download URL: datalets-0.5.1.tar.gz
  • Upload date:
  • Size: 59.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for datalets-0.5.1.tar.gz
Algorithm Hash digest
SHA256 a8ceefba7bc557b9a75acd099146fe17767a71ba2a0f99f6cdbf538124442ec4
MD5 9ebe5bfd8bf490cd32a53a34921a0635
BLAKE2b-256 7844424c7221b7a43a20e66f16b7e9a8fb70ba058ff3af2c1fb3fdb6f29e8c8f

See more details on using hashes here.

Provenance

The following attestation bundles were made for datalets-0.5.1.tar.gz:

Publisher: publish.yml on nashit8421/datalets

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file datalets-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: datalets-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 49.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for datalets-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fdde39bf8d2507c5b17278c81dc61aff0f51bdfb19a2076972093f50d4e48330
MD5 ef94243f979f0c2f13ee958d30d2b8d3
BLAKE2b-256 a2f4112b9b3efdd291c3cb69f9e7c1568839174acfaa2a602aab51c150fc0eb7

See more details on using hashes here.

Provenance

The following attestation bundles were made for datalets-0.5.1-py3-none-any.whl:

Publisher: publish.yml on nashit8421/datalets

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page