decipher.cluster

Generic feature clustering: standardise → PCA → HDBSCAN → noise reassignment.

dataclass decipher.cluster.ClusterResult
@dataclass
class ClusterResult:
    labels: np.ndarray            # raw HDBSCAN labels; -1 = noise
    labels_assigned: np.ndarray   # noise rows reassigned to nearest centroid
    n_clusters: int
    features_reduced: np.ndarray  # PCA-projected matrix, (N, n_pca_dims)
    centroids: np.ndarray         # per-cluster centroid in PCA space

Use labels_assigned for downstream symbol streams; it has no -1 holes.

function decipher.cluster.cluster_features
def cluster_features(
    X: np.ndarray,
    *,
    min_cluster_size: int = 10,
    min_samples: int = 3,
    n_pca_dims: int = 20,
    cluster_selection_epsilon: float = 0.3,
    cluster_selection_method: str = "eom",
    metric: str = "euclidean",
) -> ClusterResult

Standardise → PCA → HDBSCAN. The noise-reassignment step is load-bearing: downstream symbol-stream analyses need a complete sequence with no -1 holes.

Defaults: the defaults match the values that appeared identically in two independent paper reproductions (paper #001 hand-rolled mel-patch features and paper #002 AVES SSL features). Override at the call site rather than forking the primitive.