Tutorial Marimo: Notebook Python Reaktif dan Reproducible

# Marimo: Notebook Python yang Reaktif dan Reproducible Marimo adalah notebook Python yang menyimpan isinya sebagai berkas `.py` biasa dan menjalankan sel secara reaktif, mirip cara spreadsheet mengh...

By Ruby Abdullah · · tutorial
MarimoNotebookReactiveData ScienceReproducibilityPython

Marimo: Notebook Python yang Reaktif dan Reproducible

Marimo adalah notebook Python yang menyimpan isinya sebagai berkas .py biasa dan menjalankan sel secara reaktif, mirip cara spreadsheet menghitung ulang rumus yang saling bergantung. Marimo dirancang untuk mengatasi masalah yang sering muncul pada alur kerja notebook tradisional, terutama hidden state dan eksekusi yang tidak berurutan. Tutorial ini menjelaskan apa itu Marimo, mengapa modelnya berbeda dari Jupyter, dan cara membangun notebook eksplorasi data interaktif yang juga bisa dijalankan sebagai skrip maupun sebagai aplikasi web.

Mengapa Perlu Notebook Lain?

Notebook populer untuk eksplorasi, pengajaran, dan pelaporan. Namun notebook juga terkenal dengan satu kelas bug tertentu yang berasal dari cara kerja model notebook klasik. Sebelum melihat fitur Marimo, ada baiknya kita namai dulu masalah yang ingin diselesaikannya.

Hidden State

Pada sesi Jupyter pada umumnya, kernel menyimpan setiap variabel yang pernah Anda definisikan, bahkan setelah sel yang membuatnya dihapus. Sebuah notebook bisa tampak berfungsi hanya karena sel yang kini sudah dihapus pernah dijalankan. Buka kembali nanti, jalankan dari atas ke bawah, dan notebook itu gagal. Kode yang terlihat tidak lagi cocok dengan keadaan runtime.

Eksekusi Tidak Berurutan

Jupyter mengizinkan Anda menjalankan sel dalam urutan apa pun. Penghitung In [n] mencatat urutan yang kebetulan Anda pakai, bukan urutan yang reproducible. Dua orang yang menjalankan notebook yang sama bisa memperoleh hasil berbeda tergantung sel mana yang mereka jalankan dan kapan.

Diff dan Version Control yang Buruk

Berkas .ipynb adalah JSON yang menyatukan kode sumber, penghitung eksekusi, dan output ter-encode base64 (gambar, tabel) dalam satu dokumen. Perubahan kode satu baris saja bisa menghasilkan diff yang besar dan berisik. Meninjau pull request notebook menjadi merepotkan, dan konflik merge sering terjadi.

Sulit Digunakan Ulang

Mengubah notebook menjadi skrip atau modul biasanya berarti menyalin sel ke berkas .py dan merapikan urutan eksekusi secara manual. Notebook itu sendiri tidak bisa langsung diimpor atau dijalankan sebagai program.

Marimo mengambil sikap berbeda pada setiap poin ini. Bagian berikut menjelaskan caranya.

Instalasi dan Langkah Awal

Marimo adalah paket Python standar.

pip install marimo

Pastikan instalasi dan periksa versinya:

marimo --version

Buat notebook baru:

marimo new

Buka notebook yang sudah ada di editor:

marimo edit notebook.py

Editor berjalan di browser, tetapi berkas notebook tersimpan di disk sebagai Python biasa. Jika Anda sudah punya notebook Jupyter, konversikan:

marimo convert oldanalysis.ipynb > newanalysis.py

Untuk menyajikan notebook sebagai aplikasi interaktif yang hanya bisa dibaca, bukan dokumen yang bisa diedit:

marimo run notebook.py

Dalam mode run, sel kode disembunyikan dan hanya UI serta output yang ditampilkan. Berkas yang sama adalah dokumen editor, aplikasi, sekaligus skrip. Tidak ada langkah ekspor terpisah untuk mendapatkan program yang berfungsi.

Format Berkas .py

Notebook Marimo adalah berkas Python biasa. Setiap sel adalah fungsi yang didekorasi dengan @app.cell, dan berkas diakhiri dengan blok runner kecil. Notebook minimal terlihat seperti ini:

import marimo

app = marimo.App()

@app.cell

def ():

import marimo as mo

return (mo,)

@app.cell

def (mo):

x = 21

mo.md(f"x is {x}")

return (x,)

if name == "main":

app.run()

Ada dua hal yang patut diperhatikan. Pertama, ini adalah Python valid yang bisa Anda lint, format dengan alat seperti Black atau Ruff, dan tinjau dalam diff biasa. Perubahan kode tampil sebagai perubahan kode, bukan sebagai gumpalan JSON. Kedua, argumen fungsi dan nilai kembalian bukan boilerplate yang Anda rawat manual. Marimo menghasilkannya dari variabel yang dibaca dan didefinisikan setiap sel. Deklarasi itulah yang menggerakkan model reaktif.

Model Eksekusi Reaktif

Inilah gagasan inti Marimo dan pembeda paling jelas dari Jupyter.

Sel Membentuk Graf Ketergantungan

Marimo menganalisis setiap sel secara statis untuk melihat variabel apa yang didefinisikan dan dirujuknya. Dari situ, Marimo membangun graf berarah tanpa siklus (DAG). Sel yang membaca df bergantung pada sel yang mendefinisikan df. Saat Anda menjalankan atau mengubah sebuah sel, Marimo menjalankan sel itu dan setiap sel di hilirnya, dalam urutan topologis. Sel yang tidak bergantung pada perubahan tersebut dibiarkan.

Efek praktisnya: tidak ada "urutan jalan" yang perlu diingat. Urutan sel dalam berkas tidak menentukan eksekusi; ketergantungan data yang menentukannya. Anda bisa menaruh sel dalam urutan baca apa pun yang membuat dokumen jelas.

Sebuah Variabel Didefinisikan di Tepat Satu Sel

Marimo menegakkan aturan bahwa setiap variabel global hanya boleh diberi nilai di satu sel. Jika dua sel sama-sama mendefinisikan model, Marimo melaporkan error alih-alih diam-diam membiarkan sel terakhir yang menang. Aturan inilah yang membuat DAG terdefinisi dengan baik dan reproducible. Awalnya terasa membatasi, tetapi ini menghapus seluruh kelas kebingungan "sel mana yang mengatur nilai ini?". Saat Anda butuh nama sekali pakai di beberapa tempat, gunakan variabel lokal (beri awalan garis bawah, misalnya tmp) agar tetap privat di selnya.

Tanpa Hidden State

Karena Marimo menurunkan state dari sel yang ada saat ini, menghapus sebuah sel juga menghapus variabel yang didefinisikannya, dan setiap sel yang bergantung langsung diperbarui. Tidak ada nilai sisa dari kode yang sudah tidak ada. Apa yang Anda lihat di berkas adalah apa yang dijalankan. Membuka kembali notebook dan hasil yang tadi Anda peroleh adalah hal yang sama, karena hasilnya adalah fungsi dari kode, bukan dari riwayat klik Anda.

Perbandingan Singkat

Perhatikan dua sel:

@app.cell

def ():

baseprice = 100

return (baseprice,)

@app.cell

def (baseprice):

total = baseprice 1.11

total

return (total,)

Ubah baseprice menjadi 120 di sel pertama, dan sel kedua menghitung ulang dengan sendirinya, menampilkan total yang baru. Di Jupyter Anda harus ingat untuk menjalankan ulang sel kedua. Di Marimo, lupa itu tidak mungkin, karena keterkaitannya menjadi bagian dari graf.

Elemen UI yang Terikat Secara Reaktif

Marimo menyediakan widget interaktif di bawah mo.ui. Perbedaan penting dari widget notebook pada umumnya adalah bahwa .value sebuah elemen UI merupakan variabel biasa dalam graf. Membaca .value di sel lain membuat sel itu bergantung pada widget, sehingga menggeser slider menjalankan ulang persis sel yang memakainya.

@app.cell

def (mo):

npoints = mo.ui.slider(start=10, stop=1000, value=200, step=10, label="Points")

noise = mo.ui.slider(start=0.0, stop=2.0, value=0.3, step=0.05, label="Noise")

mo.hstack([npoints, noise])

return npoints, noise

@app.cell

def (npoints, noise, np):

rng = np.random.defaultrng(0)

x = np.linspace(0, 10, npoints.value)

y = 2.0 x + 1.0 + rng.normal(0, noise.value, size=npoints.value)

return x, y

Saat salah satu slider digeser, sel yang membangun x dan y dijalankan ulang, begitu juga apa pun yang ada di hilirnya. Anda tidak pernah merangkai callback; ketergantungan itu tersirat dari fakta bahwa Anda membaca npoints.value dan noise.value.

Elemen umum lain bekerja dengan cara yang sama:

@app.cell

def (mo):

method = mo.ui.dropdown(

options=["least squares", "ridge"], value="least squares", label="Fit method"

)

label = mo.ui.text(value="experiment-1", label="Run label")

return method, label

@app.cell

def (mo):

upload = mo.ui.file(label="Upload a CSV", filetypes=[".csv"])

upload

return (upload,)

mo.ui.table merender dataframe dengan dukungan seleksi, dan .value-nya menyimpan baris yang dipilih sehingga sel hilir dapat bereaksi terhadap pilihan pengguna.

Menampilkan Output dan Markdown

Ekspresi terakhir dalam sebuah sel menjadi outputnya, mirip Jupyter. Untuk teks dan dokumentasi, mo.md merender Markdown dan mendukung interpolasi f-string, termasuk nilai UI yang hidup:

@app.cell

def (mo, npoints, noise):

mo.md(

f"""

## Dataset sintetis

Dibuat {npoints.value} titik dengan deviasi standar

noise sebesar {noise.value:.2f}.

"""

)

return

Karena sel ini membaca nilai slider, teks yang dirender ikut berubah saat Anda menggeser slider. Anda bisa menyematkan widget langsung di dalam Markdown dengan menginterpolasikan objeknya, yang praktis untuk kontrol inline.

Tata Letak

Marimo menyediakan pembantu tata letak yang menyusun output:

@app.cell

def (mo, npoints, noise, method):

mo.vstack(

[

mo.md("### Kontrol"),

mo.hstack([npoints, noise]),

method,

]

)

return

Gunakan mo.hstack untuk baris, mo.vstack untuk kolom, dan mo.ui.tabs untuk mengelompokkan bagian:

@app.cell

def (mo, chart, summarytable):

mo.ui.tabs({"Chart": chart, "Data": summarytable})

return

Contoh Utuh: Fit Linear Interaktif

Menyatukan semua bagian, berikut demo model kecil. Slider mengontrol data, dropdown memilih metode fit, dan chart diperbarui secara reaktif. Setiap sel di bawah akan menjadi sebuah fungsi @app.cell dalam berkas notebook; di sini ditampilkan sebagai isi sel agar mudah dibaca.

# imports

import marimo as mo

import numpy as np

import polars as pl

import altair as alt

# controls

npoints = mo.ui.slider(10, 1000, value=200, step=10, label="Points")

noise = mo.ui.slider(0.0, 2.0, value=0.3, step=0.05, label="Noise")

method = mo.ui.dropdown(["least squares", "ridge"], value="least squares", label="Method")

mo.hstack([npoints, noise, method])

# data generation

rng = np.random.defaultrng(0)

x = np.linspace(0, 10, npoints.value)

y = 2.0 x + 1.0 + rng.normal(0, noise.value, size=npoints.value)

data = pl.DataFrame({"x": x, "y": y})

# fit the model

if method.value == "ridge":

lam = 1.0

X = np.vstack([x, np.oneslike(x)]).T

coef = np.linalg.solve(X.T @ X + lam np.eye(2), X.T @ y)

else:

coef = np.polyfit(x, y, 1)

slope, intercept = float(coef[0]), float(coef[1])

yfit = slope x + intercept

# chart

fitted = data.withcolumns(pl.Series("yfit", yfit))

points = alt.Chart(fitted.topandas()).markcircle(opacity=0.5).encode(x="x", y="y")

line = alt.Chart(fitted.topandas()).markline(color="red").encode(x="x", y="yfit")

chart = mo.ui.altairchart(points + line)

chart

# live summary

mo.md(

f"""

Metode: {method.value}

Slope: {slope:.3f} Intercept: {intercept:.3f}

Sampel: {npoints.value}

"""

)

Geser slider atau ubah dropdown, dan data, fit, chart, serta ringkasan dihitung ulang dalam urutan ketergantungan, tanpa rerun manual. Notebook yang sama juga merupakan program Python yang berfungsi, seperti ditunjukkan bagian berikut.

Menjalankan sebagai Skrip

Karena berkasnya adalah Python biasa dengan titik masuk app.run(), Anda bisa mengeksekusinya langsung:

python notebook.py

Ini menjalankan setiap sel sekali, dari puncak graf ke bawah, tanpa editor. Berguna untuk batch job, tugas cron, atau CI. Elemen UI mengambil nilai default saat tidak ada sesi interaktif, jadi rancanglah default yang masuk akal untuk eksekusi tanpa pengawasan.

Argumen Command-Line dan Parameterisasi

Agar skrip bisa dikonfigurasi, baca argumen CLI dengan mo.cliargs:

@app.cell

def (mo):

args = mo.cliargs()

seed = int(args.get("seed", 0))

outpath = args.get("out", "results.csv")

return seed, outpath

Berikan nilai setelah pemisah --:

python notebook.py -- --seed 42 --out run42.csv

Argumen yang sama tersedia saat menyajikan dengan marimo run, sehingga satu notebook bisa diparameterisasi secara konsisten lintas mode skrip dan aplikasi.

Menjalankan sebagai Aplikasi Web dan Deploy

marimo run notebook.py menyajikan notebook sebagai aplikasi interaktif dengan kode disembunyikan. Anda bisa meng-host-nya seperti layanan web mana pun:
marimo run notebook.py --host 0.0.0.0 --port 8080

Untuk deployment, jalankan perintah itu di balik process manager atau di dalam container. Marimo juga merupakan proses Python biasa, jadi opsi umum (unit systemd, image Docker, worker platform-as-a-service) semuanya berlaku.

Aplikasi Khusus Browser dengan WASM

Marimo bisa mengekspor notebook ke berkas HTML mandiri yang berjalan sepenuhnya di browser melalui WebAssembly, tanpa server:

marimo export html-wasm notebook.py -o site/index.html --mode run

Host berkas hasilnya di static host mana pun. Gunakan --mode edit untuk mengirim notebook WASM yang bisa diedit alih-alih aplikasi hanya-baca. Ini cocok untuk situs dokumentasi, demo, dan materi pengajaran ketika Anda tidak ingin menjalankan backend.

Sel SQL dan Integrasi DataFrame

Marimo memiliki dukungan kelas satu untuk dataframe dan SQL. Anda bisa mencampur Polars, pandas, dan DuckDB dalam satu notebook, dan sel SQL dapat mengkueri dataframe secara langsung:

@app.cell

def (mo, data):

result = mo.sql(

"""

SELECT

round(x) AS xbucket,

avg(y) AS meany,

count() AS n

FROM data

GROUP BY xbucket

ORDER BY xbucket

"""

)

return (result,)

Kueri berjalan melalui DuckDB, merujuk dataframe data berdasarkan nama, dan mengembalikan dataframe. Karena sel SQL membaca data, ia menjadi bagian dari graf reaktif yang sama: regenerasi data dan kueri akan dijalankan ulang. Di editor, Marimo juga menyediakan tipe sel SQL sehingga Anda bisa menulis kueri tanpa pembungkus mo.sql(...).

Cache Sel Mahal dan Berhenti Lebih Awal

Untuk komputasi lambat, mo.cache melakukan memoisasi sebuah fungsi berdasarkan inputnya, sehingga rerun yang dipicu perubahan tak terkait tidak menghitungnya ulang:

@app.cell

def (mo, np):

@mo.cache

def expensivefit(seed: int, size: int):

rng = np.random.defaultrng(seed)

sample = rng.normal(size=size)

return float(sample.mean()), float(sample.std())

return (expensivefit,)

Untuk hasil yang harus bertahan setelah kernel di-restart, gunakan persistent cache milik Marimo, yang menyimpan entri di disk:

@app.cell

def (mo, np):

with mo.persistentcache(name="sampling"):

big = np.random.defaultrng(0).normal(size=10000000)

bigmean = float(big.mean())

return (bigmean,)

Untuk menghentikan sel lebih awal, mo.stop menghentikan eksekusi (dan sel di hilir outputnya) ketika sebuah kondisi terpenuhi. Ini berguna untuk berjaga terhadap input yang hilang:

@app.cell

def (mo, upload):

mo.stop(upload.value is None, mo.md("Upload sebuah CSV untuk melanjutkan."))

rawbytes = upload.value[0].contents

return (rawbytes,)

Ketika berkas tidak ada, sel menampilkan pesan dan berhenti, dan sel yang bergantung tidak berjalan dengan data yang tidak valid.

Menguji Notebook dengan pytest

Karena notebook adalah Python yang bisa diimpor, Anda bisa mengujinya. Definisikan fungsi biasa di dalam sel dan impor di modul tes, atau jalankan sel langsung. Sel tes yang nama fungsinya diawali test juga dikumpulkan oleh pytest saat Anda mengarahkannya ke berkas tersebut:

@app.cell

def (expensivefit):

def testfitisdeterministic():

assert expensivefit(1, 100) == expensivefit(1, 100)

return (testfitisdeterministic,)

pytest notebook.py

Artinya notebook yang sama bisa berfungsi sebagai eksplorasi, aplikasi, sekaligus tempat menyimpan tes regresi di dekat kode yang dicakupnya.

Praktik Terbaik

Jaga satu tanggung jawab per sel. Sel kecil membuat graf ketergantungan jelas dan rerun murah, serta enak dibaca dalam diff.

Pilih nama global yang deskriptif untuk nilai yang melintasi sel, dan lokal berawalan garis bawah untuk variabel sementara. Ini menjaga aturan definisi tunggal agar tidak mengganggu Anda.

Pilih default yang masuk akal untuk setiap elemen UI. Default itulah yang dipakai mode skrip dan eksekusi tanpa pengawasan, jadi harus menghasilkan hasil yang valid dengan sendirinya.

Lakukan cache pada batas fungsi, bukan di sekeliling blok besar. mo.cache mengunci berdasarkan argumen, jadi berikan input secara eksplisit alih-alih mengandalkan closure atas global notebook.

Gunakan mo.stop untuk menjaga sel yang bergantung pada upload, panggilan jaringan, atau input lain yang mungkin tidak ada, agar graf gagal dengan anggun alih-alih menghasilkan sampah.

Commit berkas .py dan tinjau seperti kode. Karena diff-nya Python sungguhan, code review dan CI bekerja sebagaimana pada bagian lain proyek Anda.

Marimo vs. Jupyter Sekilas

Penyimpanan: Marimo memakai berkas .py biasa; Jupyter memakai JSON .ipynb dengan output tertanam.

Eksekusi: Marimo menjalankan ulang sel bergantung secara otomatis melalui graf ketergantungan; Jupyter menjalankan sel secara manual dalam urutan klik apa pun.

State: Marimo menurunkan state dari sel yang ada dan menghapus variabel saat sebuah sel dihapus; Jupyter menyimpan hidden state dari sel yang dihapus atau tidak berurutan.

Penggunaan ulang: Notebook Marimo berjalan sebagai skrip, disajikan sebagai aplikasi, dan diimpor sebagai modul; notebook Jupyter perlu dikonversi untuk menjadi program.

Version control: Diff Marimo adalah diff kode biasa; diff Jupyter adalah JSON yang berisik.

Interaktivitas: Pada Marimo, nilai UI adalah variabel graf, sehingga widget memperbarui output tanpa callback; widget Jupyter butuh observer eksplisit atau rerun manual.

Ini bukan klaim bahwa Marimo menggantikan Jupyter untuk setiap tugas. Jupyter punya ekosistem yang luas dan banyak pengguna lebih suka eksekusi bebasnya untuk coretan cepat. Pertukaran yang diambil Marimo adalah menerima beberapa aturan, terutama definisi tunggal dan reaktivitas, sebagai ganti reproducibility dan penggunaan ulang.

Kesimpulan dan Poin Penting

Marimo membingkai ulang notebook sebagai program reaktif yang disimpan sebagai Python biasa. Graf ketergantungan menghapus eksekusi tidak berurutan, aturan definisi tunggal dan state turunan menghapus hidden state, dan format .py membuat notebook bisa ditinjau, diuji, serta dijalankan sebagai skrip atau aplikasi.

Poin penting:

  • Pasang dengan pip install marimo; edit dengan marimo edit, sajikan dengan marimo run, konversi dengan marimo convert.
  • Sel membentuk DAG; mengubah sebuah nilai menjalankan ulang hanya sel yang bergantung, dan setiap variabel didefinisikan di tepat satu sel.
  • .value elemen UI adalah variabel graf, sehingga widget menggerakkan pembaruan tanpa callback.
  • Notebook adalah berkas .py sungguhan: ramah git, bisa diimpor, bisa dijalankan dengan python notebook.py, dan bisa diparameterisasi via mo.cliargs.
  • Gunakan mo.cache dan persistent cache untuk pekerjaan mahal, mo.stop untuk menjaga sel, mo.sql untuk kueri berbasis DuckDB, dan pytest untuk pengujian.
  • Ekspor ke WASM dengan marimo export html-wasm untuk deployment khusus browser.

Jika tim Anda pernah terbakar oleh notebook yang berfungsi di satu mesin namun gagal di mesin lain, model Marimo layak dicoba secara serius pada proyek nyata.

Artikel Terkait

Tutorial Kedro: Pipeline Data Science yang Reproducible dan Terstruktur

Kedro: Pipeline Data Science yang Reproducible dan Mudah Dirawat Sebagian besar proyek data science dimulai dari satu no...

DuckDB: Database Analitik In-Process untuk Data Science

DuckDB: Database Analitik In-Process untuk Data Science DuckDB adalah database analitik in-process yang dirancang khusus...

Tutorial Polars: DataFrame Library Ultra-Cepat untuk Data Science

Polars - Tutorial Lengkap Library DataFrame Ultra-Cepat Daftar Isi Pendahuluan Prasyarat Dasar-Dasar Polars [Evaluasi La...

Tutorial Feature Engineering Masterclass: Teknik Feature untuk ML

Tutorial 14: Masterclass Rekayasa Fitur (Feature Engineering) Daftar Isi Pendahuluan Prasyarat Mengapa Rekayasa Fitur Pe...