Metadata-Version: 2.4
Name: python-som
Version: 0.8.0
Summary: Python implementation of the Self-Organizing Map
Project-URL: Homepage, https://github.com/andremsouza/python-som
Project-URL: Documentation, https://andremsouza.github.io/python-som/
Project-URL: Changelog, https://github.com/andremsouza/python-som/blob/master/CHANGELOG.md
Project-URL: Issues, https://github.com/andremsouza/python-som/issues
Author-email: André Moreira Souza <msouza.andre@hotmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Provides-Extra: analysis
Requires-Dist: bandit==1.9.4; extra == 'analysis'
Requires-Dist: pip-audit==2.10.1; extra == 'analysis'
Requires-Dist: pylint==4.0.6; extra == 'analysis'
Provides-Extra: bench
Requires-Dist: asv==0.6.6; extra == 'bench'
Requires-Dist: minisom==2.3.6; extra == 'bench'
Provides-Extra: cli
Requires-Dist: tqdm>=4.66; extra == 'cli'
Provides-Extra: dev
Requires-Dist: hypothesis==6.163.0; extra == 'dev'
Requires-Dist: minisom==2.3.6; extra == 'dev'
Requires-Dist: mypy==2.3.0; extra == 'dev'
Requires-Dist: pandas-stubs==3.0.3.260530; (python_version >= '3.11') and extra == 'dev'
Requires-Dist: pandas==2.3.3; (python_version < '3.11') and extra == 'dev'
Requires-Dist: pandas==3.0.5; (python_version >= '3.11') and extra == 'dev'
Requires-Dist: pre-commit==4.6.1; extra == 'dev'
Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
Requires-Dist: pytest==9.1.1; extra == 'dev'
Requires-Dist: ruff==0.16.0; extra == 'dev'
Requires-Dist: scikit-learn==1.7.2; (python_version < '3.11') and extra == 'dev'
Requires-Dist: scikit-learn==1.9.0; (python_version >= '3.11') and extra == 'dev'
Requires-Dist: tomli==2.4.1; (python_version < '3.11') and extra == 'dev'
Requires-Dist: twine==7.0.0; extra == 'dev'
Requires-Dist: types-tqdm==4.69.0.20260728; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material==9.7.7; extra == 'docs'
Requires-Dist: mkdocs-redirects==1.2.2; extra == 'docs'
Requires-Dist: mkdocstrings-python==2.0.5; extra == 'docs'
Provides-Extra: examples
Requires-Dist: matplotlib>=3.8; extra == 'examples'
Requires-Dist: pandas>=2.0; extra == 'examples'
Requires-Dist: scikit-learn>=1.3; extra == 'examples'
Requires-Dist: seaborn>=0.13; extra == 'examples'
Provides-Extra: sklearn
Requires-Dist: scikit-learn>=1.4; extra == 'sklearn'
Description-Content-Type: text/markdown

# python-som

[![CI](https://github.com/andremsouza/python-som/actions/workflows/ci.yml/badge.svg)](https://github.com/andremsouza/python-som/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/python-som.svg)](https://pypi.org/project/python-som/)
[![Python versions](https://img.shields.io/pypi/pyversions/python-som.svg)](https://pypi.org/project/python-som/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/andremsouza/python-som/blob/master/LICENSE)

Implementation of Kohonen's 2-D self-organizing map. NumPy is the only dependency. Accepts NumPy
arrays, pandas DataFrames, polars, pyarrow, and anything else implementing the `__array__` protocol.

[Documentation](https://andremsouza.github.io/python-som/) ·
[Changelog](https://github.com/andremsouza/python-som/blob/master/CHANGELOG.md)

## Install

```bash
pip install python-som                  # requires Python 3.10+; NumPy is the only dependency
pip install "python-som[cli]"           # adds tqdm progress bars
pip install "python-som[sklearn]"       # adds the scikit-learn estimator adapter
pip install "python-som[examples]"      # adds matplotlib and seaborn, for the plots
```

Training can also use a compiled kernel, worth up to 2.4x on batch training. It is a separate
package rather than an extra, and it is picked up automatically once present:

```bash
pip install numba                       # note: numba currently requires numpy<2.5
```

Confirm it is active with `python_som.accelerated()`. See
[Speed up training](https://andremsouza.github.io/python-som/how-to/speed-up-training/).

## Quick start

```python
import numpy as np
import python_som

rng = np.random.default_rng(0)
data = rng.normal(size=(150, 4))

som = python_som.SOM(x=20, y=None, input_len=4, data=data, random_seed=42)
som.weight_initialization(mode="linear", data=data)
error = som.train(data, n_iteration=len(data), mode="batch")

umatrix = som.distance_matrix()
winner = som.winner(data[0])
```

The same map through the estimator interface, which `Pipeline` and `GridSearchCV` also understand:

```python
som.fit(data, n_iteration=len(data), mode="batch")
labels = som.predict(data)  # (n_samples,) flat node index
distances = som.transform(data)  # (n_samples, x*y)

som.save_npz("map.npz")  # models plus provenance, no pickle
som = python_som.SOM.load_npz("map.npz")
```

A full worked example with plots is in [examples/iris.py](https://github.com/andremsouza/python-som/blob/master/examples/iris.py) and in the
[getting-started guide](https://andremsouza.github.io/python-som/tutorial/first-map/).

![U-matrix of a SOM trained on Iris](https://raw.githubusercontent.com/andremsouza/python-som/master/docs/assets/iris.png)

## Features

* NumPy is the only runtime dependency; a fresh install is 69 MB across one package
* Batch training 23x to 31x faster than MiniSom and 26x to 94x faster than SOMPY, measured
* Optional compiled kernel via numba, used automatically when installed; `accelerated()` reports it
* Stepwise and batch training
* Random, random-sampling and linear (PCA) weight initialization
* Automatic selection of the map size ratio, from PCA
* Cyclic arrays, for toroidal maps
* Gaussian, bubble and Mexican hat neighborhood functions
* Custom decay functions
* Save and load a trained map without `pickle`, so loading one cannot execute code
* Provenance: every run records its seed, iteration count, error and library versions
* Works as a scikit-learn estimator: `fit`, `transform`, `predict`, and an adapter for `Pipeline`,
  `GridSearchCV` and `cross_val_score`
* Options accepted as plain strings or enums, with typos caught by a type checker
* Visualization support: U-matrix, activation matrix
* Supervised labelling, via the label map
* Fully type-annotated, with a `py.typed` marker

## Neighborhood functions

All three are functions of the distance between two nodes in the grid, `sqdist(c, i)` in Eq. (5) of
Kohonen (2013).

| Name | Shape | Notes |
| --- | --- | --- |
| `'gaussian'` | `exp(-r² / 2σ²)` | Strictly positive, monotonically decreasing. The default. |
| `'bubble'` | `1` for `max(dx, dy) ≤ σ`, else `0` | The truncated inner lobe of the Mexican hat. Uses the Chebyshev metric, so the region is a square. |
| `'mexicanhat'` | `(1 - u)·exp(-u)`, `u = r² / 2σ²` | Excitatory near the winner, inhibitory beyond it. Zero at `r = √2·σ`, minimum `-e⁻²` at `r = 2σ`. |

The Mexican hat takes negative values, so it **cannot be used with `mode='batch'`**: the batch
update of Kohonen Eq. (8) is a weighted mean whose denominator is not sign-definite for a signed
neighborhood function. Use `mode='random'` or `mode='sequential'`; `mode='batch'` raises a
`ValueError`.

See [Neighborhood functions](https://andremsouza.github.io/python-som/reference/neighborhood-functions/) for
the derivations, including why the Mexican hat is not an outer product of two 1-D wavelets.

## Upgrading

Two releases change numerical results. If you are reproducing a figure, pin the version that made
it.

**0.3.0** corrects several methodology defects, so results are not comparable with earlier versions.
In particular **`random_seed` no longer reproduces pre-0.3.0 maps**: the generator is now
per-instance rather than a call to `np.random.seed` on NumPy's global state. To reproduce older
figures, pin `python-som==0.2.0`.

**0.4.0** corrects linear initialization for data far from the origin. Its PCA previously went
through scikit-learn's `auto` solver, which forms a covariance matrix and loses precision when the
mean is large relative to the spread. On data offset by 1e7 the second explained variance was wrong
by 5.8%.
Near the origin the difference is floating-point noise. Timestamps, coordinates and absolute sensor
readings are the cases that were affected.

0.4.0 also **removed pandas and scikit-learn as runtime dependencies**. If you imported either
transitively through this package, depend on them directly, or install `python-som[examples]`.

**0.5.0** briefly made plain-string options emit a `DeprecationWarning`. **0.6.0 withdrew that**:
strings are permanent and 1.0.0 will not remove them. If you saw that warning, you can stop
migrating.

**0.7.0** makes batch training 20x to 40x faster by reorganising the same arithmetic. Because the
sums happen in a different order, trained weights differ from 0.6.1 by about 1e-15 relative. That is
far below anything a result depends on, and it does break an exact-equality check against a stored
map: pin `python-som==0.6.1` if you need one to match bit for bit. It also removes the neighborhood
kernel helpers from the private `python_som._core`, which had no callers outside the package.

Each change and the passage of Kohonen (2013) behind it is in the
[changelog](https://github.com/andremsouza/python-som/blob/master/CHANGELOG.md).

## Development

```bash
uv sync --all-extras
uv run pytest --cov          # tests and coverage
uv run ruff check .          # lint
uv run ruff format --check . # formatting
uv run mypy                  # type-check
uv run bandit -c pyproject.toml -r src/   # security checks
uv run pip-audit             # known vulnerabilities in the resolved set
uv run mkdocs serve          # docs, locally
pre-commit install           # optional, run the gates on commit
```

numba is not a declared extra: uv resolves every extra into one lockfile, so declaring it would cap
NumPy below 2.5 for the whole project. Install it alongside when working on the accelerated path,
with `uv run --with numba pytest tests/test_numba_kernel.py`.

If you use the SonarQube for IDE (SonarLint) VS Code extension, it will also apply Sonar's Python
rules locally; the ruff configuration is set up to cover most of the same ground.

### Benchmarks

Hand-run, never part of the test suite: a timing assertion on shared hardware measures noise.

```bash
uv run python benchmarks/bench_vs_minisom.py   # vs MiniSom, agreement verified before timing
cd asv_benchmarks && uv run --extra bench asv continuous master HEAD   # this package across commits
```

Results and the method behind them are in
[Comparison with MiniSom and SOMPY](https://andremsouza.github.io/python-som/explanation/comparison-with-som-libraries/).
The SOMPY comparison needs an interpreter of its own, since SOMPY cannot be imported on NumPy 2;
`benchmarks/bench_vs_sompy.py` prints the setup command and installs nothing.

## References

Based on:

Teuvo Kohonen,
Essentials of the self-organizing map,
Neural Networks,
Volume 37,
2013,
Pages 52-65,
ISSN 0893-6080,
<https://doi.org/10.1016/j.neunet.2012.09.018>

The Mexican hat neighborhood follows the lateral-interaction formulation in:

O. J. Vrieze,
Kohonen network,
in: Artificial Neural Networks: An Introduction to ANN Theory and Practice,
Lecture Notes in Computer Science, Volume 931,
Springer, Berlin, Heidelberg,
1995,
Pages 83-100,
<https://doi.org/10.1007/BFb0027024>

## License

MIT. See [LICENSE](https://github.com/andremsouza/python-som/blob/master/LICENSE).
