Cluster
Production-grade clustering for mixed numerical and categorical tabular data. Supports K-Means, K-Modes, K-Prototypes, DBSCAN, and GMM with data-driven feature weighting, sample size estimation, and comprehensive evaluation metrics.
Core
bitbullet.cluster.core.base.BaseClusterer
Bases: ABC
Abstract base class for all clusterers.
This implements the Strategy pattern, allowing clusterers to be swapped at runtime while maintaining a consistent interface.
Design Principles: - Immutability: Config is frozen after creation - Explicit state: Fitted parameters are clearly separated - Type safety: Full type hints with runtime validation - Performance: Designed for vectorization and parallel execution
cluster_centers_
property
Get cluster centers (scikit-learn compatible).
config
property
Get the immutable configuration.
is_fitted
property
Check if the clusterer has been fitted.
labels_
property
Get cluster labels (scikit-learn compatible).
n_clusters_
property
Get number of clusters found (scikit-learn compatible).
state
property
Get the current state.
__init__(config)
Initialize clusterer with configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ClusterConfig
|
Immutable configuration for this clusterer |
required |
__repr__()
String representation.
fit(data, verbose=False)
Fit the clusterer to data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Input data |
required |
verbose
|
bool
|
Whether to print progress messages |
False
|
Returns:
| Type | Description |
|---|---|
BaseClusterer
|
Self for method chaining |
Raises:
| Type | Description |
|---|---|
ValueError
|
If data is invalid |
RuntimeError
|
If already fitted |
fit_predict(data, verbose=False)
Fit the clusterer and return cluster labels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Input data |
required |
verbose
|
bool
|
Whether to print progress messages |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Cluster labels |
get_params()
Get parameters (scikit-learn compatible).
Returns:
| Type | Description |
|---|---|
Dict[str, Any]
|
Parameter dictionary |
predict(data)
Predict cluster labels for new data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Input data to assign to clusters |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Cluster labels |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
ValueError
|
If data is incompatible |
set_params(**params)
Set parameters (scikit-learn compatible).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**params
|
Any
|
Parameters to set |
{}
|
Returns:
| Type | Description |
|---|---|
BaseClusterer
|
Self for method chaining |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If already fitted |
bitbullet.cluster.core.base.ClusterConfig
Bases: BaseModel
Configuration for a clustering algorithm.
This uses Pydantic for validation and serialization, ensuring type safety at runtime.
model_dump_json(**kwargs)
Override to handle non-serializable types.
bitbullet.cluster.core.base.ClusterState
Bases: BaseModel
Encapsulates the fitted state of a clusterer.
This allows perfect reproducibility in production by storing all parameters learned during fitting.
Algorithms — Partitional
bitbullet.cluster.algorithms.partitional.kmeans.KMeansClusterer
Bases: BaseClusterer
K-Means clustering for numerical data only.
Features: - Standard K-Means or MiniBatch variant for large datasets - Multiple initialization strategies - Automatic elbow method support - Production-ready centroid saving/loading
Note
K-Means only works with numerical features. For mixed data, use K-Prototypes instead.
get_centroids()
Get cluster centroids as DataFrame.
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with centroids (means for all features) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_inertia()
Get the sum of squared distances to nearest cluster center.
Returns:
| Type | Description |
|---|---|
float
|
Inertia value (lower is better) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
bitbullet.cluster.algorithms.partitional.kmodes.KModesClusterer
Bases: BaseClusterer
K-Modes clustering for pure categorical data.
Features: - Cao initialization for optimal starting centroids - Data-driven categorical feature weighting - Memory-efficient distance computation - Production-ready centroid saving/loading - Automatic binning of numerical features if present
Based on: - Huang, Z. (1998). Extensions to the k-Means Algorithm for Clustering Large Data Sets with Categorical Values. Data Mining and Knowledge Discovery, 2(3), 283-304. - Cao, F. et al. (2009). A New Initialization Method for Categorical Data Clustering. Expert Systems with Applications, 36(7), 10223-10228.
get_centroids()
Get cluster centroids as DataFrame.
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with centroids (modes for all categorical features) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_cost()
Get the total clustering cost (sum of categorical dissimilarities).
This is the K-Modes equivalent of inertia for K-Means. It represents the sum of weighted Hamming distances from each point to its cluster mode (centroid).
Returns:
| Type | Description |
|---|---|
float
|
Total cost value (lower is better, decreases with more clusters) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_descriptive_centroids()
Get cluster centroids with descriptive labels for binned numerical features.
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with centroids where binned numerical features show value ranges |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
bitbullet.cluster.algorithms.partitional.kprototypes.KPrototypesClusterer
Bases: BaseClusterer
K-Prototypes clustering for mixed numerical and categorical data.
Features: - Automatic or manual gamma estimation - Data-driven categorical feature weighting - Custom distance function with weights - Memory-efficient distance computation - Production-ready centroid saving/loading
get_centroids()
Get cluster centroids as DataFrame.
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with centroids (medians for numerical, modes for categorical) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_cost()
Get the total clustering cost (SSE for numerical + weighted mismatches for categorical).
This is the K-Prototypes equivalent of inertia/WCSS for K-Means. It represents the sum of: - Squared Euclidean distances for numerical features - Gamma-weighted Hamming distances for categorical features
Returns:
| Type | Description |
|---|---|
float
|
Total cost value (lower is better, decreases with more clusters) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
Algorithms — Density
bitbullet.cluster.algorithms.density.dbscan.DBSCANClusterer
Bases: BaseClusterer
DBSCAN clustering for arbitrary-shaped clusters.
Features: - Density-based clustering (no need to specify n_clusters) - Automatic outlier detection (noise points labeled as -1) - Works with arbitrary cluster shapes - Supports custom distance metrics
Note
DBSCAN does not support prediction on new data. Use fit_predict on all data at once, or use a classifier trained on DBSCAN labels.
get_core_points()
Get indices of core points (points with >= min_samples neighbors).
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of core point indices |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_n_clusters()
Get number of clusters found (excluding noise).
Returns:
| Type | Description |
|---|---|
int
|
Number of clusters |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_noise_points()
Get indices of points classified as noise.
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of indices where labels == -1 |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
Algorithms — Model-Based
bitbullet.cluster.algorithms.model_based.gmm.GMMClusterer
Bases: BaseClusterer
Gaussian Mixture Model for probabilistic clustering.
Features: - Soft clustering (probabilities for each cluster) - Models overlapping clusters - Multiple covariance types (full, tied, diag, spherical) - Supports convergence diagnostics
Note
GMM only works with numerical features. For mixed data, use K-Prototypes instead.
get_aic(data)
Compute Akaike Information Criterion (AIC) for model selection.
AIC is similar to BIC but with less penalty for model complexity. Lower is better.
AIC = -2 * log_likelihood + 2 * n_params
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Data used for fitting (same data used in fit()) |
required |
Returns:
| Type | Description |
|---|---|
float
|
AIC score (lower is better) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_bic(data)
Compute Bayesian Information Criterion (BIC) for model selection.
BIC balances model fit with complexity. Lower is better. Use this for elbow-like plots when selecting optimal number of clusters.
BIC = -2 * log_likelihood + n_params * log(n_samples)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Data used for fitting (same data used in fit()) |
required |
Returns:
| Type | Description |
|---|---|
float
|
BIC score (lower is better) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_centroids()
Get cluster centroids (means) as DataFrame.
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with centroids |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_covariances()
Get covariance matrices for each cluster.
Returns:
| Type | Description |
|---|---|
ndarray
|
Covariance matrices (shape depends on covariance_type) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
get_weights()
Get mixture weights (prior probabilities of each cluster).
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of weights summing to 1.0 |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
predict_proba(data)
Get probability of each cluster for each data point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Data to get probabilities for |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Array of shape (n_samples, n_clusters) with probabilities |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
score(data)
Compute log-likelihood of data under the model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Data to score |
required |
Returns:
| Type | Description |
|---|---|
float
|
Log-likelihood score (higher is better) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If not fitted |
Feature Weighting
bitbullet.cluster.weights.categorical_weights.CategoricalWeights
Factory for calculating categorical feature weights.
Provides multiple data-driven weighting strategies: - Relevance: Combines entropy, cardinality, and missingness - Entropical: Information-theoretic weighting - Balanced: Based on category dominance patterns - Uniform: Equal weights for all features
calculate(data, method='relevance', categorical_columns=None)
staticmethod
Calculate categorical feature weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Input data |
required |
method
|
str
|
Weighting method ("relevance", "entropical", "balanced", "uniform") |
'relevance'
|
categorical_columns
|
Optional[List[str]]
|
List of categorical column names. If None, auto-detect. |
None
|
Returns:
| Type | Description |
|---|---|
Dict[str, float]
|
Dictionary mapping column names to weights |
Raises:
| Type | Description |
|---|---|
ValueError
|
If method is unknown |
to_array(weights, columns)
staticmethod
Convert weight dictionary to array in column order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
Dict[str, float]
|
Weight dictionary |
required |
columns
|
List[str]
|
Column names in desired order |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Weights as numpy array |
validate_manual_weights(weights, columns)
staticmethod
Validate and convert manual weights to dictionary format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
Union[Dict[str, float], List[float], ndarray]
|
Manual weights as dict, list, or array |
required |
columns
|
List[str]
|
Column names |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, float]
|
Dictionary mapping column names to weights |
Raises:
| Type | Description |
|---|---|
ValueError
|
If weights are invalid |
bitbullet.cluster.weights.gamma_estimation.GammaEstimator
Factory for estimating gamma values in mixed-type clustering.
Gamma balances numerical (Euclidean) and categorical (matching) distances.
calculate(data, numerical_columns, categorical_columns, method='huang', sample_size=1000)
staticmethod
Calculate gamma value(s) for mixed-type clustering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Input data |
required |
numerical_columns
|
List[str]
|
List of numerical column names |
required |
categorical_columns
|
List[str]
|
List of categorical column names |
required |
method
|
Union[str, float]
|
Estimation method ("huang", "variance_matching") or manual float value |
'huang'
|
sample_size
|
Optional[int]
|
Sample size for distance calculations (for efficiency). None = use all data. |
1000
|
Returns:
| Type | Description |
|---|---|
Union[float, ndarray]
|
Gamma value (float) or per-feature gamma values (np.ndarray) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If method is unknown or data is invalid |
calculate_with_weights(gamma, categorical_weights, categorical_columns=None)
staticmethod
Combine gamma with per-feature categorical weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gamma
|
float
|
Base gamma value |
required |
categorical_weights
|
Union[Dict[str, float], ndarray, List[float]]
|
Per-feature weights |
required |
categorical_columns
|
Optional[List[str]]
|
Column names (required if weights is dict) |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Per-feature gamma values |
Raises:
| Type | Description |
|---|---|
ValueError
|
If weights format is invalid |
validate_gamma(gamma, n_categorical_features)
staticmethod
Validate gamma value(s).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gamma
|
Union[float, ndarray, str]
|
Gamma value, array, or method name |
required |
n_categorical_features
|
int
|
Number of categorical features |
required |
Returns:
| Type | Description |
|---|---|
Union[float, ndarray]
|
Validated gamma value(s) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If gamma is invalid |
Sample Size
bitbullet.cluster.utils.sample_size.SampleSizeEstimator
Intelligent sample size estimation for clustering.
Combines multiple estimation approaches: 1. Statistical power analysis (cluster detection) 2. Dimensionality-based rules 3. Algorithm-specific requirements 4. Adaptive stability detection (optional) 5. Computational budget constraints
Example
estimator = SampleSizeEstimator() estimate = estimator.estimate( data=df, algorithm='kmeans', numerical_columns=['age', 'income'], categorical_columns=['region'], min_cluster_proportion=0.02, ) print(f"Recommended: {estimate.recommended}") print(f"Reasoning: {estimate.reasoning}")
__init__(default_confidence=0.95, default_target_runtime=60.0, adaptive_pilot_size=500)
Initialize the estimator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
default_confidence
|
float
|
Default confidence level for statistical estimates |
0.95
|
default_target_runtime
|
float
|
Default target runtime in seconds |
60.0
|
adaptive_pilot_size
|
int
|
Sample size for adaptive stability testing |
500
|
estimate(data, algorithm, numerical_columns=None, categorical_columns=None, min_cluster_proportion=0.02, confidence_level=None, expected_k=None, k_range=None, target_runtime_seconds=None, memory_limit_mb=None, run_adaptive=False, verbose=False)
Estimate optimal sample size for clustering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The full dataset |
required |
algorithm
|
str
|
Clustering algorithm ('kmeans', 'kprototypes', etc.) |
required |
numerical_columns
|
Optional[List[str]]
|
List of numerical column names |
None
|
categorical_columns
|
Optional[List[str]]
|
List of categorical column names |
None
|
min_cluster_proportion
|
float
|
Minimum cluster size to detect (e.g., 0.02 = 2%) |
0.02
|
confidence_level
|
Optional[float]
|
Statistical confidence level (default: 0.95) |
None
|
expected_k
|
Optional[int]
|
Expected number of clusters (if known) |
None
|
k_range
|
Optional[range]
|
Range of K values to test (for auto-K) |
None
|
target_runtime_seconds
|
Optional[float]
|
Target runtime budget |
None
|
memory_limit_mb
|
Optional[float]
|
Memory limit in MB |
None
|
run_adaptive
|
bool
|
Whether to run adaptive stability detection |
False
|
verbose
|
bool
|
Print detailed estimation info |
False
|
Returns:
| Type | Description |
|---|---|
SampleSizeEstimate
|
SampleSizeEstimate with minimum, recommended, optimal, and reasoning |
sample_data(data, estimate, stratify_columns=None, random_state=42)
Sample the data based on the estimate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Full dataset |
required |
estimate
|
SampleSizeEstimate
|
SampleSizeEstimate from estimate() |
required |
stratify_columns
|
Optional[List[str]]
|
Columns to stratify by (preserves distribution) |
None
|
random_state
|
int
|
Random seed for reproducibility |
42
|
Returns:
| Type | Description |
|---|---|
Tuple[DataFrame, ndarray]
|
Tuple of (sampled_data, sample_indices) |
bitbullet.cluster.utils.sample_size.SampleSizeEstimate
dataclass
Result of sample size estimation.
to_dict()
Convert to dictionary for JSON serialization.
bitbullet.cluster.utils.sample_size.estimate_sample_size(data, algorithm='kmeans', numerical_columns=None, categorical_columns=None, min_cluster_proportion=0.02, **kwargs)
Convenience function to estimate optimal sample size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The dataset to cluster |
required |
algorithm
|
str
|
Clustering algorithm name |
'kmeans'
|
numerical_columns
|
Optional[List[str]]
|
Numerical feature columns |
None
|
categorical_columns
|
Optional[List[str]]
|
Categorical feature columns |
None
|
min_cluster_proportion
|
float
|
Minimum cluster size to detect |
0.02
|
**kwargs
|
Additional arguments passed to SampleSizeEstimator.estimate() |
{}
|
Returns:
| Type | Description |
|---|---|
SampleSizeEstimate
|
SampleSizeEstimate with recommendations |