The Problem

High-dimensional datasets hide structure that standard clustering misses. The Mapper algorithm builds a graph that captures topological shape—clusters, loops, and transitions—but implementations are often slow and don't integrate with existing ML tooling. tda-mapper-python addresses this with an optimized, scikit-learn-compatible library.

What This Does

The library implements the full Mapper pipeline: lens functions (lens_pca, lens_umap in src/tdamapper/app.py), covers (BallCover, CubicalCover in src/tdamapper/cover.py), clustering (TrivialClustering in src/tdamapper/learn.py), and graph construction (mapper_graph in src/tdamapper/core.py). It includes three visualization backends—Plotly, Matplotlib, PyVis—under src/tdamapper/plot_backends/.

A Streamlit web app (app/streamlit_app.py) provides interactive parameter exploration. The package also ships benchmark scripts (benchmarks/benchmark.py) and 23 test files covering units, benchmarks, and integration with scikit-learn.

How It Is Wired

Execution starts at main in src/tdamapper/app.py:1032, which reaches 135 functions. The CLI path calls run in src/tdamapper/_run_app.py, which reaches 136 functions. The core pipeline flows: run_mapper (reaches 68 functions) → fit_transform (called from 27 places) → MapperAlgorithm (called from 20 places) → mapper_connected_components (9 callers).

The most-connected hub is src/tdamapper/cover.py—31 functions, 7 classes, called from 9 files. src/tdamapper/utils/vptree_flat/common.py follows with 16 functions called from 15 files; it implements the vantage-point tree spatial index that powers efficient neighbor searches. src/tdamapper/core.py owns graph assembly and clustering orchestration.

The code touches the outside world minimally: one function runs an external command, two read/write files. The module graph shows no circular dependencies—13 import edges across 60 internal modules—so changing one component won't cascade through cycles.

How To Use It

git clone https://github.com/moses-y/tda-mapper-python
cd tda-mapper-python
pip install -e .          # from pyproject.toml
python -m tdamapper.app   # CLI entry point
streamlit run app/streamlit_app.py  # web UI

Configuration happens through the CLI or Streamlit UI—no required environment variables. The Makefile provides convenience targets for testing and benchmarking. Docker users can build from app/Dockerfile.

Real-World Use

from tdamapper import MapperAlgorithm
from tdamapper.cover import BallCover
from tdamapper.learn import TrivialClustering

mapper = MapperAlgorithm(
    lens='pca',          # or 'umap', or custom
    cover=BallCover(radius=0.3),
    clustering=TrivialClustering()
)
graph = mapper.fit_transform(X)  # returns networkx graph

This fits into pipelines where you need to visualize cluster topology before choosing a segmentation strategy—e.g., customer segmentation from high-dimensional behavioral data.

Code Health & Issues

Static analysis found 11 issues (2 high, 9 medium):

  • High - Duplicated code blocks: 174 repeated 6-line blocks across 27 files, notably in docs/source/notebooks/ and tests/example.py. Extract shared helpers.
  • High - Deep nesting (depth 8) in benchmarks/benchmark.py, src/tdamapper/_common.py, src/tdamapper/plot_backends/plot_plotly.py. Use early returns.
  • Medium - File opened without context manager in tests/test_unit_app.py.
  • Medium - Oversized files: app/streamlit_app.py, src/tdamapper/app.py (834 lines), plot_plotly.py.
  • Medium - High branching density in docs/source/notebooks/cover.py (24 branches over 79 lines).

SDLC gaps: no lockfile (non-reproducible builds), GitHub Actions pinned to mutable tags (use SHA pins), no Dependabot, Docker base image unpinned, no non-root user in container, no dependency vulnerability scan in CI.

The Bottom Line

A solid, well-tested Mapper implementation with clean module boundaries and no circular dependencies. The main risks are maintainability (duplication, large files) and supply-chain hygiene (unpinned dependencies, mutable CI tags). Use it if you need topological data analysis in a scikit-learn workflow; fix the lockfile and CI pins before production.