LabHub

ブログ

2026年の新しい Python ノートブックスタック — Marimo・Quarto と Jupyter 以後のデータワークフロー(Observable・Pluto・Polars・DuckDB まで)

한국어English日本語

プロローグ — Jupyter の隠れた状態問題

2026 年でも毎週のようにどこかで同じ場面が繰り返される。データサイエンティストが同僚にノートブックを渡し、同僚が「Run All」を押すと、5 つ目のセルで爆発する。変数が定義されていないと言う。送った人の画面ではちゃんと動いている。

原因はほぼ常に同じ — 隠れた状態(hidden state)だ。セルを上から下に一度も回さないまま、途中のセルだけ何度も実行していると、ノートブックのテキストとカーネルのメモリ状態がずれていく。送り主のカーネルには df が生きているが、ノートブックファイルからは df を作るセルが消えている。

これは Jupyter のバグではない。設計判断だ。Jupyter はセルを任意の順序で実行できることを資産と見なした — それが REPL の本質だから。しかしその自由は協業・再現性・CI に入ると負債になる。

「ノートブックは 1 人なら天国、2 人以上では地獄」

2024 年から 2026 年にかけて、この負債を新しい設計で返そうとする動きが本格化した。Marimo はセル間のデータフローグラフを静的解析し、依存関係に従って自動再実行する — セル順序ではなく、データ依存性こそが真実だ。Quarto はノートブックを入力として受け取り、HTML・PDF・ウェブサイト・論文補助として出版する多言語・多フォーマットエンジン。Observable Framework は JS ネイティブノートブックを静的サイトとしてビルドする。それらの下にあるコンピュート層も Pandas から Polars・DuckDB へ移っている。

この記事ではそのスタックを深く見る — Jupyter が何を間違え、Marimo がどう解決し、Quarto でどう出版し、いつまだ Jupyter を使うべきか。


1 章 · Jupyter の 4 つの負債

Jupyter(正確には IPython カーネル + ipynb JSON + Notebook/Lab UI の組み合わせ)は 2014 年以来のデータサイエンス標準環境だった。やったことは多い — 出力をコードの隣に置き、インライン可視化・Markdown・LaTeX を組み合わせて「実行可能な文書」を作った。しかし 12 年が経って 4 つの負債が積み上がった。

負債 1 — 隠れた状態

セルを In[14]、In[3]、In[27] の順で実行しても、ノートブックファイルにその順序は残らない。誰かが「Run All」を押したとき同じ結果が出る保証はない。真実はノートブックファイルではなくメモリに住むカーネルにあり、そのカーネルは終了した瞬間に消える。

負債 2 — diff 不可能(.ipynb は JSON)

.ipynb はコード・Markdown・画像・出力・実行カウンターを 1 つの JSON ファイルに詰める。PR レビュアーが GitHub で diff を開くと、base64 エンコードされた PNG が一行で延々と続く。nbstripoutjupytext のような回避策が必要なこと自体がシグナル — フォーマットが git と本質的に合わない。

負債 3 — 再現不能

上の 2 つを合わせると再現不能になる。「このノートブックを回した」が「この環境、この順序、このデータで回した」を意味しない。論文補助ノートブックを 6 か月後に開けば依存関係が壊れ、出力はコードとずれている。

負債 4 — UI の一方向性

Jupyter は「上から下へ」の線形モデルを前提とする。データ依存性を見せるグラフビューがない — このセルの出力がどこへ流れるか、どのセルがどのセルを呼ぶかを知るには、人間が頭で追跡するしかない。セルが 50 を超えたら事実上不可能だ。

これら 4 つの負債は互いを強化する。隠れた状態があるから再現できず、再現できないから diff を見ても意味がなく、diff が無意味だから協業もできない。


2 章 · Marimo — ノートブックを再設計する

Marimo は 2023 年後半に登場し、2026 年中頃時点で 0.10.x シリーズに到達したオープンソース Python ノートブックだ。出発点は単純 — 「ノートブックを最初から再設計したら何が変わるか」

3 つの核となる設計判断:

判断 1 — .py ファイルとして保存

Marimo ノートブックは .ipynb ではなく 純粋な Python ファイルだ。各セルはデコレータが付いた関数。

import marimo as mo

app = mo.App()


@app.cell
def __():
    import polars as pl
    df = pl.read_csv("sales.csv")
    return (df,)


@app.cell
def __(df):
    summary = df.group_by("region").agg(pl.col("revenue").sum())
    return (summary,)


@app.cell
def __(summary):
    summary
    return ()


if __name__ == "__main__":
    app.run()

ここで 2 つのことが起きる。第一に、このファイルは そのまま Python スクリプトとしても動くpython notebook.py だけで終わり。第二に、git diff に意味がある。変数名を変えれば diff に変数名の変更が見える。base64 エンコードされた PNG はどこにもない。

判断 2 — データフローグラフ、セル順序ではなく

Marimo はノートブックを静的解析して、各セルがどの変数を定義し、どの変数を参照するかを見つける。これが 有向非巡回グラフ(DAG) になる。セルを実行すると、Marimo はそのセルに 依存する 全セルを自動的に再実行する — Excel スプレッドシートと同じモデルだ。

結果: 隠れた状態は 不可能になる。変数を再代入すれば、それを使う全セルが即時更新される。セルを削除すれば、その変数を使うセルは即「未定義」エラーを出す。「Run All」が常に動くノートブックファイルと、メモリ内のカーネル状態が一致する。

判断 3 — ノートブック as アプリモード

Marimo は同じファイルを 2 つのモードで起動できる:

UI コンポーネントは mo.ui モジュールで定義する。

@app.cell
def __(mo):
    region = mo.ui.dropdown(
        options=["US", "EU", "APAC"],
        value="US",
        label="Region",
    )
    region
    return (region,)


@app.cell
def __(df, region):
    filtered = df.filter(pl.col("region") == region.value)
    filtered
    return (filtered,)

ドロップダウンの値が変わると、それを使うセルが自動的に再実行される。Streamlit/Gradio なしでノートブック自体がアプリになる。

Marimo がうまく解く他の問題


3 章 · Jupyter vs Marimo — 同じ作業、異なるコード

同じワークフロー(データロード → フィルタ → 可視化)を 2 つのノートブックでどう表現するか。

Jupyter のセル

# Cell 1
import pandas as pd
df = pd.read_csv("sales.csv")
df.head()

# Cell 2
df_us = df[df["region"] == "US"]
df_us.shape

# Cell 3
import matplotlib.pyplot as plt
df_us.groupby("month")["revenue"].sum().plot(kind="bar")
plt.show()

# Cell 4 — 30 分後、df の定義を忘れて書き直す
df = pd.read_csv("sales_v2.csv")  # 以前の df_us はまだ古いデータを指している!

Cell 4 を実行してチャート(Cell 3)を見直すと — df_us が古い df をキャプチャしているからチャートは更新されない。メモリ状態とコード状態がずれた。隠れた状態。

Marimo のセル

@app.cell
def __():
    import polars as pl
    df = pl.read_csv("sales.csv")
    df
    return (df, pl)


@app.cell
def __(df, pl):
    df_us = df.filter(pl.col("region") == "US")
    df_us
    return (df_us,)


@app.cell
def __(df_us):
    chart = df_us.group_by("month").agg(pl.col("revenue").sum())
    chart
    return (chart,)

ここで最初のセルの dfsales_v2.csv に変えると — Marimo は df に依存する 2 つ目、それに依存する 3 つ目のセルを 自動的に再実行する。チャートは即時更新。メモリ状態とコード状態が本質的に同期する。

さらに、Marimo は 同じ変数を 2 つのセルで再定義することを禁じる。「その変数は別のセルで定義済み」というエラーを出し、どちらか 1 つを選ばせる。最初はもどかしいが、隠れた状態が永遠に消える


4 章 · Quarto — ノートブックを出版可能な文書に

Marimo が「ノートブックの再設計」なら、Quarto は「ノートブックの出版」だ。Quarto は RStudio の RMarkdown の後継として 2022 年に 1.0 を打ち、2026 年時点で 1.5+ に到達している。多言語(Python・R・Julia・Observable JS)・多フォーマット(HTML・PDF・EPUB・Word・ウェブサイト・本)を 1 つのツールで扱う。

Quarto 文書の形

Quarto 文書は .qmd 拡張子を使い、YAML フロントマター + Markdown + コードフェンスで構成される。

---
title: "2025 Sales Analysis"
author: "Data Team"
date: "2026-05-14"
format:
  html:
    code-fold: true
    toc: true
  pdf:
    documentclass: article
execute:
  echo: true
  warning: false
---

## データロード

今四半期の売上を分析する。

\`\`\`{python}
import polars as pl
df = pl.read_csv("sales.csv")
df.head()
\`\`\`

## 地域別合計

\`\`\`{python}
df.group_by("region").agg(pl.col("revenue").sum())
\`\`\`

(上の例では MDX パーサーが混乱しないようにバックティックフェンスをエスケープしている。実際の .qmd では普通にバックティック 3 つを使う。)

このファイル 1 つを quarto render report.qmd --to html でビルドするとインタラクティブ HTML が、--to pdf でビルドすると LaTeX 品質の PDF が出る。コードが実行され、出力が文書に埋まる。

Quarto が対応する出力フォーマット

多言語 — Python・R・Julia・Observable JS を 1 ファイルで

Quarto はコードフェンスの言語識別子を見て適切なカーネルを起動する。python フェンスは Jupyter カーネル、r フェンスは knitr、julia フェンスは IJulia、{ojs} フェンスは Observable JS ランタイムで実行される。同じ文書内で Python でデータを加工し、R で統計モデルを回し、Observable JS でインタラクティブチャートを描くことが可能だ。

Quarto + Marimo、Quarto + Jupyter

Quarto は入力として .qmd 以外にも Jupyter ノートブック(.ipynb)Marimo ノートブック を受け取る。つまり Quarto はノートブックフォーマット非依存。「Marimo で作業し Quarto で出版する」ワークフローが自然だ。

学術論文と補助資料

Quarto は Journal of Statistical Software のような学術誌の公式テンプレートがある。APA・IEEE・Nature・Elsevier スタイルをフロントマター 1 行で切り替える。引用は BibTeX や Zotero から直接引き、CSL スタイルでフォーマットする。論文本文とその論文の分析コードが同じファイルに入る — 再現性の決定版


5 章 · Observable Framework — JS ネイティブノートブックの進化

Observable は 2018 年頃 Mike Bostock(d3.js の作者)が作った JS ネイティブノートブックだ。セルが反応的に結ばれ(Marimo のインスピレーション元)、可視化が一級市民。しかしホスティングが Observable.com に縛られる難点があった。

2024 年発表の Observable Framework はこのモデルを 静的サイトビルダー に持ってきた。.md ファイルに JS セルを書き、observable build を叩くと静的 HTML/CSS/JS が出る。ホスティングは Vercel・Netlify・GitHub Pages どこでも OK。

Observable Framework が得意な領域:

Marimo が「Python ユーザーがインタラクティブを欲しがるとき」の答えなら、Observable Framework は「JS と可視化中心のデータ出版」の答え。両者は競合というより別市場だ。


6 章 · Pluto.jl — Julia の反応型ノートブック、そして元祖

Pluto.jl は Julia 用の反応型ノートブックで、Marimo の直接的なインスピレーション元だ。2020 年に初めて登場し、「セル間データフローグラフ → 自動再実行」モデルをノートブック世界に最初に導入した。

Pluto が提示した核となる原則:

Marimo がこのモデルを Python の世界に持ってきた一方、Pluto は Julia データサイエンスコミュニティ(SciML・Plots.jl・DataFrames.jl)とより深く結合していった。Julia を使うなら Pluto が事実上のデフォルト選択。


7 章 · 新しいコンピュート層 — Polars・DuckDB

変わったのはノートブック UX だけではない。その下でデータを実際に転がすコンピュート層も変わった。

Polars — Rust ベースの DataFrame

Polars は Rust で書かれた DataFrame ライブラリで、Apache Arrow をネイティブメモリフォーマットとして使う。Pandas 比:

重いノートブックで Polars に移ると 5〜30 倍速くなることが普通にある。

DuckDB — インプロセス OLAP DB

DuckDB は SQLite の OLAP(分析)版だ。組み込み、列指向、シングルノード分析に最適化。ファイル・Parquet・CSV・JSON・Arrow を直接クエリする。

ノートブックで DuckDB が輝く瞬間:

import duckdb
duckdb.sql("""
    SELECT region, SUM(revenue)
    FROM 'sales/*.parquet'
    WHERE date >= '2026-01-01'
    GROUP BY region
""").to_df()

Spark クラスタなしでノートブックから 100GB の Parquet を直接クエリ。Polars と DuckDB は Arrow を通じて zero-copy でデータをやり取りするから、同じパイプラインで両方を混ぜてもコストはない。

このコンピュート層が Marimo・Quarto の下に敷かれると、単一ノートブックが扱えるデータ規模が一桁 GB から三桁 GB に上がる。


8 章 · 比較マトリックス

項目JupyterMarimoQuartoObservable FrameworkPluto.jl
言語Python(多カーネル)Python多言語JS 中心Julia
ファイル形式.ipynb(JSON).py.qmd.md.jl
反応型実行なしあり(DAG)なし(出版ツール)ありあり(元祖)
Git diff悪い良い良い良い良い
ノートブック as アプリ別途(Voila・Panel)内蔵別途静的サイトビルド部分的
静的 exportHTML(再実行なし)HTML/WASMHTML・PDF・サイト・本静的サイトHTML
学習曲線低い低〜中中(JS 要)低い
エコシステム規模巨大成長中大きい可視化中心中(Julia)
代表的用途EDA・教育・研究EDA・社内ツール・アプリレポート・論文・本・ブログダッシュボード・データジャーナリズムJulia 科学計算
ライセンスBSD-3Apache-2.0MITISCMIT

9 章 · 実際のワークフローシナリオ

シナリオ A · データサイエンティストの日常 EDA

シナリオ B · 四半期レポート → 社内役員

シナリオ C · 学術論文 + 補助資料

シナリオ D · 社内データツール

シナリオ E · データジャーナリズム・インタラクティブストーリー

シナリオ F · Julia 科学計算


10 章 · いつまだ Jupyter を使うべきか

Marimo・Quarto が強力でも、Jupyter のほうが良い場所はまだある。

まだ Jupyter

Marimo に移るシグナル

Quarto に移るシグナル


11 章 · 移行 — どう移すか

Jupyter → Marimo

Marimo は marimo convert notebook.ipynb で自動変換を提供する。一度できれいに移るわけではない — 同じ変数を複数セルで再定義するなど Jupyter で合法だったパターンが Marimo ではエラー。最初の変換後 30 分〜1 時間ほど手を入れる覚悟が必要だ。

推奨: 新規ノートブックから Marimo で書き、既存の .ipynb はそのままにする。一気に全部移さないこと。

Jupyter → Quarto

Quarto は .ipynb をそのまま入力として受け取る — 変換は事実上不要。quarto render notebook.ipynb だけで済む。出力フォーマットが増えるのが核となる価値。

Marimo + Quarto の組み合わせ

最強の組み合わせ。Marimo で執筆・インタラクティブ探索 → 結果を Quarto で出版。両方とも .py.ipynb を入出力で扱うから、パイプラインがきれい。


12 章 · アンチパターン

この新しいスタックでも避けるべき罠がある。

アンチパターン 1 — Marimo を単なる Jupyter 代替として使う

Marimo の本当の価値は反応型・.py 保存・ノートブック as アプリ。それを使わずに単にセル単位でコードを書くツールとして使うと、学習曲線だけ払って得がない。UI コンポーネントを差し込み、反応型依存性を活用すること。

アンチパターン 2 — Quarto 文書に巨大な分析をそのまま書く

Quarto のビルド時間が爆発する。重いデータ加工は別スクリプトに分け、Quarto には結果データ(Parquet/CSV)だけロードさせる。または freeze: true でキャッシュ。

アンチパターン 3 — ノートブックを永遠にノートブックのままにする

Marimo・Quarto が良いからといって全コードをノートブックに置くのは罠だ。安定したデータパイプラインは普通の .py モジュールに移し、ノートブックは探索・文書・インターフェースの役割に限定する。ノートブックは終着点ではなくインターフェースだ。

アンチパターン 4 — Polars/DuckDB を試さず Pandas に固執する

データが GB 級を超えると Pandas はメモリ・速度の両面で崩れる。Polars への移行コストは 1 週間以下。その 1 週間で次の 1 年の待ち時間を半分にできる。

アンチパターン 5 — Marimo の変数再定義制約を回避しようとする

最初もどかしいからといって globals() ハックで回避するな。その制約こそが隠れた状態を防ぐ中核メカニズムだ。もどかしければ変数名を新しくつけ、セルを統合する。


エピローグ — ノートブックの次の 10 年

Jupyter は 2014〜2024 年のデータサイエンスワークフローの標準だった。2026 年にその標準が壊れているのではない — 標準が分化している。

そして全員の下に Polars・DuckDB・Arrow が新しいコンピュート層として敷かれる。

核心は ノートブックがもう「コードを一度回すツール」ではない という点だ。ノートブックは 再現可能な協業資産(Marimo の .py)、出版可能な文書(Quarto)、インタラクティブアプリ(Marimo run mode)、データストーリー(Observable)— 4 つのインターフェースを 1 ファイルが全て提供できる時代になった。

ノートブックは終着点ではなくインターフェースだ。良いインターフェースはあらゆる読者に違う形で開かれる — データサイエンティストにはコード、同僚にはアプリ、役員には PDF、インターネットには静的サイト。

チェックリスト — 新しいノートブックワークフローに移るか決める

アンチパターン要約

次回予告


参考 / References

コメント

まだコメントはありません。

ログインするとコメントできます