marimonotebookreactivedata-analysispython

Análise de Dados Reativa com Marimo e DataSpoc Lens

Michael San Martim · 2026-04-26

Notebooks Jupyter têm um segredo sujo: as células podem rodar em qualquer ordem. Você pode ter df definido na célula 10, usado na célula 3 e sobrescrito na célula 7. O estado é invisível. Bugs são garantidos.

Marimo corrige isso. É um notebook reativo onde as células re-executam automaticamente quando suas dependências mudam. Mude um filtro na célula 2 e cada célula que depende dele atualiza instantaneamente. Combine com o DataSpoc Lens e você tem uma interface ao vivo e reativa para seu data lake.

O Que É o Marimo?

Marimo é um notebook Python onde:

  • Células auto-executam quando você muda uma variável anterior
  • Sem estado oculto — a ordem de execução é determinada pelo fluxo de dados, não pela posição da célula
  • Python puro — notebooks são arquivos .py, não blobs JSON
  • Elementos de UI integrados — sliders, dropdowns, tabelas que são reativos por padrão

Pense nele como uma planilha para Python: mude uma célula, tudo que depende dela atualiza.

Início Rápido

Inicie o Marimo com o DataSpoc Lens:

Terminal window
dataspoc-lens notebook --marimo

Isso inicia um servidor Marimo pré-configurado com uma conexão DuckDB ao seu data lake. Todos os seus buckets e tabelas estão disponíveis imediatamente.

Alternativamente, instale e inicie manualmente:

Terminal window
pip install marimo dataspoc-lens
marimo edit

Célula 1: Conectar ao Seu Data Lake

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

Saída: Connected. 12 tables available.

Célula 2: Seletor Interativo de Tabelas

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

Isso renderiza um dropdown no notebook. Quando você seleciona uma tabela diferente, cada célula que referencia table_dropdown re-executa automaticamente.

Célula 3: Mostrar Schema (Reativo)

# 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

Selecione “raw.orders” no dropdown — esta célula mostra as colunas instantaneamente. Selecione “raw.customers” — ela atualiza. Sem botão de re-executar.

Célula 4: Consulta Dinâmica com 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())

Mova o slider de 100 para 500 — a consulta re-executa e a tabela atualiza. O grafo reativo fica assim:

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

Célula 5: Agregação com Intervalo de Datas

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,
])

Mude o seletor de intervalo de datas — a consulta de agregação re-executa, a tabela atualiza, o gráfico redesenha. Tudo automático.

A API Programática connect()

Para scripts e automação, use connect() para obter uma conexão DuckDB diretamente:

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)

Isso é útil em células Marimo quando você quer acesso direto ao 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: Uma Comparação Real

Considere este fluxo de trabalho: explorar receita por região, depois detalhar a região principal.

No Jupyter:

  1. Célula 1: Carregar dados — Executar
  2. Célula 2: Agrupar por região — Executar
  3. Célula 3: Plotar resultados — Executar
  4. Célula 4: Filtrar pela região principal — Executar
  5. Opa, você quer mudar o intervalo de datas na Célula 1
  6. Re-executar Célula 1 — manualmente
  7. Re-executar Célula 2 — manualmente
  8. Re-executar Célula 3 — manualmente
  9. Re-executar Célula 4 — manualmente
  10. Você esqueceu alguma célula? O estado está desatualizado? Quem sabe.

No Marimo:

  1. Célula 1: Carregar dados com seletor de data — executa
  2. Célula 2: Agrupar por região — auto-executa
  3. Célula 3: Plotar resultados — auto-executa
  4. Célula 4: Filtrar pela região principal — auto-executa
  5. Mude o seletor de data na Célula 1
  6. Células 2, 3, 4 todas atualizam automaticamente
  7. O estado é sempre consistente
RecursoJupyterMarimo
Ordem de execuçãoManual (qualquer ordem)Automática (fluxo de dados)
Estado ocultoSim (problema constante)Não (impossível por design)
Widgets de UIipywidgets (conexão manual)Integrados, reativos
Formato de arquivoJSON (diffs ruins)Python puro (diffs limpos)
ReprodutibilidadeRun All e torcerGarantida por design
Integração DataSpocdataspoc-lens notebookdataspoc-lens notebook --marimo

Dicas para Notebooks Reativos Eficazes

  1. Uma saída por célula — Células Marimo retornam sua última expressão. Mantenha células focadas.
  2. Use elementos de UI para parâmetrosmo.ui.slider, mo.ui.dropdown, mo.ui.text são todos reativos. Use-os em vez de valores fixos no código.
  3. Cache de consultas pesadas — use lens.cache_refresh(table) antes de sessões de exploração para evitar leituras repetidas da nuvem.
  4. Nomeie variáveis claramente — como o Marimo rastreia dependências por nome de variável, nomes claros tornam o grafo reativo mais fácil de seguir.
  5. Use mo.stop() — para impedir que uma célula execute até que uma condição seja atendida:
mo.stop(not table_dropdown.value, "Select a table above to continue.")
# Code below only runs when a table is selected

Marimo torna a exploração de dados como usar um dashboard, mas com todo o poder de Python e SQL. Combinado com o DataSpoc Lens, você tem uma interface reativa para terabytes de dados Parquet na nuvem — sem infraestrutura, sem estado desatualizado, sem re-execuções manuais.

Recomendados