Metadata-Version: 2.4
Name: MSSCpy
Version: 0.1.6
Summary: Multi-Scalar Spectral Clustering with persistent scale regions
Author: Francis Baffour-Awuah Junior, Mehran Fazli, Deborah A. Striegel
License-Expression: MIT
Project-URL: Homepage, https://github.com/mehranfazli/MSSCpy
Project-URL: Repository, https://github.com/mehranfazli/MSSCpy
Project-URL: Issues, https://github.com/mehranfazli/MSSCpy/issues
Project-URL: Changelog, https://github.com/mehranfazli/MSSCpy/blob/main/CHANGELOG.md
Project-URL: Publication, https://doi.org/10.1109/ACCESS.2025.3628224
Keywords: clustering,machine learning,multi-scale analysis,spectral clustering
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: <3.13,>=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy<2,>=1.23
Requires-Dist: scipy<1.18,>=1.9
Requires-Dist: scikit-learn>=1.3
Provides-Extra: examples
Requires-Dist: matplotlib>=3.7; extra == "examples"
Requires-Dist: jupyter>=1.0; extra == "examples"
Provides-Extra: test
Requires-Dist: pytest>=7.4; extra == "test"
Requires-Dist: matplotlib>=3.7; extra == "test"
Dynamic: license-file

# MSSCpy

**MSSCpy** is a Python implementation of Multi-Scalar Spectral Clustering
(MSSC), a method for finding robust, scale-dependent clustering structures.
Instead of requiring the user to select one radial-basis-function scale before
analysis, MSSC evaluates spectral clustering across a range of scales and
identifies consecutive intervals where the estimated number of clusters
persists.

MSSCpy is based on:

> Baffour-Awuah Junior, F., Fazli, M. & Striegel, D. A. Multi-Scalar
> Spectral Clustering: A New Approach to Capture Scale-Dependent Persistent
> Clusterings. *IEEE Access* **13**, 190843-190858 (2025).
> [https://doi.org/10.1109/ACCESS.2025.3628224](https://doi.org/10.1109/ACCESS.2025.3628224)

## How Multi-Scalar Spectral Clustering works

MSSC begins with a finite sample-by-feature data matrix. By default, each
feature is standardized to zero mean and unit variance. Standardization can be
disabled when measurements are already comparable or their original scales
are scientifically meaningful.

For samples `x_i` and `x_j`, MSSC converts Euclidean distance into an RBF
affinity:

```text
A(i, j) = exp(-||x_i - x_j||^2 / (2 sigma^2))
        = exp(-gamma ||x_i - x_j||^2)

gamma = 1 / (2 sigma^2)
```

Small `sigma` values produce local similarity graphs and can reveal fine
cluster structure. Large `sigma` values connect more distant observations
and expose coarser structure.

MSSCpy derives the explored sigma interval from the sorted off-diagonal
pairwise distances. The `low` and `high` parameters identify two fractions
of that distribution. Each selected boundary distance is converted to the
sigma at which its RBF affinity equals 0.5:

```text
sigma = distance / sqrt(2 log(2))
```

The package evaluates `n_sigmas` evenly spaced values between those
boundaries. At every sigma:

1. Construct the RBF affinity matrix.
2. Remove self-loops and calculate the symmetric normalized graph Laplacian.
3. Sort the lowest Laplacian eigenvalues.
4. Use the largest consecutive eigengap to estimate the cluster count.
5. Run spectral clustering on the precomputed affinity using deterministic
   `cluster_qr` label assignment.
6. Store labels and a binary sample co-membership matrix.

MSSC then groups consecutive sigma values having the same estimated cluster
count. Each such interval is a **persistent region**. Region length is the
number of evaluated scales in the interval and measures how long that cluster
count persists over the selected scale grid.

A persistent region guarantees a stable cluster count, not necessarily
identical sample assignments at every scale. MSSCpy therefore retains the
complete label sequence. The user may inspect labels at the start, midpoint,
or end of any region. The midpoint is the default representative clustering.

There is no universally correct final region. Fine-scale regions may divide
the data into many small groups, while broad scales may merge meaningful
structure. The user should review region length, eigengap strength, the
three-dimensional spectrum, PCA projections, affinity matrices, and domain
knowledge before selecting a representative result.

## Features

- Sample-by-feature NumPy-compatible input
- Optional feature standardization
- Data-derived sigma interval based on pairwise-distance fractions
- RBF affinity matrices at every evaluated scale
- Symmetric normalized graph-Laplacian analysis
- Largest-eigengap cluster-count estimation
- Deterministic precomputed-affinity spectral clustering
- Persistent-region detection across consecutive scales
- Complete labels and co-membership matrices for every sigma
- Start, midpoint, and end labels for each persistent region
- Scale-dependent cluster-count and eigengap plots
- Three-dimensional sigma/eigenvalue-index/eigenvalue plot
- PCA projections with categorical cluster legends
- Cluster-ordered RBF affinity heatmaps
- Synthetic and real-world Jupyter examples
- Estimator, convenience-function, and low-level numerical APIs

## Installation

After MSSCpy is published on PyPI, install the core package with:

```bash
python -m pip install MSSCpy
```

Install the plotting and Jupyter dependencies from PyPI with:

```bash
python -m pip install "MSSCpy[examples]"
```

Install from the repository directory:

```bash
python -m pip install -e .
```

Install the plotting and Jupyter dependencies:

```bash
python -m pip install -e ".[examples]"
```

For development and testing:

```bash
python -m pip install -e ".[test,examples]"
python -m pytest
```

Install directly from GitHub:

```bash
python -m pip install "MSSCpy[examples] @ git+https://github.com/mehranfazli/MSSCpy.git"
```

MSSCpy 0.1.6 supports Python 3.9 through 3.12 and requires NumPy 1.x, SciPy
below 1.18, and scikit-learn 1.3 or newer. The NumPy and SciPy upper bounds
avoid binary compatibility problems in scientific environments containing
extensions compiled against NumPy 1.x. The Python upper bound prevents pip
from selecting MSSCpy where a compatible NumPy 1.x wheel is unavailable.

## Input format

Input data must have samples in rows and features in columns:

| Sample | Feature A | Feature B | Feature C |
|---|---:|---:|---:|
| Sample 1 | 0.24 | 1.08 | 0.51 |
| Sample 2 | 0.31 | 0.92 | 0.66 |
| Sample 3 | 0.18 | 1.15 | 0.47 |

The matrix must:

- be two-dimensional;
- contain at least two samples and one feature; and
- contain only finite numeric values.

If the lower selected distance quantile is zero because of duplicate samples,
choose a larger `low` value or resolve the duplicates before analysis.

### Cluster samples or features

MSSCpy clusters **samples by default**. For an input matrix with shape
`(n_samples, n_features)`, rows are the observations being clustered,
columns are their measured features, and every label array in
`result.labels` has length `n_samples`.

To cluster features instead, transpose the input matrix so that each original
feature becomes an observation:

```python
from MSSCpy import analyze

feature_result = analyze(
    data.T,
    low=0.01,
    high=0.60,
    n_sigmas=25,
    standardize=True,
)
```

The labels in `feature_result.labels` then correspond to the original
features and have length `n_features`. MSSCpy does not currently provide an
`axis="features"` option; feature clustering requires explicit transposition.
After transposition, standardization is applied to the columns of the
transposed matrix. Set `standardize=False` when that transformation is not
appropriate for the intended feature-level analysis.

## Tutorial: run MSSC

Generate a reproducible synthetic dataset:

```python
from sklearn.datasets import make_blobs

data, generating_labels = make_blobs(
    n_samples=120,
    centers=4,
    cluster_std=[0.35, 0.45, 0.55, 0.40],
    random_state=7,
)
```

Run the estimator interface:

```python
from MSSCpy import MSSC

model = MSSC(
    low=0.01,
    high=0.60,
    n_sigmas=25,
    standardize=True,
    max_eigenvalues=50,
    random_state=7,
).fit(data)

result = model.result_
```

The convenience function returns the same result directly:

```python
from MSSCpy import analyze

result = analyze(
    data,
    low=0.01,
    high=0.60,
    n_sigmas=25,
    standardize=True,
    random_state=7,
)
```

## Choose the sigma range

The main scale parameters are:

| Parameter | Meaning | Default |
|---|---|---:|
| `low` | Lower fraction of sorted pairwise distances | `0.01` |
| `high` | Upper fraction of sorted pairwise distances | `0.60` |
| `n_sigmas` | Number of sigma values, including both endpoints | `50` |
| `standardize` | Standardize every feature before distance calculation | `True` |
| `max_eigenvalues` | Maximum number of lowest eigenvalues used by the eigengap rule | `50` |
| `random_state` | Reproducibility setting passed to spectral clustering | `0` |

`low` and `high` must satisfy:

```text
0 <= low < high <= 1
```

The evaluated scales and inverse-width parameters are stored in:

```python
result.sigmas
result.gammas
```

## Review cluster counts and persistent regions

Inspect the estimated cluster count at every scale:

```python
print(result.cluster_counts)
```

List the persistent regions:

```python
for index, region in enumerate(result.regions):
    print(
        f"Region {index}: "
        f"sigma={region.sigma_min:.3f}..{region.sigma_max:.3f}, "
        f"clusters={region.n_clusters}, "
        f"scales={region.length}"
    )
```

Each `PersistentRegion` contains:

```python
region.start_index
region.end_index
region.sigma_min
region.sigma_max
region.n_clusters
region.length
```

## Review the scale diagnostics

Plot the scale-dependent cluster counts and largest eigengaps:

```python
import matplotlib.pyplot as plt
from MSSCpy import plot_scale_summary

figure, axes = plt.subplots(1, 2, figsize=(14, 5))
plot_scale_summary(result, axes)
figure.tight_layout()
plt.show()
```

The cluster-count panel shades persistent intervals. The eigengap panel shows
the strength of the selected largest gap at each sigma.

## Draw the three-dimensional eigenvalue plot

Reproduce the sigma/eigenvalue-index/eigenvalue diagnostic from the original
MSSC notebook:

```python
import matplotlib.pyplot as plt
from MSSCpy import plot_eigenvalues_3d

axis = plot_eigenvalues_3d(result)
axis.figure.tight_layout()
plt.show()
```

Each vertical slice corresponds to one sigma. Adjacent slices alternate
between gray and silver. The black marker identifies the eigenvalue immediately
before the largest eigengap used to estimate the cluster count.

Supply an existing three-dimensional axis when composing a larger figure:

```python
figure = plt.figure(figsize=(12, 9))
axis = figure.add_subplot(projection="3d")
plot_eigenvalues_3d(result, ax=axis)
```

## Select a persistent region

One reproducible starting point is the longest region:

```python
selected_region = max(
    range(len(result.regions)),
    key=lambda index: result.regions[index].length,
)

selected = result.regions[selected_region]
labels = result.region_labels(selected_region)
```

The default labels come from the midpoint sigma. Labels from other positions
within the same region are also available:

```python
start_labels = result.region_labels(selected_region, position="start")
middle_labels = result.region_labels(selected_region, position="middle")
end_labels = result.region_labels(selected_region, position="end")
```

`MSSC.fit_predict()` also returns midpoint labels. Its default `region=-1`
selects the final persistent region, so pass the intended region explicitly
when a different interval is desired:

```python
labels = MSSC(n_sigmas=25, random_state=7).fit_predict(
    data,
    region=selected_region,
)
```

## Plot the selected clustering

Display the same selected scale as a PCA projection and ordered affinity
matrix:

```python
import matplotlib.pyplot as plt
from MSSCpy import plot_affinity, plot_projection

figure, axes = plt.subplots(1, 2, figsize=(13, 5))
plot_projection(result, region=selected_region, ax=axes[0])
plot_affinity(result, region=selected_region, ax=axes[1])
figure.tight_layout()
plt.show()
```

`plot_projection()` uses a categorical legend containing only cluster labels
that actually occur. It does not use a continuous colorbar. The affinity
heatmap uses a continuous colorbar because RBF affinity is a continuous value
between zero and one.

Either function can also display an exact scale:

```python
plot_projection(result, scale_index=10)
plot_affinity(result, scale_index=10)
```

Specify either `region` or `scale_index`, not both. When neither is supplied,
the midpoint of the complete sigma grid is used.

## Real-world WDBC example

The complete notebook includes the Wisconsin Diagnostic Breast Cancer dataset
distributed with scikit-learn. Following the original MSSC workflow, it uses
all 569 samples and the first 10 measured features:

```python
from sklearn.datasets import load_breast_cancer
from MSSCpy import analyze

cancer = load_breast_cancer()
cancer_data = cancer.data[:, :10]

cancer_result = analyze(
    cancer_data,
    low=0.001,
    high=0.10,
    n_sigmas=15,
    standardize=True,
    random_state=7,
)
```

The malignant/benign diagnosis is excluded from MSSC and used only afterward
for visual interpretation. Separate PCA figures display the MSSC clusters and
the known diagnosis. Both use categorical legends with distinct colors.

## Complete Jupyter workflow

Open `examples/MSSCpy_example.ipynb` in Jupyter Notebook or JupyterLab and run
its cells in order. It contains two complete examples.

The first example generates a four-center synthetic dataset, runs MSSC across
25 scales, displays the scale summary and three-dimensional eigenvalue plot,
selects the longest persistent region, and draws its PCA projection and
cluster-ordered affinity matrix.

The second example analyzes the WDBC data, lists its persistent regions,
displays the MSSC diagnostic and selected-region figures, and compares the
unsupervised MSSC projection with known malignant/benign diagnosis.

The final cell saves reusable synthetic and WDBC arrays, sigma grids, gamma
grids, cluster counts, and selected labels as compressed NumPy files.

## Result data

`MSSCResult` retains the complete multi-scale analysis:

```python
result.data             # analyzed data; standardized when requested
result.sigmas           # evaluated RBF scales
result.gammas           # 1 / (2 sigma^2)
result.affinities       # one sample-by-sample affinity matrix per scale
result.eigenvalues      # retained normalized-Laplacian eigenvalues per scale
result.eigengaps        # consecutive eigenvalue gaps per scale
result.labels           # spectral-clustering labels per scale
result.co_memberships   # binary co-membership matrix per scale
result.cluster_counts   # eigengap-selected cluster count per scale
result.regions          # persistent scale intervals
```

Affinity and co-membership storage grows as
`O(n_sigmas * n_samples^2)`. Large datasets or dense sigma grids may
therefore require substantial memory.

## Low-level workflow

The numerical steps are independently available:

```python
from scipy.spatial.distance import cdist
from MSSCpy import (
    affinity_matrix,
    cluster_affinity,
    eigengap_cluster_count,
    normalized_laplacian,
    persistent_regions,
    sigma_grid,
)

distances = cdist(data, data)
sigmas = sigma_grid(distances, low=0.01, high=0.60, n_sigmas=25)
affinity = affinity_matrix(data, gamma=1 / (2 * sigmas[0] ** 2))
laplacian = normalized_laplacian(affinity)
n_clusters, eigenvalues, eigengaps = eigengap_cluster_count(affinity)
labels, co_membership = cluster_affinity(affinity, n_clusters)
```

These functions support testing, reproduction of individual manuscript steps,
and integration of MSSC components into other workflows.

## Public API

| Name | Purpose |
|---|---|
| `MSSC` | Fit Multi-Scalar Spectral Clustering and store the complete result in `result_`. |
| `MSSCResult` | Immutable container for all scale-dependent numerical outputs. |
| `PersistentRegion` | Describe one inclusive sigma-index interval with a constant cluster count. |
| `analyze()` | Run MSSC and return an `MSSCResult` directly. |
| `affinity_matrix()` | Calculate an RBF affinity matrix from sample-by-feature data and gamma. |
| `sigma_grid()` | Derive the manuscript's sigma interval from a pairwise-distance matrix. |
| `normalized_laplacian()` | Calculate the symmetric normalized graph Laplacian without self-loops. |
| `eigengap_cluster_count()` | Estimate a cluster count from the largest retained eigengap. |
| `cluster_affinity()` | Spectrally cluster a precomputed affinity and return labels and co-membership. |
| `persistent_regions()` | Find inclusive consecutive ranges with unchanged cluster counts. |
| `plot_scale_summary()` | Plot cluster-count persistence and largest eigengap across sigma. |
| `plot_eigenvalues_3d()` | Plot eigenvalues over sigma and mark each selected eigengap position. |
| `plot_projection()` | Draw a PCA projection with a discrete legend for observed cluster labels. |
| `plot_affinity()` | Draw an affinity heatmap reordered by the selected clustering. |

## Reproducibility

Set `random_state` when constructing `MSSC` or calling `analyze()`.
MSSCpy uses scikit-learn's deterministic `cluster_qr` assignment and retains
the random-state parameter for estimator compatibility and future assignment
strategies. Identical inputs, parameters, dependency versions, and numerical
platforms should produce the same cluster assignments.

Report at least the following when publishing an MSSC analysis:

- features and preprocessing;
- whether standardization was enabled;
- `low`, `high`, and `n_sigmas`;
- `max_eigenvalues`;
- selected persistent region and representative position; and
- MSSCpy and dependency versions.

## Compatibility and documented fixes

The packaged implementation preserves the original method's:

- Euclidean-distance RBF kernel;
- relationship `gamma = 1 / (2 sigma^2)`;
- distance-derived sigma range;
- symmetric normalized graph Laplacian;
- largest-eigengap cluster-count rule;
- precomputed-affinity spectral clustering; and
- definition of persistent regions as consecutive scales with the same
  estimated cluster count.

The following clear implementation defects and boundary cases were corrected:

1. The original `copmute_affinity()` function left the final diagonal entry
   at zero while every other self-affinity was one. MSSCpy evaluates the RBF
   formula uniformly, giving every sample self-affinity one before all
   self-loops are removed for the Laplacian.
2. The original `stable_sigma_range()` function could omit the last scale
   when it formed a singleton region. MSSCpy always returns a complete
   partition of the evaluated scale grid.
3. `high=1` now selects the final pairwise distance instead of indexing one
   position beyond the array.
4. Eigendecomposition automatically adapts when fewer than 50 samples are
   available.
5. PCA cluster plots use categorical legends rather than continuous
   cluster-number colorbars.

These corrections do not change the mathematical MSSC procedure.

## Repository layout

```text
src/MSSCpy/                    Installable package and public API
tests/                         Numerical, model, plotting, and API tests
examples/MSSCpy_example.ipynb  Complete synthetic and WDBC workflow
MSSC_functions.py              Original implementation
MSSC-jupyter-notebook.ipynb    Original research notebook
MSSC-jupyter-notebook_1.ipynb  Original extended research notebook
MSSC_IEEE_25.pdf               Published manuscript
pyproject.toml                 Package metadata and dependencies
CITATION.cff                   Machine-readable citation metadata
LICENSE                        MIT software license
```

The original research files remain unchanged at the repository root for
provenance. The installable implementation lives under `src/MSSCpy`.

## Citation

If you use MSSC or MSSCpy in your research, please cite:

Baffour-Awuah Junior, F., Fazli, M. & Striegel, D. A. Multi-Scalar Spectral
Clustering: A New Approach to Capture Scale-Dependent Persistent Clusterings.
*IEEE Access* **13**, 190843-190858 (2025).
[https://doi.org/10.1109/ACCESS.2025.3628224](https://doi.org/10.1109/ACCESS.2025.3628224)

### BibTeX

```bibtex
@article{BaffourAwuahJunior2025MSSC,
  author  = {Baffour-Awuah Junior, Francis and Fazli, Mehran and Striegel, Deborah A.},
  title   = {Multi-Scalar Spectral Clustering: A New Approach to Capture Scale-Dependent Persistent Clusterings},
  journal = {IEEE Access},
  volume  = {13},
  pages   = {190843--190858},
  year    = {2025},
  doi     = {10.1109/ACCESS.2025.3628224},
  url     = {https://doi.org/10.1109/ACCESS.2025.3628224}
}
```

## Project status

MSSCpy 0.1.6 provides a tested implementation of the core MSSC workflow,
persistent-region analysis, and the principal visual diagnostics from the
original notebooks. Users should treat region selection as an exploratory,
scale-dependent modeling decision rather than assuming one universally
optimal clustering.

## License

The MSSCpy software is distributed under the MIT License. See `LICENSE` for
the complete terms.

The published manuscript is a separate work distributed under the Creative
Commons Attribution 4.0 License.
