marimonotebookreactivedata-analysispython

Analisis de datos reactivo con Marimo y DataSpoc Lens

Michael San Martim · 2026-04-26

Los notebooks de Jupyter tienen un secreto oscuro: las celdas pueden ejecutarse en cualquier orden. Puedes tener df definido en la celda 10, usado en la celda 3 y sobreescrito en la celda 7. El estado es invisible. Los bugs estan garantizados.

Marimo solucióna esto. Es un notebook reactivo donde las celdas se re-ejecutan automáticamente cuando sus dependencias cambian. Cambias un filtro en la celda 2 y cada celda que depende de el se actualiza al instante. Combinalo con DataSpoc Lens y obtienes una interfaz reactiva y en vivo para tu data lake.

Que es Marimo?

Marimo es un notebook de Python donde:

  • Las celdas se auto-ejecutan cuando cambias una variable ascendente
  • Sin estado oculto — el orden de ejecucion lo determina el flujo de datos, no la posicion de las celdas
  • Python puro — los notebooks son archivos .py, no blobs JSON
  • Elementos de UI integrados — sliders, dropdowns, tablas que son reactivos por defecto

Piensa en el cómo una hoja de calculo para Python: cambias una celda, todo lo que depende de ella se actualiza.

Inicio rápido

Lanza Marimo con DataSpoc Lens:

Terminal window
dataspoc-lens notebook --marimo

Esto inicia un servidor Marimo preconfigurado con una conexión DuckDB a tu data lake. Todos tus buckets y tablas estan disponibles inmediatamente.

Alternativamente, instala y lanza manualmente:

Terminal window
pip install marimo dataspoc-lens
marimo edit

Celda 1: Conectar a tu data lake

import marimo as mo
from dataspoc_lens import LensClient
lens = LensClient()
tables = lens.tables()
mo.md(f"**Connected.** {len(tables)} tables available.")

Salida: Connected. 12 tables available.

Celda 2: Selector interactivo de tablas

table_dropdown = mo.ui.dropdown(
options=tables,
label="Select a table",
)
table_dropdown

Esto renderiza un dropdown en el notebook. Cuando seleccionas una tabla diferente, cada celda que hace referencia a table_dropdown se re-ejecuta automáticamente.

Celda 3: Mostrar esquema (reactivo)

# This cell re-runs whenever table_dropdown.value changes
selected = table_dropdown.value
if selected:
schema = lens.schema(selected)
schema_table = mo.ui.table(
[{"Column": c["name"], "Type": c["type"]} for c in schema],
label=f"Schema: {selected}",
)
schema_table

Selecciona “raw.orders” del dropdown — esta celda muestra las columnas al instante. Selecciona “raw.customers” — se actualiza. No se necesita boton de re-ejecucion.

Celda 4: Consulta dinamica con filtros

# Build a query based on the selected table
if selected:
limit_slider = mo.ui.slider(
start=10, stop=1000, step=10, value=100,
label="Row limit",
)
mo.hstack([limit_slider])
# This cell depends on both selected and limit_slider
if selected:
sql = f"SELECT * FROM {selected} LIMIT {limit_slider.value}"
result = lens.query(sql)
mo.ui.table(result.to_pandas())

Mueve el slider de 100 a 500 — la consulta se re-ejecuta y la tabla se actualiza. El grafo reactivo se ve asi:

table_dropdown → selected → sql → result → table display
limit_slider ──┘

Celda 5: Agregacion con rango de fechas

if selected == "raw.orders":
date_range = mo.ui.date_range(
start="2026-01-01",
stop="2026-04-26",
label="Date range",
)
date_range
if selected == "raw.orders" and date_range.value:
start, end = date_range.value
agg_sql = f"""
SELECT
DATE_TRUNC('week', order_date) as week,
COUNT(*) as orders,
SUM(amount) as revenue,
ROUND(AVG(amount), 2) as avg_order
FROM raw.orders
WHERE order_date BETWEEN DATE '{start}' AND DATE '{end}'
GROUP BY 1
ORDER BY 1
"""
agg_result = lens.query(agg_sql)
df = agg_result.to_pandas()
# Reactive chart
import altair as alt
chart = alt.Chart(df).mark_bar().encode(
x="week:T",
y="revenue:Q",
tooltip=["week", "orders", "revenue", "avg_order"],
).properties(width=600, height=300)
mo.vstack([
mo.ui.table(df),
chart,
])

Cambia el selector de rango de fechas — la consulta de agregacion se re-ejecuta, la tabla se actualiza, el grafico se redibuja. Todo automático.

La API programatica connect()

Para scripts y automatización, usa connect() para obtener una conexión DuckDB directamente:

import duckdb
from dataspoc_lens import LensClient
lens = LensClient()
conn = lens.connect() # Returns a duckdb.DuckDBPyConnection
# Use standard DuckDB API
result = conn.execute("""
SELECT plan, COUNT(*) as users
FROM raw.customers
GROUP BY plan
ORDER BY users DESC
""").fetchdf()
print(result)

Esto es util en celdas de Marimo cuando quieres acceso directo a DuckDB:

# Marimo cell using connect()
conn = lens.connect()
# Complex analytical query
funnel = conn.execute("""
WITH signups AS (
SELECT user_id, MIN(created_at) as signup_date
FROM raw.events
WHERE event_type = 'signup'
GROUP BY user_id
),
activations AS (
SELECT user_id, MIN(timestamp) as activation_date
FROM raw.events
WHERE event_type = 'first_purchase'
GROUP BY user_id
)
SELECT
DATE_TRUNC('week', s.signup_date) as cohort_week,
COUNT(DISTINCT s.user_id) as signups,
COUNT(DISTINCT a.user_id) as activated,
ROUND(100.0 * COUNT(DISTINCT a.user_id) / COUNT(DISTINCT s.user_id), 1) as activation_rate
FROM signups s
LEFT JOIN activations a ON s.user_id = a.user_id
GROUP BY 1
ORDER BY 1
""").fetchdf()
mo.ui.table(funnel)

Marimo vs. Jupyter: Una comparación real

Considera este flujo de trabajo: explorar ingresos por region, luego profundizar en la region principal.

En Jupyter:

  1. Celda 1: Cargar datos — Ejecutar
  2. Celda 2: Agrupar por region — Ejecutar
  3. Celda 3: Graficar resultados — Ejecutar
  4. Celda 4: Filtrar por region principal — Ejecutar
  5. Oh espera, quieres cambiar el rango de fechas en la Celda 1
  6. Re-ejecutar Celda 1 — manualmente
  7. Re-ejecutar Celda 2 — manualmente
  8. Re-ejecutar Celda 3 — manualmente
  9. Re-ejecutar Celda 4 — manualmente
  10. Olvidaste alguna celda? El estado esta desactualizado? Quien sabe.

En Marimo:

  1. Celda 1: Cargar datos con selector de fecha — se ejecuta
  2. Celda 2: Agrupar por region — se auto-ejecuta
  3. Celda 3: Graficar resultados — se auto-ejecuta
  4. Celda 4: Filtrar por region principal — se auto-ejecuta
  5. Cambiar el selector de fecha en la Celda 1
  6. Las Celdas 2, 3, 4 se actualizan automáticamente
  7. El estado siempre es consistente
CaracteristicaJupyterMarimo
Orden de ejecucionManual (cualquier orden)Automatico (flujo de datos)
Estado ocultoSi (problema constante)No (imposible por diseno)
Widgets de UIipywidgets (cableado manual)Integrados, reactivos
Formato de archivoJSON (malos diffs)Python puro (diffs limpios)
ReproducibilidadRun All y esperarGarantizada por diseno
Integracion DataSpocdataspoc-lens notebookdataspoc-lens notebook --marimo

Consejos para notebooks reactivos efectivos

  1. Una salida por celda — las celdas de Marimo devuelven su ultima expresion. Manten las celdas enfocadas.
  2. Usa elementos de UI para parametrosmo.ui.slider, mo.ui.dropdown, mo.ui.text son todos reactivos. Usalos en lugar de valores hardcodeados.
  3. Cachea consultas pesadas — usa lens.cache_refresh(table) antes de sesiones de exploracion para evitar lecturas repetidas a la nube.
  4. Nombra las variables claramente — dado que Marimo rastrea dependencias por nombre de variable, nombres claros hacen el grafo reactivo mas fácil de seguir.
  5. Usa mo.stop() — para evitar que una celda se ejecute hasta que se cumpla una condicion:
mo.stop(not table_dropdown.value, "Select a table above to continue.")
# Code below only runs when a table is selected

Marimo hace que la exploracion de datos se sienta cómo usar un dashboard, pero con todo el poder de Python y SQL. Combinado con DataSpoc Lens, obtienes una interfaz reactiva a terabytes de datos Parquet en la nube — sin infraestructura, sin estado desactualizado, sin re-ejecuciones manuales.

Recomendados