Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cc33f65
Moved ui elements to plotly plot
lucasimi May 14, 2025
36560bf
Fixed format
lucasimi May 14, 2025
266d67c
Added feature button
lucasimi May 15, 2025
8e1fb7b
Fixed trace colors
lucasimi May 15, 2025
8f03130
Removed comments
lucasimi May 15, 2025
1c76634
Updated example
lucasimi May 16, 2025
79dcb41
Added dev dependency for CI
lucasimi May 16, 2025
0467475
Removed deprecated imports
lucasimi May 16, 2025
7f80242
Removed calls to plt.show
lucasimi May 16, 2025
050b074
Added color column and cmap selection
lucasimi May 16, 2025
4efb86e
simplified buttons code
lucasimi May 17, 2025
586ee8d
Improved docs
lucasimi May 17, 2025
5ba6732
Refectored plot construction
lucasimi May 18, 2025
8fc4a23
Minor improvements
lucasimi May 18, 2025
8a3b335
Fixed colorscales using rgb conversion
lucasimi May 18, 2025
c647003
Improved buttons
lucasimi May 19, 2025
f851822
Fixed type hints for python 3.8
lucasimi May 19, 2025
6755709
Marked method as deprecated
lucasimi May 19, 2025
099a038
Added colormaps
lucasimi May 19, 2025
a7a8e91
Fixed colorbar
lucasimi May 20, 2025
f2ba09e
Fixed optional argument
lucasimi May 20, 2025
42f1165
Fixed rendering of notebooks. Extended title argument for passing lists
lucasimi May 20, 2025
d0e48c4
Added full test
lucasimi May 20, 2025
d9d1f58
Improved ui
lucasimi May 20, 2025
e8516bf
Transitional change
lucasimi May 20, 2025
5c16ed5
Fixed changes
lucasimi May 20, 2025
13737e5
Updated version
lucasimi May 20, 2025
a7bc234
Updated version
lucasimi May 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ and in the

- **Flexible visualization**

Multiple visualization backends supported (e.g., Plotly, Matplotlib) for
Multiple visualization backends supported (Plotly, Matplotlib, PyVis) for
generating high-quality Mapper graph representations with adjustable
layouts and styling.

Expand Down
2 changes: 1 addition & 1 deletion app/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ numpy>=1.25.2,<2.0.0
scikit-learn>=1.5.0,<1.6.0
umap-learn>=0.5.7,<0.6.0
pandas>=2.1.0,<3.0.0
tda-mapper>=0.10.0,<0.11.0
tda-mapper>=0.11.0,<0.12.0
plotly>=6.0.0,<7.0.0
115 changes: 46 additions & 69 deletions app/streamlit_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from sklearn.decomposition import PCA
from umap import UMAP

from tdamapper._plot_plotly import _marker_size
from tdamapper.core import aggregate_graph
from tdamapper.cover import BallCover, CubicalCover
from tdamapper.learn import MapperAlgorithm
from tdamapper.plot import MapperPlot
Expand Down Expand Up @@ -100,6 +102,16 @@

V_CMAP_TWILIGHT = "Twilight (Cyclic)"

V_CMAPS = {
V_CMAP_JET: "Jet",
V_CMAP_VIRIDIS: "Viridis",
V_CMAP_CIVIDIS: "Cividis",
V_CMAP_SPECTRAL: "Spectral",
V_CMAP_PORTLAND: "Portland",
V_CMAP_HSV: "HSV",
V_CMAP_TWILIGHT: "Twilight",
}

GIT_REPO_URL = "https://github.com/lucasimi/tda-mapper-python"

ICON_URL = f"{GIT_REPO_URL}/raw/main/docs/source/logos/tda-mapper-logo-icon.png"
Expand Down Expand Up @@ -165,14 +177,6 @@ def _fix_data(data):
return df


def _get_dim(fig):
dim = 2
for trace in fig.data:
if "3d" in trace.type:
dim = 3
return dim


def _get_graph_no_attribs(graph):
graph_no_attribs = nx.Graph()
graph_no_attribs.add_nodes_from(graph.nodes())
Expand Down Expand Up @@ -561,50 +565,6 @@ def plot_agg_input_section():
return agg, agg_name


def plot_cmap_input_section():
cmap_type = st.selectbox(
"Colormap",
options=[
V_CMAP_JET,
V_CMAP_VIRIDIS,
V_CMAP_CIVIDIS,
V_CMAP_PORTLAND,
V_CMAP_SPECTRAL,
V_CMAP_HSV,
V_CMAP_TWILIGHT,
],
)
cmap = None
if cmap_type == V_CMAP_JET:
cmap = "Jet"
elif cmap_type == V_CMAP_VIRIDIS:
cmap = "Viridis"
elif cmap_type == V_CMAP_CIVIDIS:
cmap = "Cividis"
elif cmap_type == V_CMAP_PORTLAND:
cmap = "Portland"
elif cmap_type == V_CMAP_SPECTRAL:
cmap = "Spectral"
elif cmap_type == V_CMAP_HSV:
cmap = "HSV"
elif cmap_type == V_CMAP_TWILIGHT:
cmap = "Twilight"
return cmap


def plot_color_input_section(df_X, df_y):
X_cols = list(df_X.columns)
y_cols = list(df_y.columns)
col_feat = st.selectbox(
"Color",
options=y_cols + X_cols,
)
if col_feat in X_cols:
return df_X[col_feat].to_numpy(), col_feat
elif col_feat in y_cols:
return df_y[col_feat].to_numpy(), col_feat


@st.cache_data(
hash_funcs={
"networkx.classes.graph.Graph": lambda g: _encode_graph(
Expand Down Expand Up @@ -654,15 +614,13 @@ def mapper_plot_section(mapper_graph):
hash_funcs={"tdamapper.plot.MapperPlot": lambda mp: mp.positions},
show_spinner="Rendering Mapper",
)
def compute_mapper_fig(
mapper_plot, colors, node_size, cmap, _agg, agg_name, colors_feat
):
def compute_mapper_fig(mapper_plot, colors, node_size, cmap, _agg, agg_name):
logger.info("Generating Mapper figure")
mapper_fig = mapper_plot.plot_plotly(
colors,
node_size=node_size,
agg=_agg,
title=f"{agg_name} of {colors_feat}",
title=[f"{agg_name} of {c}" for c in colors.columns],
cmap=cmap,
width=600,
height=600,
Expand All @@ -673,23 +631,17 @@ def compute_mapper_fig(
def mapper_figure_section(df_X, df_y, mapper_plot):
st.header("🎨 Plot")
agg, agg_name = plot_agg_input_section()
cmap = plot_cmap_input_section()
colors, colors_feat = plot_color_input_section(df_X, df_y)
node_size = st.slider("Node size", min_value=0.1, max_value=10.0, value=1.0)
colors = pd.concat([df_y, df_X], axis=1)
mapper_fig = compute_mapper_fig(
mapper_plot,
colors=colors,
node_size=node_size,
node_size=1.0,
_agg=agg,
cmap=cmap,
cmap=["Jet", "Viridis", "Cividis"],
agg_name=agg_name,
colors_feat=colors_feat,
)
dim = _get_dim(mapper_fig)
mapper_fig.update_layout(
dragmode="orbit" if dim == 3 else "pan",
uirevision="constant",
margin=dict(b=0, l=0, r=0, t=0),
margin=dict(b=5, l=5, r=5, t=5),
)
mapper_fig.update_xaxes(
showline=False,
Expand All @@ -699,16 +651,41 @@ def mapper_figure_section(df_X, df_y, mapper_plot):
scaleanchor="x",
scaleratio=1,
)

return mapper_fig


def mapper_rendering_section(mapper_graph, mapper_fig):
def _compute_colors_agg(mapper_plot, df_X, df_y, col_feat, agg):
X_cols = list(df_X.columns)
y_cols = list(df_y.columns)
colors = np.array([])
if col_feat in X_cols:
colors = df_X[col_feat].to_numpy()
elif col_feat in y_cols:
colors = df_y[col_feat].to_numpy()
return aggregate_graph(colors, mapper_plot.graph, agg)


def _edge_colors(mapper_plot, df_X, df_y, col_feat, agg):
colors_avg = []
colors_agg = _compute_colors_agg(mapper_plot, df_X, df_y, col_feat, agg)
for edge in mapper_plot.graph.edges():
c0, c1 = colors_agg[edge[0]], colors_agg[edge[1]]
colors_avg.append(c0)
colors_avg.append(c1)
colors_avg.append(c1)
return colors_avg


def mapper_rendering_section(mapper_fig):
config = {
"scrollZoom": True,
"displaylogo": False,
"modeBarButtonsToRemove": ["zoom", "pan"],
}
st.plotly_chart(mapper_fig, use_container_width=True, config=config)
st.plotly_chart(
mapper_fig, use_container_width=True, config=config, key="mapper_plot"
)


def data_summary_section(df_X, df_y, mapper_graph):
Expand Down Expand Up @@ -826,7 +803,7 @@ def main():
with col_0:
data_summary_section(df_X, df_y, mapper_graph)
with col_1:
mapper_rendering_section(mapper_graph, mapper_fig)
mapper_rendering_section(mapper_fig)
col_2, col_3 = st.columns([1, 3])
with col_2:
data_download_button(df_X, df_y)
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from sklearn.decomposition import PCA

import tdamapper as tm
from tdamapper.clustering import TrivialClustering
from tdamapper.core import TrivialClustering


def _segment(cardinality, dimension, noise=0.1, start=None, end=None):
Expand Down Expand Up @@ -81,7 +81,7 @@ def run_gm(X, n, p):

def run_tm(X, n, p):
t0 = time.time()
tm.core.MapperAlgorithm(
tm.learn.MapperAlgorithm(
cover=tm.cover.CubicalCover(
n_intervals=n,
overlap_frac=p,
Expand Down
16 changes: 12 additions & 4 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,27 @@ Core features

- **Efficient construction**

Leverages optimized spatial search techniques and parallelization to accelerate the construction of Mapper graphs, supporting the analysis of high-dimensional datasets.
Leverages optimized spatial search techniques and parallelization to
accelerate the construction of Mapper graphs, supporting the analysis of
high-dimensional datasets.

- **Scikit-learn integration**

Provides custom estimators that are fully compatible with scikit-learn's API, enabling seamless integration into scikit-learn pipelines for tasks such as dimensionality reduction, clustering, and feature extraction.
Provides custom estimators that are fully compatible with scikit-learn's
API, enabling seamless integration into scikit-learn pipelines for tasks
such as dimensionality reduction, clustering, and feature extraction.

- **Flexible visualization**

Multiple visualization backends supported (e.g., Plotly, Matplotlib) for generating high-quality Mapper graph representations with adjustable layouts and styling.
Multiple visualization backends supported (Plotly, Matplotlib, PyVis) for
generating high-quality Mapper graph representations with adjustable
layouts and styling.

- **Interactive app**

Provides an interactive web-based interface (via Streamlit) for dynamic exploration of Mapper graph structures, offering real-time adjustments to parameters and visualizations.
Provides an interactive web-based interface (via Streamlit) for dynamic
exploration of Mapper graph structures, offering real-time adjustments to
parameters and visualizations.


Background
Expand Down
21 changes: 15 additions & 6 deletions docs/source/notebooks/circles.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
cover=CubicalCover(n_intervals=10, overlap_frac=0.3), clustering=DBSCAN()
)
graph = mapper.fit_transform(X, y)
print(f"nodes: {len(graph.nodes())}, edges: {len(graph.edges())}")

# %% [markdown]
# ### Visualization
Expand All @@ -92,9 +93,15 @@
# %%
plot = MapperPlot(graph, dim=2, iterations=60, seed=42)

fig = plot.plot_plotly(colors=labels, cmap="jet", agg=np.nanmean, width=600, height=600)
fig = plot.plot_plotly(
colors=labels,
cmap=["jet", "viridis", "cividis"],
agg=np.nanmean,
width=600,
height=600,
)

fig.show(config={"scrollZoom": True})
fig.show(config={"scrollZoom": True}, renderer="notebook_connected")
# fig.write_image("circles_mean.png", width=500, height=500)

# %% [markdown]
Expand All @@ -107,14 +114,16 @@
# data where such ambiguity is common.

# %%
plot.plot_plotly_update(
fig,

fig = plot.plot_plotly(
colors=labels,
cmap="viridis",
cmap=["jet", "viridis", "cividis"],
agg=np.nanstd,
width=600,
height=600,
)

fig.show(config={"scrollZoom": True})
fig.show(config={"scrollZoom": True}, renderer="notebook_connected")
# fig.write_image("circles_std.png", width=500, height=500)

# %% [markdown]
Expand Down
9 changes: 5 additions & 4 deletions docs/source/notebooks/digits.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
)

graph = mapper.fit_transform(X, y)
print(f"nodes: {len(graph.nodes())}, edges: {len(graph.edges())}")

# %% [markdown]
# ### Visualization
Expand Down Expand Up @@ -100,15 +101,15 @@ def mode(arr):

fig = plot.plot_plotly(
colors=labels,
cmap="jet",
cmap=["jet", "viridis", "cividis"],
agg=mode,
title="mode of digits",
width=600,
height=600,
node_size=0.5,
)

fig.show(config={"scrollZoom": True})
fig.show(config={"scrollZoom": True}, renderer="notebook_connected")

# %% [markdown]
# We also color the nodes by the **entropy** of their digit labels, which
Expand All @@ -131,15 +132,15 @@ def entropy(arr):

fig = plot.plot_plotly(
colors=labels,
cmap="viridis",
cmap=["jet", "viridis", "cividis"],
agg=entropy,
title="entropy of digits",
width=600,
height=600,
node_size=0.5,
)

fig.show(config={"scrollZoom": True})
fig.show(config={"scrollZoom": True}, renderer="notebook_connected")

# %% [markdown]
# ### Identifying high-entropy
Expand Down
4 changes: 2 additions & 2 deletions docs/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ Development

pip install git+https://github.com/lucasimi/tda-mapper-python

- To install from the latest commit of develop branch
- To install from the latest commit of a branch

.. code:: bash

pip install git+https://github.com/lucasimi/tda-mapper-python@develop
pip install git+https://github.com/lucasimi/tda-mapper-python@[name-of-the-branch]


How To Use
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "tda-mapper"
version = "0.10.0"
version = "0.11.0"
description = "A simple and efficient Python implementation of Mapper algorithm for Topological Data Analysis"
readme = "README.md"
authors = [{ name = "Luca Simi", email = "lucasimi90@gmail.com" }]
Expand Down Expand Up @@ -49,6 +49,7 @@ dev = [
"black[jupyter]",
"isort",
"flake8",
"nbformat>=4.2.0",
]

[project.urls]
Expand Down
Loading