Skip to main content

dtour: Python

This is the dtour Python package: a data-generic anywidget that drops into Jupyter and marimo notebooks.

Install

pip install dtour

Optional extras enable additional tour generators and metrics:

pip install "dtour[umap]"   # umap_little_tour() + UMAP-based tours (umap-learn, numba)
pip install "dtour[tsne]"   # attraction–repulsion tours (openTSNE)
pip install "dtour[pymde]"  # PyMDE-based tours
pip install "dtour[cev]"    # confusion metric (cev-metrics)
pip install "dtour[mcp]"    # the MCP server (see below)

Quick start

Load a dataset and instantiate the widget:

import dtour
import polars as pl

df = pl.read_parquet("https://github.com/uwdata/mosaic/raw/main/data/athletes.parquet")

dtour.Widget(data=df)

Widget API

dtour.Widget(
    data=...,  # the data the tour was computed from: DataFrame, Arrow table, numpy array, or file path
    tour=...,  # TourResult from any tour generator
    # display
    height=720,  # canvas height in pixels
    preview_count=4,  # keyframe previews: 2–32
    preview_size="auto",  # "auto" | "small" | "medium" | "large"
    preview_padding=12.0,  # gap between previews
    preview_keyframe_numbers="auto",  # "auto" | "visible" | "hidden"
    preview_label_content="auto",  # "auto" | "description" | "loadings"
    preview_label_visibility="auto",  # "auto" | "visible" | "interactive" | "hidden"
    # point style
    point_size="auto",  # point radius or "auto"
    point_opacity="auto",  # point alpha or "auto"
    min_point_size=2.0,  # smallest automatic point size in px: 1–20
    point_color=[0.25, 0.5, 0.9],  # default RGB color
    point_color_by=None,  # column name, or [x, y] numeric columns for a 2D colormap
    point_color_map_2d="schumann",  # 2D colormap for a column pair
    color_map={},  # label → color mapping (see build_color_map())
    # tour playback
    tour_by="dimensions",  # "dimensions" | "pca" | "parameter"
    tour_position=0.0,  # 0–1 position along the tour
    tour_playing=False,  # auto-play on load
    tour_speed=1.0,  # playback speed multiplier
    tour_direction="forward",  # "forward" | "backward"
    tour_slider_spacing="equal",  # "equal" | "geodesic"
    tour_slider_visibility="visible",  # "visible" | "subtle" | "hidden"
    tour_dimensions=[],  # columns in the tour (set by the tour; without one, the columns checked in the toolbar; two columns show a static scatter)
    link=None,  # widgets in the same browser with the same id share their selection (see dtour.link())
    # camera
    camera_pan_x=0.0,
    camera_pan_y=0.0,
    camera_zoom=1 / 1.5,
    centering="midrange",  # "midrange" | "mean"
    # mode & appearance
    tour_traversal="guided",  # "guided" | "manual" | "grand"
    show_legend=True,  # show/hide color legend
    legend_order=None,  # labels the legend lists first, in this order
    show_axes=False,  # show/hide the axis biplot in guided mode
    theme_mode="dark",  # "light" | "dark" | "system"
    # tooltip
    tooltip_columns=None,  # columns the point tooltip lists, in order; [] hides it
    tooltip_image_fit="contain",  # "contain" | "cover" for image columns
)

String columns of image URLs (.png, .jpg, .jpeg, .webp) or data:image/... URIs show as images in the tooltip.

All settings are exposed as traitlets, so they can be read, set, and observed live from the notebook.

Widget methods

w = dtour.Widget(data=X, tour=tour)
w.set_data(df)  # load new data
w.set_data(df, tour)  # load new data with its tour
w.set_tour(tour)  # set tour keyframes
w.set_metrics(metrics)  # display radial quality charts
w.select([0, 1, 2])  # select points by index
w.clear_selection()  # clear selection
dtour.link(w, other)  # keep the selections of widgets showing the same points in sync

Without a tour, data with two numeric columns, or tour_dimensions naming two columns, shows a static scatter of them, with the first column on the x-axis. Both axes share one scale, so a 2D embedding keeps its shape. Next to a tour, this compares, e.g., a UMAP with the PCA space it was computed from:

tour = dtour.Widget(df, dtour.little_tour(df[pc_cols]), point_color_by="cell_type")
umap = dtour.Widget(df, tour_dimensions=["umap_x", "umap_y"], point_color_by="cell_type")
dtour.link(tour, umap)

Tour computation

dtour ships with two families of tour generators. Hyperdimensional tours show one high-dimensional space from different angles:

# PCA: cycles through consecutive pairs of principal components
tour = dtour.little_tour(
    X,  # (n_samples, n_features) array or DataFrame
    n_components=None,  # defaults to min(n_features, 10)
)

# UMAP to n_components, then a little tour over the embedding (pip install dtour[umap])
tour = dtour.umap_little_tour(X, n_components=10, umap_kwargs=None)

# Laplacian Eigenmaps: each keyframe adds one more eigenvector (global → local)
tour = dtour.le_tour(
    X,
    n_frames=8,  # computes n_frames + 1 eigenvectors
    n_neighbors=15,
    labels=None,  # class labels: same-label edges attract, cross-label edges repel
    discriminative=False,  # with labels: order eigenvectors by class separation (spectral Fisher)
    subsample=None,  # fit on a subsample and extend to the rest (large data)
)

Sequential tours morph between aligned 2D embeddings of the same points, e.g. across time points, hyperparameters, or models:

# One embedding per dataset, each warm-started from the previous one
tour = dtour.sequential_tour([X_1, X_2, X_3], method="umap")  # "umap" | "tsne" | "pymde" | callable

# Sweep from attraction (LE-like) to repulsion (t-SNE) (pip install dtour[tsne])
tour = dtour.attraction_repulsion_tour(X, n_frames=4)

# Optimize all embeddings jointly with UMAP's AlignedUMAP (pip install dtour[umap])
tour = dtour.aligned_umap_tour([X_1, X_2, X_3])

All generators return a TourResult with .keyframes (list of p×2 float32 arrays), .n_keyframes, .n_dims, .tour_family, and .save(path) / TourResult.load(path) for persistence. little_tour projects the input columns. All other tours project their own .embedding, which the widget adds to the data. Either way, pass the data you computed the tour from, plus any columns to color by:

tour = dtour.le_tour(df.select(features), n_frames=8)
dtour.Widget(df, tour, point_color_by="cell_type")

Quality metrics

Compute per-keyframe quality scores and display them as radial bar charts on the circular slider:

metrics = dtour.compute_metrics(
    X,  # (n_samples, n_features) float32
    keyframes=tour.keyframes,  # from TourResult
    labels=None,  # cluster/class labels for supervised metrics
    metrics=None,  # list of metric names; defaults to ["silhouette", "trustworthiness"]
    k=7,  # neighbors for neighborhood-based metrics
    subsample=None,  # int, per-metric dict, or None for built-in defaults
    exclude_labels=None,  # label values to exclude from label-based metrics
)

w = dtour.Widget(data=X, tour=tour)
w.set_metrics(metrics)

Supported metrics: silhouette, trustworthiness, calinski_harabasz, neighborhood_hit, confusion (require labels), hdbscan_score (unsupervised). confusion needs the optional cev extra (pip install dtour[cev]).

Color maps

Build a label → color mapping that matches the engine's auto-assignment:

cmap = dtour.build_color_map(
    labels=sorted_unique_labels,  # same order the engine sees
    theme=None,  # "light" | "dark" | None (theme-aware dicts)
    overrides=None,  # per-label color overrides
)
dtour.Widget(data=df, point_color_by="cluster", color_map=cmap)

MCP server

dtour-mcp is an MCP server that lets an AI assistant show tours right in the chat. Ask, e.g., "Show me a UMAP tour of ~/data/cells.csv colored by cell_type". The server reads the file, computes the tour, and the viewer appears inline as an MCP App.

It has one tool, visualize, which takes:

  • path: a CSV, TSV, Parquet, or Arrow file, or an http(s) URL to one
  • tour: "pca" (default), "le" (Laplacian Eigenmaps), or "umap"
  • columns: the numeric columns to tour (default: all). Two columns show as a static scatter
  • dimensions: how many dimensions the tour moves through, e.g., 4 for a 4D UMAP. 2 shows a static 2D embedding ("pca" or "umap")
  • sample: a number of rows to sample, for large data
  • settings: viewer settings, as in build_dtour_metadata, e.g. {"point_color_by": "cell_type"}
  • views: up to 4 views of the same rows, shown side by side with linked selections. Each view can set its own tour, columns, dimensions, and settings; the rest come from the call

For example, "Compare a PCA tour of ~/data/cells.csv with a 2D UMAP" leads to a call like visualize(path, views=[{"tour": "pca"}, {"tour": "umap", "dimensions": 2}]). To compare two embeddings of the same items, e.g., from a text and a vision model, store both in one file and give each view its columns. Side by side, the legends start hidden; the toolbar shows them.

Rows with missing values in the tour columns are dropped, and the columns are scaled to the same range. In apps without MCP Apps support, like Claude Code, the tool returns a link that opens the viewer in the browser. The viewer follows the app's light or dark theme unless settings sets theme_mode.

As you explore, the viewer tells the assistant what you see: the traversal mode, the color column, selected legend labels, and for selected points, how their column means and categories differ from the other rows. So you can ask, e.g., "What's special about the points I selected?"

What leaves your computer: the server reads files and computes tours locally, and the viewer loads the data from a server on 127.0.0.1. If the app blocks that, the viewer receives the file through the app instead. The assistant sees the tool's arguments and results, like file paths and column names, and the summaries above. A summary of a few selected points shows their values; for a single point, it shows that row's values.

In Claude Desktop, download dtour.mcpb and open it to install the extension. Claude Desktop sets up Python and the dependencies itself.

In Claude Code, install the plugin, which also includes the dtour skill: /plugin install dtour --marketplace flekschas/dtour. It needs uv.

Other MCP clients can run the server with uv, e.g.:

{
  "mcpServers": {
    "dtour": {
      "command": "uvx",
      "args": ["--from", "dtour[mcp,umap]", "dtour-mcp"]
    }
  }
}

Example notebooks

The notebooks/ directory has self-contained marimo demos on real datasets (Fashion-MNIST, a developing-brain scRNA-seq atlas, a ShareGPT4V × COCO image embedding, and immune-cell CyTOF markers). Each notebook declares its own dependencies and downloads its data on first run, so you can open one straight from a checkout:

uvx marimo edit --sandbox notebooks/demo_spectral.py

See the notebooks README for a description of each and more ways to run them.

Development

dtour loads the widget's JavaScript bundle from src/dtour/static/widget.js, which is not checked in. Build it, together with the @dtour/scatter and @dtour/viewer packages it bundles, from the repo root:

pnpm install
pnpm build:widget

Rebuild after changing any frontend code. Importing dtour warns when the bundle is older than the frontend sources.

Edit a notebook against the local source with all dev extras:

uv run --extra dev marimo edit notebooks/demo_immune_cell_markers.py

Run the MCP server from the local source, e.g., in Claude Desktop's config with "command": "uv" and "args": ["run", "--directory", "<repo>/packages/python", "--extra", "mcp", "--extra", "umap", "dtour-mcp"]. To try it in a browser, run uv run --extra mcp dtour-mcp --http 3001 and connect ext-apps' basic-host to http://localhost:3001/mcp.

Build the Claude Desktop extension with pnpm build:mcpb from the repo root. It installs the dtour release named in mcpb/pyproject.toml from PyPI; with pnpm build:mcpb --local, it includes a wheel of your checkout instead. Open mcpb/dist/dtour.mcpb to install it.

Run the tests:

uv run --extra dev --extra cev pytest

Smoke-test the demo notebooks against your local working copy (from the repo root):

pnpm test:notebooks                 # all notebooks
pnpm test:notebooks demo_spectral   # a single notebook

Metadata

Release files for dtour 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dtour 0.5.1
File Size Uploaded
dtour-0.5.1.tar.gz 1.3 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for dtour 0.5.1
File Interpreter ABI Platform
dtour-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 2.2 MB

Release files / dtour-0.5.1.tar.gz

Download URL dtour-0.5.1.tar.gz
Size 1.3 MB
Tags Source
SHA-256 checksum
How to use checksums
6a9750e164830fafdd517d69a4e50880c9ec5db7970d3ea46947b9dd8f351fa1
BLAKE2b-256 checksum
How to use checksums
ef7825ec70c91b7d5cf0872f01869d8cd567d1e5207f097eb15c05a41bd930f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / dtour-0.5.1-py3-none-any.whl

Download URL dtour-0.5.1-py3-none-any.whl
Size 943.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
208867a72c05350584d5a6d2ddef5e282d6011ec2ed02f59b4636ac1537ffc78
BLAKE2b-256 checksum
How to use checksums
26a9031f67a78e4d8c0291783680354e054592f6aa324e976dfd6692d06b3541
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 release files

0.5.0

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page