Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Clustering

Unsupervised clustering estimators. Live in datarust::cluster. All implement the Clusterer trait — the unsupervised counterpart to Predictor. fit takes only X (no target y); predict returns cluster indices as Vec<usize>.

KMeans

k-means clustering via Lloyd’s algorithm with k-means++ initialization. Partitions n observations into k clusters by minimizing within-cluster sum of squares (inertia). Each iteration assigns every point to its nearest centroid, then recomputes centroids as the mean of their members.

#![allow(unused)]
fn main() {
use datarust::cluster::{KMeans, KMeansInit};
use datarust::traits::Clusterer;

let mut km = KMeans::new()
    .with_n_clusters(3)                         // default 8
    .with_init(KMeansInit::KMeansPlusPlus)      // default; or KMeansInit::Random
    .with_n_init(10)                            // restarts, keep best (default 10)
    .with_max_iter(300)                         // default 300
    .with_tol(1e-4)                             // default 1e-4
    .with_random_state(42);                     // deterministic seed

let labels = km.fit_predict(&x)?;               // Vec<usize>, one cluster per row
let centers = km.cluster_centers();             // &[Vec<f64>], one centroid per cluster
let inertia = km.inertia();                     // f64, sum of squared distances
let iters = km.n_iter();                        // Lloyd's iterations of best run

let new_labels = km.predict(&new_x)?;           // assign new points to nearest centroid
}

n_clusters, n_init, and max_iter must be positive. tol must be finite and non-negative. Invalid values return InvalidConfig before clustering; the same checks apply immediately when values are supplied through Params.

Initialization strategies (KMeansInit):

  • KMeansPlusPlus (default) — first centroid is uniform random; each subsequent centroid is sampled with probability proportional to its squared distance from the nearest already-chosen centroid. Produces well-spread initial centroids and is the scikit-learn default.
  • Random — choose n_clusters distinct points uniformly at random from the data.

How it works:

  1. Initialize k centroids using the chosen strategy.
  2. Assignment step — each point is assigned to its nearest centroid (lowest squared Euclidean distance).
  3. Update step — each centroid is recomputed as the mean of its members. Empty clusters keep their previous centroid.
  4. Repeat until centroid movement drops below tol or max_iter is reached.
  5. Steps 1–4 run n_init times with different seeds; the lowest-inertia result is kept.

Reproducibility: with_random_state(seed) makes results fully deterministic — the same seed yields identical labels, centroids, and inertia. KMeans uses the crate’s internal xorshift64 PRNG, preserving the zero-dependency ethos (no rand crate).

Serialization: KMeans derives Serialize/Deserialize under the serde feature — fitted centroids, labels, and inertia round-trip through JSON, and the restored model serves predictions without refitting.

Choosing a clustering algorithm

GoalEstimator
Spherical, equally-sized blobsKMeans
Arbitrary-shape clusters(DBSCAN — planned, see Roadmap)
Hierarchical structure(AgglomerativeClustering — planned)

Evaluating clusters

Without ground-truth labels, the silhouette score assesses clustering quality from the data alone:

#![allow(unused)]
fn main() {
use datarust::cluster::metrics::silhouette_score;

let s = silhouette_score(&x, &labels)?;  // f64 in [-1, 1]
}

For each sample, the silhouette coefficient is (b − a) / max(a, b) where a is the mean intra-cluster distance and b is the mean distance to the nearest other cluster. Values near 1 indicate well-separated clusters; near 0 indicate overlapping clusters. The inertia() (within-cluster sum of squares, lower is better) is also useful for comparing runs with different n_clusters or random_state values.