Análise de Dados Reativa com Marimo e DataSpoc Lens
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:
dataspoc-lens notebook --marimoIsso 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:
pip install marimo dataspoc-lensmarimo editCélula 1: Conectar ao Seu Data Lake
import marimo as mofrom 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_dropdownIsso 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 changesselected = table_dropdown.valueif 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_tableSelecione “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 tableif 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_sliderif 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_rangeif 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 duckdbfrom dataspoc_lens import LensClient
lens = LensClient()conn = lens.connect() # Returns a duckdb.DuckDBPyConnection
# Use standard DuckDB APIresult = 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 queryfunnel = 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:
- Célula 1: Carregar dados — Executar
- Célula 2: Agrupar por região — Executar
- Célula 3: Plotar resultados — Executar
- Célula 4: Filtrar pela região principal — Executar
- Opa, você quer mudar o intervalo de datas na Célula 1
- Re-executar Célula 1 — manualmente
- Re-executar Célula 2 — manualmente
- Re-executar Célula 3 — manualmente
- Re-executar Célula 4 — manualmente
- Você esqueceu alguma célula? O estado está desatualizado? Quem sabe.
No Marimo:
- Célula 1: Carregar dados com seletor de data — executa
- Célula 2: Agrupar por região — auto-executa
- Célula 3: Plotar resultados — auto-executa
- Célula 4: Filtrar pela região principal — auto-executa
- Mude o seletor de data na Célula 1
- Células 2, 3, 4 todas atualizam automaticamente
- O estado é sempre consistente
| Recurso | Jupyter | Marimo |
|---|---|---|
| Ordem de execução | Manual (qualquer ordem) | Automática (fluxo de dados) |
| Estado oculto | Sim (problema constante) | Não (impossível por design) |
| Widgets de UI | ipywidgets (conexão manual) | Integrados, reativos |
| Formato de arquivo | JSON (diffs ruins) | Python puro (diffs limpos) |
| Reprodutibilidade | Run All e torcer | Garantida por design |
| Integração DataSpoc | dataspoc-lens notebook | dataspoc-lens notebook --marimo |
Dicas para Notebooks Reativos Eficazes
- Uma saída por célula — Células Marimo retornam sua última expressão. Mantenha células focadas.
- Use elementos de UI para parâmetros —
mo.ui.slider,mo.ui.dropdown,mo.ui.textsão todos reativos. Use-os em vez de valores fixos no código. - Cache de consultas pesadas — use
lens.cache_refresh(table)antes de sessões de exploração para evitar leituras repetidas da nuvem. - 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.
- 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 selectedMarimo 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.