ClusteringModel

class ClusteringModel(**kwargs)

Bases: MetaModel

Subclass of MetaModel for handling clustering-related data processing.

Owns the clustering computation itself, rather than ClusteringView, which had been importing hdbscan, sklearn.mixture.GaussianMixture and pandas.api.types.is_float_dtype into a QWidget; none of those names appears in the View any more.

The View asks for a result by emitting cluster_requested; ClusteringController calls cluster() and hands what comes back to ClusteringView.set_clustering_result; RawDataController.calculate_psd is the same shape.

Public Methods

ClusteringModel.build_clustering_frame(plot_data: DataFrame, frame_columns: List[str], log_flags: List[bool]) → DataFrame

Filter and log-scale the loaded rows into the frame that gets clustered.

The View still reads the settings dialog, since the per-column flags are strings the user typed; deciding what the numbers mean is this layer’s.

logscale_and_filter_columns masks rows across every array it is handed at once, which is what keeps the columns aligned - so "id" is passed through the filter with the rest rather than reattached afterwards, or it would index rows that are no longer there.

Parameters:
  • plot_data (pd.DataFrame) – the rows the loader returned

  • frame_columns (List[str]) – the columns to carry through, "id" included

  • log_flags (List[bool]) – per column, whether to log-scale it, index-aligned with frame_columns

Returns:

the filtered, log-scaled frame to cluster

Return type:

pd.DataFrame

Raises:

KeyError – If a requested column is missing from the loaded rows.

ClusteringModel.cluster(frame: DataFrame, exclude_cols: List[str], method: str, params: Dict[str, Any]) → Tuple[DataFrame, ndarray, ndarray]

Normalize the given columns and cluster the frame by the named method.

The single entry point the Controller calls. params carries values the View has already parsed and validated - integers and floats, not the raw strings from the settings dialog - so a malformed entry is reported to the user where they typed it rather than raised from in here.

Parameters:
  • frame (pd.DataFrame) – the rows to cluster, carrying an id column for row identity

  • exclude_cols (List[str]) – columns to leave un-normalized, id among them

  • method (str) – either "HDBSCAN" or "Gaussian Mixtures"

  • params (Dict[str, Any]) – the method’s already-parsed parameters

Returns:

the normalized frame, the cluster labels, and the confidences

Return type:

Tuple[pd.DataFrame, np.ndarray, np.ndarray]

Raises:

ValueError – if the method is not one this model implements

ClusteringModel.cluster_gaussian_mixture(df: DataFrame, n_components: int) → Tuple[ndarray, ndarray]

Fit a Gaussian mixture to the data and return its labels and confidences.

Seeded (random_state=42, n_init=100) so that re-running on the same rows gives the same answer, which 1.9.0 fixed and this move preserves.

Parameters:
  • df (pd.DataFrame) – DataFrame to cluster.

  • n_components (int) – Number of mixture components to fit.

Returns:

Cluster labels and per-row confidences.

Return type:

Tuple[np.ndarray, np.ndarray]

ClusteringModel.cluster_hdbscan(df: DataFrame, min_cluster_size: int = 30, min_samples: int = 1, cluster_selection_epsilon: float = 1) → Tuple[ndarray, ndarray]

Performs HDBSCAN clustering on the provided data.

Parameters:
  • df (pd.DataFrame) – DataFrame to cluster.

  • min_cluster_size (int) – Minimum size of clusters.

  • min_samples (int) – Minimum samples per cluster.

  • cluster_selection_epsilon (float) – Epsilon value to influence cluster boundaries.

Returns:

Cluster labels and probabilities.

Return type:

Tuple[np.ndarray, np.ndarray]

ClusteringModel.commit_cluster_columns(loader: str, cluster_data: DataFrame, table_name: str) → bool

Write the cluster labels and confidences into the database.

The two new columns are unitless, which is what the [None, None] says. It belongs with the call it describes rather than with the widget that asks.

Parameters:
  • loader (str) – the database loader’s plugin key

  • cluster_data (pd.DataFrame) – the id, label and confidence columns to write

  • table_name (str) – the table to write them into

Returns:

True if the loader reported success

Return type:

bool

ClusteringModel.drop_cluster_columns(loader: str, table: str) → bool

Delete an existing clustering result so a new one can replace it.

Owns the SQL, rather than ClusteringView - authoring ALTER TABLE statements is not a widget’s job. DECISIONS.md (2026-08-25) accepts the f-string interpolation itself, because the database is a local file owned by the user running the app; this is about where the SQL lives.

Parameters:
  • loader (str) – the database loader’s plugin key

  • table (str) – the table the cluster columns are in

Returns:

True if the loader reported success

Return type:

bool

ClusteringModel.find_cluster_column_table(loader: str) → str | None

Return the table already holding cluster columns, or None if there is none.

Parameters:

loader (str) – the database loader’s plugin key

Returns:

the table name, or None if cluster_label is not in the database

Return type:

Optional[str]

ClusteringModel.normalize_column_data(df: DataFrame, exclude_cols: List[str] | None = None) → DataFrame

Applies MAD-based normalization to float columns in the dataframe.

Parameters:
  • df (pd.DataFrame) – Input DataFrame.

  • exclude_cols (Optional[List[str]]) – Columns to exclude from normalization. None means exclude nothing.

Returns:

Normalized DataFrame.

Return type:

pd.DataFrame

Private Methods

ClusteringModel._init() → None

Perform additional initialization specific to the algorithm being implemented. Must be implemented by subclasses.

This function is called at the end of the class constructor to perform additional initialization specific to the algorithm being implemented. kwargs provided to the base class constructor are available as class attributes.