Learn how to use Marimo, the reactive Python notebook.
Marimo is a reactive Python notebook that runs as pure Python files (.py), not JSON blobs (.ipynb).
Reactive Execution π
Pure Python Files π
.py filesReproducible π―
Interactive π¨
| Feature | Jupyter | Marimo |
|---|---|---|
| File format | .ipynb (JSON) |
.py (Python) |
| Git-friendly | β Messy diffs | β Clean diffs |
| Reactivity | β Manual re-runs | β Automatic updates |
| Hidden state | β οΈ Common issue | β Impossible |
| Execution order | β οΈ Can be confusing | β Always clear |
| IDE support | β οΈ Limited | β Full Python support |
| Sharing | .ipynb file |
.py file or HTML |
Use Marimo when:
Use Jupyter when:
.ipynb filesFor this course: We use Marimo because itβs better for learning and collaboration!
uv run marimo edit example_notebooks/01_python_basics.py
What happens:
uv run marimo edit my_analysis.py
If the file doesnβt exist, Marimo creates it for you.
uv run marimo run notebook.py
This executes the notebook and shows outputs, but you canβt edit cells.
When you open a Marimo notebook, youβll see:
βββββββββββββββββββββββββββββββββββββββββββ
β [βΆ] Run All [+] Cell [πΎ] Save β β Toolbar
βββββββββββββββββββββββββββββββββββββββββββ€
β β
β Cell 1: Markdown β β Markdown cell
β # My Data Analysis β
β β
βββββββββββββββββββββββββββββββββββββββββββ€
β β
β Cell 2: Python Code β β Code cell
β import polars as pl β
β df = pl.read_csv("data.csv") β
β β
β βββββββββββββββββββββββββ β
β β Output: 100 rows Γ 5 β β β Cell output
β βββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ€
β β
β Cell 3: Interactive Plot β β Another cell
β ... β
βββββββββββββββββββββββββββββββββββββββββββ
β β
β π Files π Variables βοΈ Settings β β Sidebar
βββββββββββββββββββββββββββββββββββββββββββ
B (below current cell) or A (above current cell)Code Cell:
import polars as pl
df = pl.read_csv("data/raw/students.csv")
df.head()
Markdown Cell:
# My Analysis
This is a **markdown** cell with _formatting_.
To make a cell Markdown:
python by default)markdownAutomatic execution:
Shift + Enter to force run and move to next cellCtrl/Cmd + Enter to run and stay in current cellWhat gets re-run:
Outputs appear below the cell:
This is Marimoβs superpower! Letβs see it in action.
Cell 1:
import marimo as mo
slider = mo.ui.slider(1, 100, value=50)
slider
Cell 2:
number = slider.value
print(f"The number is: {number}")
What happens:
Marimo tracks dependencies:
# Cell A
x = 10
# Cell B (depends on A)
y = x * 2
# Cell C (depends on B)
z = y + 5
If you change x in Cell A:
x)y)uv run marimo export html notebook.py -o output.html
Result:
Marimo notebooks ARE Python files! Just run them:
uv run python notebook.py
Convert to standalone app:
uv run marimo run notebook.py
Others can view (but not edit) the notebook.
Good:
# Cell 1: Load data
df = pl.read_csv("data.csv")
# Cell 2: Clean data
df_clean = df.filter(pl.col("age") > 0)
# Cell 3: Analyze
summary = df_clean.group_by("category").agg(pl.mean("value"))
Avoid:
# Cell 1: Everything at once
df = pl.read_csv("data.csv")
df_clean = df.filter(pl.col("age") > 0)
summary = df_clean.group_by("category").agg(pl.mean("value"))
plot = px.bar(summary)
Good:
student_df = pl.read_csv("students.csv")
high_scorers = student_df.filter(pl.col("score") > 85)
Avoid:
df = pl.read_csv("students.csv")
df2 = df.filter(pl.col("score") > 85)
# Cell 1 (Markdown)
"""
# Data Loading
Loading the sales dataset from Q4 2024.
"""
# Cell 2 (Python)
sales = pl.read_csv("sales_q4.csv")
Use markdown headers to organize:
# Data Loading
... cells for loading data ...
# Data Cleaning
... cells for cleaning ...
# Analysis
... cells for analysis ...
# Visualization
... cells for plots ...
Marimo notebooks are .py files, so Git works perfectly!
What to commit:
git add my_analysis.py # The notebook
git add data/raw/input.csv # Input data
git add .gitignore # Git configuration
What NOT to commit:
.venv/ - Virtual environmentdata/processed/ - Generated outputs.marimo_cache/ - Marimo cacheGit diffs are readable:
import polars as pl
- df = pl.read_csv("old_data.csv")
+ df = pl.read_csv("new_data.csv")
df.head()
Compare this to Jupyterβs JSON mess!
Team workflow:
uv run marimo edit notebook.pyMarimo makes interactive notebooks easy!
import marimo as mo
# Create slider
age_slider = mo.ui.slider(0, 100, value=25, label="Age")
age_slider
# Use slider value
selected_age = age_slider.value
print(f"Selected age: {selected_age}")
category_dropdown = mo.ui.dropdown(
options=["Electronics", "Clothing", "Books"],
value="Electronics",
label="Category"
)
category_dropdown
name_input = mo.ui.text(placeholder="Enter name", label="Name")
name_input
filter_checkbox = mo.ui.checkbox(label="Show high scorers only")
filter_checkbox
# Cell 1: Create slider
import marimo as mo
threshold = mo.ui.slider(0, 100, value=75, label="Score Threshold")
threshold
# Cell 2: Filter based on slider (reactive!)
import polars as pl
df = pl.read_csv("data/raw/students.csv")
filtered = df.filter(pl.col("test_score") > threshold.value)
print(f"Students above {threshold.value}: {len(filtered)}")
Move the slider β Cell 2 updates automatically!
# Cell 1: Load
import polars as pl
df = pl.read_csv("data.csv")
# Cell 2: Clean
df_clean = df.drop_nulls()
# Cell 3: Analyze
summary = df_clean.group_by("category").agg(pl.mean("value"))
# Cell 4: Visualize
import plotly.express as px
fig = px.bar(summary, x="category", y="value")
fig
# Cell 1: Create widgets
import marimo as mo
category = mo.ui.dropdown(options=["A", "B", "C"], value="A")
threshold = mo.ui.slider(0, 100, value=50)
mo.hstack([category, threshold])
# Cell 2: Filter reactively
filtered = df.filter(
(pl.col("category") == category.value) &
(pl.col("score") > threshold.value)
)
filtered
# Cell 3: Plot reactively
fig = px.scatter(filtered, x="x", y="y")
fig
# Cell 1: Parameters
import marimo as mo
params = mo.ui.dictionary({
"data_file": mo.ui.text(value="data.csv"),
"min_score": mo.ui.slider(0, 100, value=70),
"group_by": mo.ui.dropdown(options=["A", "B"], value="A")
})
params
# Cell 2: Run analysis with parameters
df = pl.read_csv(params.value["data_file"])
result = df.filter(
pl.col("score") > params.value["min_score"]
).group_by(params.value["group_by"]).count()
result
| Action | Shortcut |
|---|---|
| Run cell | Shift + Enter |
| Run and stay | Ctrl/Cmd + Enter |
| New cell below | B |
| New cell above | A |
| Delete cell | D D (press D twice) |
| Convert to Markdown | M |
| Convert to Code | Y |
| Save | Ctrl/Cmd + S |
| Command palette | Ctrl/Cmd + K |
Problem: uv run marimo edit notebook.py shows error
Solutions:
ls should show pyproject.tomluv syncuv run marimo edit --port 8080 notebook.pyProblem: Cell doesnβt execute when you change it
Solutions:
Shift + EnterProblem: Changing one cell doesnβt update others
Solutions:
Problem: ModuleNotFoundError: No module named 'package'
Solutions:
uv add package-nameuv run marimo editexample_notebooks/01_python_basics.py to see Marimo in actionuv run marimo edit my_first_notebook.pyOfficial Resources:
Marimo makes data analysis interactive, reproducible, and fun! Start coding! π