Create reactive Python notebooks for IMSA racing data analysis using marimo. Use for building interactive filtering UIs (seasons, classes, events), connecting to DuckDB databases, creating reactive visualizations, and performing data analysis with automatic cell re-execution. Includes templates, patterns, and IMSA-specific workflows.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Create reactive Python notebooks for IMSA racing data analysis using marimo. Use for building interactive filtering UIs (seasons, classes, events), connecting to DuckDB databases, creating reactive visualizations, and performing data analysis with automatic cell re-execution. Includes templates, patterns, and IMSA-specific workflows.
Marimo Notebooks for IMSA Data Analysis
Purpose
Create interactive, reactive marimo notebooks for analyzing IMSA racing data with:
Interactive filtering UIs for seasons, classes, events, and sessions
Reactive programming where cells auto-update when filters change
DuckDB integration for SQL-based data analysis
Visualization dashboards with Altair charts and tables
Git-friendly notebooks stored as pure Python files
Key Concepts
Reactive Execution
marimo automatically runs dependent cells when variables change. When you interact with a UI element (e.g., change a dropdown), all cells referencing that element automatically re-run with the new value.
# Cell 1: Define UI element (global variable)
season = mo.ui.dropdown(options=[2023, 2024, 2025], value=2025)
# Cell 2: Use the value - automatically reruns when season changes
data = load_season_data(season.value)
# Cell 3: Use data - automatically reruns when data changes
chart = create_visualization(data)
Critical Rules
Assign UI elements to global variables - marimo can only synchronize elements assigned to global variables
- Access UI element values via
Reference the .value attribute
element.value
No hidden state - Delete a cell and marimo deletes its variables
Pure Python files - Notebooks are .py files, not JSON
Quick Start
Directory
Work in ./outputs/reports folder, create it if necissary
Installation
cd ./output/reports && uv init # if neededcd ./output/reports && uv add marimo openai pandas ...
Create New Notebook
cd ./output/reports && uv run marimo edit imsa_analysis.py
Use Template
Copy the template from assets/imsa_analysis_template.py as a starting point for IMSA analysis.
Building IMSA Analysis Notebooks
Step 1: Import and Connect
import marimo as mo
@app.celldef__():
import marimo as mo
import duckdb
import pandas as pd
import altair as alt
return alt, duckdb, mo, pd
@app.celldef__(duckdb):
# Connect to IMSA database
conn = duckdb.connect("../imsa.duckdb", read_only=True)
return conn,
Step 2: Load Filter Options
@app.celldef__(conn):
# Get available seasons, events, and classes
seasons_df = conn.execute("""
SELECT DISTINCT season FROM seasons
WHERE session = 'race'
ORDER BY season DESC
""").df()
events_df = conn.execute("""
SELECT DISTINCT event FROM seasons
WHERE session = 'race'
ORDER BY event
""").df()
classes_df = conn.execute("""
SELECT DISTINCT class FROM laps
WHERE class IS NOT NULL
ORDER BY class
""").df()
return seasons_df, events_df, classes_df
@app.celldef__(conn, season_filter, event_filter, mo):
# CRITICAL: Always work with session_id for lap time comparisons# Never compare lap times across different session_ids
session_query = f"""
SELECT session_id, start_date, session
FROM seasons
WHERE season = {season_filter.value}
AND event = '{event_filter.value}'
AND session = 'race'
LIMIT 1
"""
session_result = conn.execute(session_query).df()
iflen(session_result) > 0:
session_id = session_result['session_id'].iloc[0]
mo.md(f"**Session ID**: {session_id}")
else:
mo.md("⚠️ No race session found")
session_id = Nonereturn session_id, session_result
Step 5: Query and Analyze Data
@app.celldef__(conn, session_id, class_filter, mo):
# Query with proper IMSA filteringif session_id:
query = f"""
SELECT
driver_name,
car_number,
lap_time,
lap_number,
bpillar_quartile
FROM laps
WHERE session_id = {session_id}
AND class = '{class_filter.value}'
AND bpillar_quartile IN (1, 2) -- Top 50% clean laps
AND flags = 'GF' -- Green flag only
ORDER BY lap_time
LIMIT 100
"""
laps_df = conn.execute(query).df()
iflen(laps_df) > 0:
mo.md(f"Loaded {len(laps_df)} laps")
else:
mo.md("⚠️ No data found")
else:
laps_df = Nonereturn laps_df,
Step 6: Visualize Results
@app.celldef__(laps_df, alt, mo):
if laps_df isnotNoneandlen(laps_df) > 0:
chart = alt.Chart(laps_df).mark_boxplot().encode(
x=alt.X('driver_name:N', title='Driver', sort='-y'),
y=alt.Y('lap_time:Q', title='Lap Time (seconds)'),
color='driver_name:N'
).properties(
width=800,
height=400,
title='Lap Time Distribution'
)
chart
else:
mo.md("No data to visualize")
return chart,
IMSA-Specific Requirements
Always Use session_id
# ✅ CORRECT: Filter to specific session
WHERE session_id = {session_id}
AND class = '{class_filter.value}'# ❌ WRONG: Comparing across sessions
WHERE season = 2025# Don't do this for lap time analysis
Always Default to Race Sessions
# ✅ CORRECT: Default to 'race'
WHERE session = 'race'# ⚠️ Only if explicitly requested
WHERE session = 'practice'# Different objectives, not comparable
Always Use BPillar Filtering for Races
# ✅ CORRECT: Filter to clean, representative laps
WHERE bpillar_quartile IN (1, 2) -- Top 50% of clean laps
AND flags = 'GF' -- Green flag
# ❌ WRONG: Including all laps (pit stops, traffic, etc.)
-- No quartile filtering
Never Compare Across Classes
# ✅ CORRECT: Analyze each class separately
WHERE session_id = {session_id} AND class = 'GTP'# ❌ WRONG: Comparing GTP to GTD
WHERE session_id = {session_id} -- Missing classfilter
Common UI Patterns
Multi-Select for Classes
class_selector = mo.ui.multiselect(
options=["GTP", "LMP2", "GTD"],
value=["GTP"],
label="Select Classes"
)
# Use in query with IN clause
classes_str = "', '".join(class_selector.value)
query = f"WHERE class IN ('{classes_str}')"
Slider for Top N Selection
top_n = mo.ui.slider(
start=5,
stop=50,
step=5,
value=10,
label="Top N Drivers",
show_value=True
)
# Use in LIMIT clause
query = f"... ORDER BY lap_time LIMIT {top_n.value}"
Form for Expensive Operations
# Gate expensive analysis behind a form
analysis_params = mo.md("""
### Configure Analysis
- Minimum laps: {min_laps}
- Include practice: {include_practice}
""").batch(
min_laps=mo.ui.slider(5, 50, value=10),
include_practice=mo.ui.checkbox(value=False)
).form()
# Only run when submittedif analysis_params.value:
results = run_expensive_analysis(**analysis_params.value)
Conditional Display with mo.stop
# Stop if session not selected
mo.stop(
session_id isNone,
mo.md("⚠️ Please select a valid session")
)
# Stop if no data
mo.stop(
laps_df isNoneorlen(laps_df) == 0,
mo.md("⚠️ No data available for analysis")
)
Performance Optimization
Filter in SQL, Not Python
# ✅ GOOD: Filter in SQL
query = f"""
SELECT * FROM laps
WHERE session_id = {session_id}
AND class = '{class}'
AND bpillar_quartile IN (1, 2)
LIMIT 1000
"""
df = conn.execute(query).df()
# ❌ BAD: Load all data then filter
df = conn.execute("SELECT * FROM laps").df()
df = df[df['session_id'] == session_id] # Inefficient
Limit Data for Visualization
# Sample large datasets
query = f"""
SELECT * FROM laps
WHERE session_id = {session_id}
ORDER BY RANDOM()
LIMIT 1000 -- Sample for performance
"""
See references/marimo_patterns.md for comprehensive patterns including:
Advanced UI element configurations
DuckDB integration techniques
Reactive design patterns
Data visualization examples
IMSA-specific query patterns
Performance optimization tips
Deeper docs
See references/agent-docs.md are officially written docs for running a marimo agent. It's excellent and you should read it.
Template Notebook
See assets/imsa_analysis_template.py for a complete working example that demonstrates:
Database connection setup
Interactive filter creation
Session ID retrieval
Proper IMSA data filtering
Visualization and analysis
Error handling
Running Notebooks
Development Mode
# Edit notebook with live previewcd ./output/reports && uv run marimo edit notebook.py
App Mode
# Run as read-only web app (hides code)cd ./output/reports && uv run marimo run notebook.py
# Run with code visiblecd ./output/reports && uv run marimo run --include-code notebook.py
# Custom portcd ./output/reports && uv run marimo run --port 8080 notebook.py
Script Mode
# Execute notebook as Python scriptcd ./output/reports && uv run python notebook.py
# With command-line argumentscd ./output/reports && uv run python notebook.py --season 2025 --event Sebring
Best Practices
1. One Cell, One Purpose
Each cell should have a single, clear purpose:
Cell 1: Imports
Cell 2: Database connection
Cell 3: Load filter options
Cell 4: Create UI filters
Cell 5: Get session_id
Cell 6: Query data
Cell 7: Visualize
2. Validate Inputs
Always check if data exists before processing:
if session_id and laps_df isnotNoneandlen(laps_df) > 0:
# Process dataelse:
mo.md("⚠️ No data available")
3. Use Descriptive Variable Names
# ✅ GOOD
season_filter = mo.ui.dropdown(...)
laps_df = conn.execute(query).df()
# ❌ BAD
s = mo.ui.dropdown(...)
df = conn.execute(query).df()
# Add comments explaining IMSA-specific filtering
query = f"""
SELECT * FROM laps
WHERE session_id = {session_id} -- Single session for comparison
AND class = '{class}' -- Each class analyzed separately
AND bpillar_quartile IN (1, 2) -- Top 50% clean laps (excludes pit stops)
AND flags = 'GF' -- Green flag laps only
"""
Troubleshooting
UI Element Not Updating Other Cells
Cause: Element not assigned to global variable
Fix: Ensure element = mo.ui.dropdown(...) at module level
Cell Not Re-Running on Change
Cause: Cell doesn't reference the changed variable
Fix: Check that cell reads element.value somewhere
"No module named marimo"
Cause: marimo not installed in current environment
Fix: pip install "marimo[sql]"
Database Connection Error
Cause: Wrong path or database doesn't exist
Fix: Check path in duckdb.connect("path/to/imsa.duckdb")
Empty Results
Cause: Filters too restrictive or no data for selection
Fix: Check if session exists, validate filter values