Mosaic Jupyter Widget
The Mosaic widget package provides a Jupyter widget for creating interactive Mosaic plots over Pandas and Polars data frames or DuckDB connections.
Installation
Install the widget with pip install mosaic-widget. Then you can import it in Jupyter with import mosaic_widget.
The PyPI package is at https://pypi.org/project/mosaic-widget/. The widget also works in Google Colab.
Using the Widget
After importing the widget with
from mosaic_widget import MosaicWidgetyou can initialize and show the widget with
widget = MosaicWidget()
widgetThe widget constructor take three arguments which are all optional.
spec, a Mosaic specification as a dictionary. This argument is optional and can be set later via thespectraitlet.con, a DuckDB connection. IfNone, the widget will create a connection to an in-memory database.data, a dictionary of data frames that should be added to the database connection. The keys of the dictionary are the table names.
A widget has a spec traitlet that can be used to set the Mosaic specification. A widget automatically updates when the specification changes. A widget also has a params traitlet, which updates automatically with params in the widget. The params are a dictionary from parameter name to the current value of the parameter and the predicate which can be used as the WHERE clause in a SQL query.
Example
In this example, we create a Mosaic plot over the Seattle weather dataset. This will render an interactive view of Seattle’s weather, including maximum temperature, amount of precipitation, and type of weather. You can try a live example on Google Colab.
Building a spec with the vgplot Python API
The vgplot Python API lets you build the specification programmatically instead of loading a YAML/JSON file. Load your data — a Polars or Pandas DataFrame (pl.read_csv), or a file source with vg.csv(...) / vg.parquet(...) — assign it to a variable, and pass that variable straight to a mark. It's picked up by its variable name (an in-memory DataFrame is registered as a table, a file source is inlined), so you can hand the view straight to MosaicWidget with no separate data argument:
import polars as pl
import vgplot as vg
from mosaic_widget import MosaicWidget
weather = pl.read_csv(
"https://uwdata.github.io/mosaic-datasets/data/seattle-weather.csv",
try_parse_dates=True,
)
view = vg.plot(
vg.dot(
weather,
x=vg.date_month_day("date"),
y="temp_max",
fill="weather",
r="precipitation",
fill_opacity=0.7,
),
vg.x_tick_format("%b"),
vg.width(680),
vg.height(300),
)
MosaicWidget(view)To register the data yourself instead, pass data={"weather": weather} to MosaicWidget and reference the table from a mark with vg.source("weather").
Loading a YAML/JSON spec
Alternatively, load an existing declarative specification and pass it to the widget as a dictionary:
import polars as pl
import yaml
from mosaic_widget import MosaicWidget
weather = pl.read_csv(
"https://uwdata.github.io/mosaic-datasets/data/seattle-weather.csv",
try_parse_dates=True,
)
# Load weather spec, remove data key to ensure load from the DataFrame
with open("weather.yaml") as f:
spec = yaml.safe_load(f)
spec.pop("data")
MosaicWidget(spec, data={"weather": weather})Listening to parameter changes
A widget's params traitlet updates automatically as the user interacts with the plot. Call observe on the widget to react to those changes. Here we build the spec with the vgplot API, add an interval selection to brush over, and print the params into an output widget:
from pprint import pprint
import ipywidgets as widgets
import polars as pl
import vgplot as vg
from mosaic_widget import MosaicWidget
weather = pl.read_csv(
"https://uwdata.github.io/mosaic-datasets/data/seattle-weather.csv",
try_parse_dates=True,
)
brush = vg.selection.intersect()
view = vg.plot(
vg.dot(
weather,
x=vg.date_month_day("date"),
y="temp_max",
fill="weather",
select=vg.interval_x(bind=brush),
),
vg.x_tick_format("%b"),
vg.width(680),
)
widget = MosaicWidget(view)
output = widgets.Output()
@output.capture(clear_output=True)
def handle_change(change):
pprint(change.new)
widget.observe(handle_change, names=["params"])
widgets.VBox([widget, output])Reading the Filtered Data
After the user interacts with the widget, you can read the current selections as SQL and fetch the filtered rows directly from Python:
widget.sql # 'SELECT * FROM "weather" WHERE ("weather" = \'sun\')'
widget.data().df() # pandas DataFrame of the currently filtered rowswidget.sql combines the active selection predicates from params with AND. widget.data() returns the lazy DuckDB relation for that query; materialize it with .df() (pandas), .pl() (Polars), .arrow(), or .fetchall().
widget.data() infers the source table from the spec's data entries and the data constructor argument. If those name more than one table, pass the table explicitly (widget.sql returns None in that case). The query applies every selection; pass filter_by with a selection name or a list of names to apply a subset:
widget.data("weather").df() # explicit source table
widget.data("weather", filter_by="range").df() # apply only the "range" selectionNote that in a cross-filtered view each chart skips its own selection, but widget.data() applies all of them, so a chart may show more rows than widget.data() returns.