
Source: Mastering Shiny, ch. 19

session, data, results, …)So why isn’t it enough here?
@reactive.calc that filters the data"filter_total_bill" isn’t a variable: it’s a string shipped to the browser and tracked in Shiny’s session-wide input list, and Python’s scope has no jurisdiction over itinput_slider()s both named "filter_total_bill": Shiny can’t tell them apartA module is a function that:
@moduleCall it like a normal function, plus an id: scatter_panel("bill_depth_mm_bill_length_mm", data=dat, x="bill_depth_mm", y="bill_length_mm").
Note
Shiny Core has the same idea, split into @module.ui / @module.server. We’ll stick to Express here.
One scatterplot panel with its own “Points” vs “Density” toggle, built once and used once per pair of columns:
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 550
from itertools import combinations
from palmerpenguins import load_penguins
from plotnine import aes, geom_bin2d, geom_point, ggplot, theme_minimal
from shiny.express import module, render, ui
dat = load_penguins().dropna()
num_cols = dat.select_dtypes("float64").columns.tolist()
@module
def scatter_panel(input, output, session, data, x, y):
ui.input_radio_buttons("view", None, choices=["Points", "Density"], inline=True)
@render.plot
def plot():
p = ggplot(data, aes(x=x, y=y)) + theme_minimal()
if input.view() == "Points":
p = p + geom_point(aes(color="species"), alpha=0.6)
else:
p = p + geom_bin2d(bins=20)
return p
ui.page_opts(title="Scatterplot combinations", fillable=True)
with ui.layout_columns(col_widths=6):
for x, y in combinations(num_cols, 2):
with ui.card():
ui.card_header(f"{x} vs {y}")
scatter_panel(f"{x}_{y}", data=dat, x=x, y=y)view id is namespaced by its own f"{x}_{y}" instance idx, y, and data are just arguments, captured normally on each callimport it into any apppip install it and use your moduleshinywidgets and shinychat are built: a module (or a few) packaged up for reuse across projects
SciPy 2026. chendaniely/scipy-2026-shiny