Skip to content

API reference

The complete typed public surface. Everything below is re-exported from the top-level betula_cluster package.

Convenience functions

betula_cluster.fit_predict builtin

fit_predict(
    data: _FloatArray,
    n_clusters: int = 8,
    feature: _Feature = "diagonal",
    method: _Method = "gmm",
    threshold: float = 0.0,
    branching: int = 32,
    leaf_cap: int = 32,
    max_leaves: int = 2000,
    max_iter: int = 100,
    min_samples: int = 5,
    min_cluster_size: int = 5,
    seed: int = 0,
    distance: _Distance = "euclidean",
    absorb: _Absorb = "euclidean",
    chi2_p: float = 0.95,
    chi2_scale: float = 0.0,
    n_jobs: int = 1,
    normalize: bool = False,
    resolution: float = 1.0,
) -> NDArray[np.int64]

Cluster the rows of a 2-D float32 or float64 array; returns one int64 label per row (-1 = noise, produced only by method="hdbscan"). float32 input is clustered in f32 (half the memory, no upcast). With n_clusters=0 and method="gmm"/"gmm-full" the component count is selected automatically by BIC. absorb="chi2" switches the CF-tree's absorption to a mass-invariant Mahalanobis-χ² gate at level chi2_p with within-cluster variance chi2_scale (required for chi2; absorb="euclidean" is the default and unchanged).

betula_cluster.fit_predict_sparse

fit_predict_sparse(
    X: Any,
    n_clusters: int = 8,
    method: _Method = "kmeans",
    threshold: float = 0.0,
    max_leaves: int = 2048,
    max_iter: int = 100,
    seed: int = 0,
) -> NDArray[np.int64]

One-shot O(nnz) clustering of a scipy.sparse matrix.

Summarises rows into spherical micro-clusters touching only the non-zeros (a flat leader pass bounded by max_leaves), clusters those with a parametric head (kmeans / gmm / gmm-full / ward), and labels each row by its nearest micro-cluster. For very high-dimensional sparse data this avoids the O(d)-per-row cost of the dense path. It uses the expanded squared-distance form for speed and so does not carry the dense path's cancellation-free guarantee (accurate for sparse rows far from the dense centroid; see the library docs). Returns one int64 label per row.

betula_cluster.tune

tune(
    x: ndarray,
    n_clusters: int,
    *,
    space: dict[str, tuple] | None = None,
    y: ndarray | None = None,
    objective: str = "calinski_harabasz",
    n_trials: int = 30,
    sampler: str = "random",
    multi_objective: bool = False,
    seed: int = 0,
    **fixed: Any,
) -> TuneResult

Search betula's CF-representation knobs for the best clustering of x into n_clusters.

Parameters

x, n_clusters Data and the number of clusters to fit each trial with. space Search space {param: spec} where each spec is ("int_log", lo, hi) or ("cat", [values]). Defaults to sweeping max_leaves, feature and normalize. y Ground-truth labels; required only for objective="ari". objective "calinski_harabasz" (default, higher better), "davies_bouldin" (lower better), "dbcv" (density-based, higher better — use for the HDBSCAN-CF / DbStream density heads, where the convex metrics mislead), or "ari" (needs y). n_trials, seed Search budget and RNG seed. sampler "random" (NumPy, no extra deps) or "optuna" (needs betula-cluster[tune]): TPE for single-objective, NSGA-II for the multi_objective Pareto front. multi_objective If true, also return the (quality / memory=n_leaves / speed=fit-time) Pareto front. **fixed Extra Betula keyword arguments held constant across trials (e.g. method="gmm").

Returns

TuneResult best_params / best_score / trials (and pareto when multi_objective).

Estimator

betula_cluster.Betula

Streaming, scikit-learn-style BETULA estimator.

Parameters are validated lazily — when the engine is built at fit / partial_fit time — following the scikit-learn convention that __init__ only records its arguments verbatim.

threshold accepts a non-negative float (the CF absorption radius, 0.0 grows it from scratch) or "auto": a subsample pilot then estimates a warm-start threshold so the full fit starts near-converged instead of thrashing rebuilds up from zero. "auto" is dense-only.

n_rebuilds_ property

n_rebuilds_: int

How many times the CF-tree rebuilt under the leaf bound; high ⇒ thrashing.

threshold_ property

threshold_: float

Current CF-tree absorption threshold (grows as it rebuilds).

effective_max_leaves_ property

effective_max_leaves_: int

The max_leaves actually used: derived from memory_budget_mb if set, else configured.

microcluster_centers_ property

microcluster_centers_: NDArray[float64]

Leaf (microcluster) centroids — (n_microclusters, dim).

microcluster_weights_ property

microcluster_weights_: NDArray[float64]

Leaf effective point mass — (n_microclusters,).

microcluster_radii_ property

microcluster_radii_: NDArray[float64]

Leaf RMS radius — (n_microclusters,).

cluster_centers_ property

cluster_centers_: NDArray[float64]

Macro-cluster centroids — (n_clusters, dim); requires a finalized clustering.

cluster_radii_ property

cluster_radii_: NDArray[float64]

Macro-cluster RMS radius — (n_clusters,); requires a finalized clustering.

cluster_sizes_ property

cluster_sizes_: NDArray[float64]

Macro-cluster total point mass — (n_clusters,); requires a finalized clustering.

microcluster_proba_ property

microcluster_proba_: NDArray[float64]

Per-microcluster GMM soft responsibilities (n_microclusters, k). GMM heads only.

fit

fit(
    X: _FloatArray,
    y: Any = None,
    must_link: Any = None,
    cannot_link: Any = None,
) -> Betula

Fit the CF-tree on X and cluster it; returns self (scikit-learn style).

Parameters:

Name Type Description Default
X _FloatArray

dense float32/float64 array or a scipy.sparse CSR matrix (never densified).

required
y Any

ignored; present for scikit-learn API compatibility.

None
must_link Any

optional (m, 2) row-index pairs forced into the same cluster.

None
cannot_link Any

optional (m, 2) row-index pairs forced apart. Any constraint switches the run to semi-supervised COP-KMeans (method="kmeans", dense input only).

None

fit_predict

fit_predict(
    X: _FloatArray,
    y: Any = None,
    must_link: Any = None,
    cannot_link: Any = None,
) -> NDArray[np.int64]

Fit on X; return one int64 label per row (-1 = noise, HDBSCAN head).

Takes the same X / must_link / cannot_link as :meth:fit.

partial_fit

partial_fit(
    X: _FloatArray | None = None, y: Any = None
) -> Betula

Absorb a chunk; call with no argument to finalize the global clustering.

assign_microclusters

assign_microclusters(X: _FloatArray) -> NDArray[np.int64]

Nearest leaf index per row (matches microcluster_centers_ order).

outlier_scores

outlier_scores(X: _FloatArray) -> NDArray[np.float64]

Per-row distance to its assigned cluster centroid / that cluster's RMS radius.

summary

summary() -> dict[str, float]

A compact dict describing the dataset's structure (microclusters + macro clusters).

find_outliers

find_outliers(
    X: _FloatArray, top_k: int = 100
) -> NDArray[np.int64]

Row indices of the top_k most outlying points (highest score first).

sample_representatives

sample_representatives(
    X: _FloatArray, k: int = 5
) -> dict[int, NDArray[np.int64]]

For each cluster, the row indices of the k points nearest its centroid.

find_near_duplicates

find_near_duplicates(
    X: _FloatArray, radius: float
) -> list[NDArray[np.int64]]

Groups (row-index arrays) of points sharing a microcluster tighter than radius.

near_duplicate_pairs

near_duplicate_pairs(
    X: _FloatArray, threshold: float = 0.9
) -> NDArray[np.float64]

Scored near-duplicate row pairs by exact cosine similarity within each microcluster.

The CF-tree blocks rows into leaves in O(N); within each (small) leaf, exact pairwise cosine is computed and pairs scoring >= threshold are kept. The cost is ~O(N * leaf_size) -- the scalable counterpart to an O(N^2) all-pairs scan, and the scored complement to :meth:find_near_duplicates (which returns unscored groups). Recall is bounded by the blocking: a pair split across two leaves is missed; use a coarser tree (smaller max_leaves) to widen blocks and trade speed for recall. Returns an (m, 3) float64 array of [cos_sim, i, j] with i < j, sorted by similarity descending.

mapper

mapper(
    lens: _Lens = "density",
    resolution: int = 10,
    gain: float = 0.3,
    link_scale: float = 1.0,
    min_node_mass: float = 0.0,
    density_k: int = 5,
    coordinate: int = 0,
) -> MapperGraph

Build a Mapper topological-skeleton :class:MapperGraph over the fitted microclusters.

TDA Mapper specialised to BETULA: a lens filter ("density" | "radius" | "l2norm" | "coordinate" | "eccentricity") is covered by resolution bins overlapping by gain; microclusters in a bin are single-linked at link_scale × the bin's median nearest-neighbour gap; one node per (bin, component). It surfaces non-convex structure, branch points and bridges (topic leakage) over the M << N microclusters — an exploration tool, not a partition. Build the model first.

mapper_stability

mapper_stability(
    resolutions: Sequence[int] | None = None,
    **mapper_kwargs: Any,
) -> list[dict[str, int]]

Sweep Mapper resolution and report how the topology persists across scale.

Returns a list of dicts (one per resolution) with resolution, n_nodes, n_edges, n_branch_points, n_bridges, n_components (β₀, connected components) and n_loops (β₁ = edges − nodes + components, the number of independent cycles). Features constant across many resolutions are real structure; ones that flicker are binning artefacts — the Mapper analogue of a persistence diagram, without cross-scale node matching.

resolutions defaults to range(4, 30, 2); mapper_kwargs (lens, gain, link_scale …) pass straight through to :meth:mapper. Build the model first.

export_coreset

export_coreset() -> Coreset

The CF-tree leaves as a weighted-point :class:Coreset (centers, weights, radii) — a compact streaming summary of all data seen. Fit any weighted clustering / model on it rather than the raw points. Requires a built model.

predict_proba

predict_proba(X: _FloatArray) -> NDArray[np.float64]

Per-point soft assignment, shape (n, n_components).

The GMM heads return the true posterior responsibilities (routed via each point's microcluster). k-means / Ward / HDBSCAN return a heuristic softmax(−d²/2τ²) over the cluster centroids (τ = mean cluster radius) — a confidence proxy, not a calibrated posterior. Columns are component indices aligned with :meth:predict.

assignment_confidence

assignment_confidence(
    X: _FloatArray,
) -> NDArray[np.float64]

Per-point confidence in [0, 1] = the max soft-assignment probability (see :meth:predict_proba); low values flag boundary / ambiguous points.

diagnostics

diagnostics() -> dict[str, float]

A richer structural report than :meth:summary — compression, microcluster-radius percentiles, rebuild count, and (once finalized) cluster mass spread.

representatives

representatives(
    X: _FloatArray,
    cluster_id: int,
    method: _Repr = "medoid",
    k: int = 5,
) -> NDArray[np.int64]

Row indices of k representatives of cluster_id. method: medoid (nearest centroid), boundary (farthest in-cluster), outlier (highest outlier score), diverse (farthest-point sampling). Empty if the cluster has no predicted members.

cluster_profile

cluster_profile(cluster_id: int) -> dict[str, Any]

A JSON-able profile of a macro-cluster (size, radius, center, nearest clusters) — feed to an LLM to name it. Geometry only; no data pass needed.

snapshot

snapshot() -> dict[str, Any]

A JSON-able snapshot of the current cluster geometry (centers / sizes / radii) for drift monitoring across time. Requires a finalized clustering; compare two with :meth:compare_snapshots.

compare_snapshots staticmethod

compare_snapshots(
    before: dict[str, Any], after: dict[str, Any]
) -> dict[str, Any]

Drift report between two :meth:snapshot dicts. Each after cluster is matched to its nearest before centroid; reports the centroid shift (absolute and in after-radius units) and the mass ratio per match, plus the cluster counts and the worst shift. Both snapshots must come from finalized models with ≥ 1 cluster.

active_learning_batch

active_learning_batch(
    X: _FloatArray,
    n: int = 100,
    strategy: _Strategy = "uncertain",
) -> NDArray[np.int64]

Row indices of the n most informative points to review/label. strategy: uncertain (lowest :meth:assignment_confidence) or outlier (highest :meth:outlier_scores) — for human-in-the-loop curation / labeling.

Streaming

betula_cluster.DenStream

Streaming DenStream density clusterer (Cao et al., SDM 2006) over fading micro-clusters.

For evolving streams where old data should fade: feed chunks with :meth:partial_fit, then :meth:predict (which finalizes the offline clustering on first call) — or both at once with :meth:fit / :meth:fit_predict. eps is the micro-cluster radius (tune to the data scale), decay the fading rate λ, and beta × mu the promotion/pruning weight (must exceed 1). Spherical micro-clusters, float64; -1 labels are noise.

n_microclusters_ property

n_microclusters_: int

Number of potential (cluster-eligible) micro-clusters.

microcluster_weights_ property

microcluster_weights_: NDArray[float64]

Potential micro-cluster weights, faded to the current stream time.

partial_fit

partial_fit(X: _FloatArray, y: Any = None) -> DenStream

Stream a chunk of points into the fading micro-clusters.

cluster

cluster() -> DenStream

Run the offline step (label the potential micro-clusters) over what has streamed.

predict

predict(X: _FloatArray) -> NDArray[np.int64]

Label rows by their nearest potential micro-cluster (-1 = noise); finalizes the offline clustering first if points have streamed since the last :meth:cluster.

betula_cluster.DbStream

Streaming DBSTREAM density clusterer (Hahsler & Bolaños, 2016) over fading micros.

Like :class:DenStream it fades old data and marks -1 as noise, but it connects micro-clusters by shared density — the mass of points within radius r of both — rather than by mere proximity. This recovers arbitrarily-shaped clusters (chained overlapping micro-clusters) and, unlike a distance rule, keeps two close-but-disconnected dense regions apart (an empty gap means zero shared density). r is the radius, decay the fading rate, alpha the shared-density bridge threshold (a pair links when their overlap mass exceeds alpha * min_weight), and min_weight the weight a micro-cluster needs to form a cluster.

microcluster_weights_ property

microcluster_weights_: NDArray[float64]

Micro-cluster weights, faded to the current stream time.

partial_fit

partial_fit(X: _FloatArray, y: Any = None) -> DbStream

Stream a chunk of points into the fading micro-clusters.

cluster

cluster() -> DbStream

Run the offline step (label micro-clusters via the shared-density graph).

predict

predict(X: _FloatArray) -> NDArray[np.int64]

Label rows by their nearest micro-cluster within r (-1 = noise); finalizes the offline clustering first if points have streamed since the last :meth:cluster.

Mixed-type data

betula_cluster.KPrototypes

k-prototypes clustering of mixed numeric + categorical data (Huang, 1997).

categorical lists the integer-coded categorical column indices of X; the rest are numeric. Distance is ||Δnum||² + gamma · (categorical mismatch); gamma defaults to half the mean numeric standard deviation (Huang's heuristic) when None. Rows are summarised into bounded mixed micro-clusters (a flat leader pass capped at max_leaves) before clustering, so memory stays bounded. Both numeric and categorical columns are required; float64.

cluster_centroids_ property

cluster_centroids_: NDArray[float64]

Numeric cluster centroids — (n_clusters, n_numeric).

cluster_modes_ property

cluster_modes_: NDArray[int64]

Categorical cluster modes — (n_clusters, n_categorical) integer codes.

predict

predict(X: _FloatArray) -> NDArray[np.int64]

Label rows by their nearest mixed micro-cluster.

Coresets

betula_cluster.Coreset dataclass

A weighted-point coreset: the CF-tree leaf microclusters as (centers, weights, radii).

Each row is one microcluster — a numerically stable summary of the points absorbed into it. The set is bounded by max_leaves and built in a single streaming pass, so fitting a weighted clustering / classifier on it is competitive with fitting on the full data at a fraction of the cost.

n_points property

n_points: float

Total mass (≈ number of points summarized).

Streaming sketches

betula_cluster.KllSketch

Streaming KLL quantile sketch (rank-error). Standalone betula-sketch primitive.

__doc__ class-attribute

__doc__ = "Streaming **KLL** quantile sketch (rank-error). Standalone `betula-sketch` primitive."

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'betula_cluster._core'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

count property

count: int

Total number of values added.

max property

max: float

Largest value seen (NaN if empty).

min property

min: float

Smallest value seen (NaN if empty).

__new__ builtin

__new__(*args, **kwargs) -> KllSketch

Create and return a new object. See help(type) for accurate signature.

merge method descriptor

merge(other: KllSketch) -> None

Merge another KLL sketch into this one.

quantile method descriptor

quantile(q: float) -> float

Estimated q-quantile (q ∈ [0, 1]).

quantiles method descriptor

quantiles(qs: NDArray[float64]) -> NDArray[np.float64]

Estimated quantiles for an array of q values.

rank method descriptor

rank(value: float) -> int

Estimated number of values ≤ value.

update method descriptor

update(x: float) -> None

Add one value.

update_many method descriptor

update_many(data: NDArray[float64]) -> None

Add every value of a 1-D array.

betula_cluster.DdSketch

Streaming DDSketch quantile sketch (relative-error). Standalone betula-sketch primitive.

__doc__ class-attribute

__doc__ = "Streaming **DDSketch** quantile sketch (relative-error). Standalone `betula-sketch` primitive."

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'betula_cluster._core'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

alpha property

alpha: float

Relative accuracy α (smaller ⇒ tighter quantiles, more buckets).

count property

count: int

Total number of values added.

max property

max: float

Largest value seen (NaN if empty).

min property

min: float

Smallest value seen (NaN if empty).

__new__ builtin

__new__(*args, **kwargs) -> DdSketch

Create and return a new object. See help(type) for accurate signature.

merge method descriptor

merge(other: DdSketch) -> None

Merge another DDSketch into this one.

quantile method descriptor

quantile(q: float) -> float

Estimated q-quantile (q ∈ [0, 1]).

quantiles method descriptor

quantiles(qs: NDArray[float64]) -> NDArray[np.float64]

Estimated quantiles for an array of q values.

update method descriptor

update(x: float) -> None

Add one value.

update_many method descriptor

update_many(data: NDArray[float64]) -> None

Add every value of a 1-D array.

Result types

betula_cluster.MapperGraph dataclass

A Mapper topological-skeleton graph over a fitted model's leaf microclusters.

Each node is a connected group of microclusters inside one cover bin; edges link nodes that share microclusters (from the cover overlap). branch_points are nodes where the shape splits (degree ≥ 3); bridges index the edges whose removal would disconnect the graph — thin links between otherwise separate regions (e.g. leakage between topics in an embedding). edge_overlap is a per-edge Bhattacharyya coefficient in (0, 1] from the two nodes' pooled diagonal-Gaussian summaries: a bridge across a sparse neck scores lower than an edge inside one dense blob, so links are weighted by distributional overlap, not a bare shared count.

persistence

persistence(
    filtration: str = "overlap", finite_only: bool = False
) -> NDArray[np.float64]

The nerve's 0-D persistence diagram as (k, 2) births/deaths, sorted by persistence.

filtration="overlap" (default) filters by the 1 − edge_overlap gap — a finite bar's death is the Bhattacharyya depth of a bottleneck, a ranked upgrade of the boolean bridges; "lens" is the lens sublevel diagram (flares of the shape). Essential (component) classes carry np.inf in the death column; finite_only=True drops them.

to_networkx

to_networkx() -> Any

Build a networkx.Graph (requires networkx); nodes carry mass/bin/lens/centroid, edges carry weight and a boolean bridge flag.

betula_cluster.TuneResult dataclass

The outcome of :func:tune: the best single configuration, every trial, and — for multi_objective=True — the non-dominated (quality / memory / speed) Pareto front.