Org Babelを使った計算ノートブックの作成
Org Babelは、Emacs Org Modeをインタラクティブで多言語対応の計算ノートブック環境に変えます。JupyterやMathematicaと同様に、Org Babelは実行可能コード、実行結果、図、表、説明文をインターリーブします。JSONベースのノートブックとは異なり、Orgノートブックはプレーンテキストで人間が読みやすく、Gitでネイティブにバージョン管理でき、単一のドキュメント内で複数のプログラミング言語を混在させることができます。
1. コアアーキテクチャと関心事の分離
すべてのOrg Babelノートブックは、ヘッダー引数によって制御される5つの異なる関心事にわたって動作します。これらを混同することは、Orgノートブックにおけるバグの主な原因です。
| 関心事 | 主要なヘッダー引数 | 制御内容 |
|---|---|---|
| 実行 | :session, :eval, :dir, :var, :cache | ブロックを実行するか、どのプロセスで、どこで、どのような入力で実行するか |
| 結果 | :results (value vs output, 形式, タイプ, 処理) | 実行から何をキャプチャし、バッファ内でどのようにレンダリングするか |
| エクスポート | :exports (code, results, both, none) | エクスポートされたレポート(HTML, PDF, Markdown)に何を表示するか |
| タングリング | :tangle, :shebang, :noweb | ディスク上のスタンドアロンソースコードファイルに何を抽出するか |
| 構成 | :var, #+CALL:, <<noweb>>, org-sbe | ブロックがどのようにデータやコードを相互に渡すか |
2. ノートブックの構造とプリアンブルの設定
Orgノートブックファイル(.org)は、ドキュメントメタデータとバッファ全体のデフォルト設定から始まり、その後に説明文、数式、コードブロックを含む階層的な見出しが続きます。
2.1 標準ノートブックプリアンブルテンプレート
#+TITLE: 実験Xの計算分析
#+AUTHOR: 山田太郎
#+DATE: [2025-05-18 Sun]
#+OPTIONS: toc:2 num:t ^:{}
#+STARTUP: overview hideblocks inlineimages
# このバッファ内のすべてのブロックに対するグローバルBabelデフォルト設定:
#+PROPERTY: header-args :eval yes :exports both :results drawer
# 言語固有のデフォルト設定:
#+PROPERTY: header-args:python :session *py-analysis* :results value :exports both
#+PROPERTY: header-args:R :session *R-analysis* :results value :exports both
#+PROPERTY: header-args:sh :session none :results output :exports both
主要なバッファ内設定:
#+STARTUP: inlineimages: 結果が挿入されたとき、またはバッファが開かれたときに、生成されたプロット/画像を自動的にインライン表示します。#+STARTUP: hideblocks: コードブロックを折りたたんだ状態で開始し、読みやすくするために説明文と結果のみを表示します。#+STARTUP: nohideblocks: すべてのコードブロックを展開して表示します。#+PROPERTY: header-args ...: バッファ全体のデフォルト設定。header-args+で追加できます。
2.2 Emacs設定での言語の有効化
OrgはデフォルトでEmacs Lispの評価のみを有効にします。それ以外のすべての言語は、Emacs設定で明示的にロードする必要があります。
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(python . t)
(R . t)
(shell . t)
(sqlite . t)
(gnuplot . t)
(C . t)))
信頼できるノートブックに対して対話的な実行確認ダイアログを回避するには:
;; nil に設定すると、確認プロンプトなしでブロックを実行できる
(setq org-confirm-babel-evaluate nil)
3. コードブロックの構造
#+NAME: <ブロック名>
#+HEADER: <追加ヘッダー引数>
#+BEGIN_SRC <言語> <スイッチ> <ヘッダー引数>
<本体>
#+END_SRC
#+NAME: <名前>: オプションの一意の識別子。ブロックが関数として参照されたり、:varを介して別のブロックへの入力として渡されたり、#+CALL:を介して呼び出される場合は必須です。<言語>: Babelが認識する識別子(python,R,sh,emacs-lisp,sqlite,sql,julia,calc,gnuplotなど)。<スイッチ>: オプションのスイッチ(例:エクスポート時の行番号表示用-n、ラベル削除用-r)。<ヘッダー引数>: 実行、結果、エクスポートを制御するキーと値のペア。#+HEADER:: 長いヘッダー引数のリストをきれいに指定するためのセカンダリライン。
インラインコードブロック
動的に計算された値を段落内に埋め込む場合:
最終計算精度は src_python[:session *py* :results value]{f"{acc * 100:.2f}%"} でした。
4. ヘッダー引数リファレンス
4.1 セッション管理: インタラクティブな探索 vs 密閉された再現性
:session none(デフォルト): ブロックを1回限りの外部インタプリタプロセスで評価します。密閉され、クリーンで、状態の汚染を避けますが、実行のたびにインポートと環境を再初期化します。:session <名前>: 永続的なEmacs劣後REPLバッファ(例:*py-analysis*)で評価します。- 同じセッション名を共有する複数のブロック間で、変数、関数、インポートされたモジュールが保持されます。
- 大規模なデータセット、大規模モデルウェイト、探索的ワークフローに不可欠です。
- ベストプラクティス:
:session <名前>でプロトタイプを作成し、最終確認ではセッションを再起動するか、:session noneで重要なセクションをテストして再現性を保証します。
4.2 作業ディレクトリとリモート環境
:dir /path/to/project: 指定されたディレクトリ内でブロックを実行します。:mkdirp yes: ターゲットディレクトリが存在しない場合、作成します。- リモート実行 (TRAMP):
:dir /ssh:user@gpu-cluster:/workspace/dataは、ローカルのEmacsバッファ内に出力をレンダリングしながら、サーバー上でリモート実行します。
4.3 高コスト計算のキャッシュ
:cache yes: ブロックテキスト、入力、:varパラメータのSHA1ハッシュを計算します。- ハッシュが
#+RESULTS[hash]:行に保存されているものと一致する場合、評価はスキップされ、キャッシュされた結果が保持されます。 - 外部副作用のない、決定論的な純粋な計算にのみ使用してください。
5. 結果の処理: 重要な区別
:results 引数は、最大4つの直交するコンポーネントを取ります。
:results [収集] [タイプ] [形式] [処理]
5.1 収集: 関数型 (value) vs スクリプト型 (output)
この違いを理解することで、Babelの混乱の90%が解消されます。
| 収集モード | キャプチャするもの | Pythonの動作 | シェルの動作 |
|---|---|---|---|
:results value (Python, R, elispのデフォルト) | 最後の式の戻り値または明示的な return。 | 明示的な return <値> 文を使用する必要があります。すべての print() 出力は破棄されます。 | 終了コードまたは文字列値を返します。 |
:results output (シェル, bashのデフォルト) | 実行中に出力されるstdoutストリーム。 | すべての print() 文をキャプチャします。return は無視されます。 | すべてのコマンド stdout (echo, ls など) をキャプチャします。 |
Python関数型の例 (:results value):
#+BEGIN_SRC python :results value
data = [1, 2, 3, 4]
# 関数型モードでは、Pythonでは明示的な return が必要です:
return sum(data)
#+END_SRC
#+RESULTS:
: 10
Pythonスクリプト型の例 (:results output):
#+BEGIN_SRC python :results output
for i in range(3):
print(f"Epoch {i}: loss={0.5/(i+1):.4f}")
#+END_SRC
#+RESULTS:
: Epoch 0: loss=0.5000
: Epoch 1: loss=0.2500
: Epoch 2: loss=0.1667
5.2 結果タイプ
table/vector: 返されたリスト/配列をOrg Modeの表としてフォーマットします。scalar/verbatim: 表形式にせず、生の引用テキストとして出力をフォーマットします。file: 出力をディスク上のファイルに直接出力し(:file <ファイル名>で指定)、結果にOrgファイルリンクを挿入します。
5.3 結果形式
drawer(推奨): 結果を:RESULTS:…:END:デリミタでラップします。特に複数行の出力で、ドキュメントを整理整頓します。raw: エスケープされていないOrgテキストとして出力を直接挿入します(動的な見出しや表の生成に便利)。code: 出力を#+BEGIN_SRC <言語>…#+END_SRCでラップします。link/graphics: 結果を画像/ファイルへのパスとして扱い、[[file:パス]]を生成します。
5.4 処理
replace(デフォルト): 以前の#+RESULTS:ブロックを置き換えます。append: 新しい結果を既存の結果の下に追加します。prepend: 新しい結果を既存の結果の上に挿入します。silent: Orgバッファに書き込まずに、Emacsのエコーエリア/ミニバッファに結果を表示します。
6. ノートブック内の可視化と図
ノートブックに直接レンダリングされるプロットや図を生成するには:
6.1 Python Matplotlibプロット
#+NAME: 正規分布プロット
#+BEGIN_SRC python :session *py-vis* :results graphics file :file figures/normal_dist.png :exports both
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(6, 3.5))
x = np.linspace(-3, 3, 200)
plt.plot(x, np.exp(-x**2 / 2) / np.sqrt(2 * np.pi), color="#2b5c8f", lw=2)
plt.title("標準正規分布")
plt.xlabel("z")
plt.ylabel("密度")
plt.tight_layout()
plt.savefig("figures/normal_dist.png", dpi=150)
plt.close()
#+END_SRC
#+RESULTS: 正規分布プロット
[[file:figures/normal_dist.png]]
6.2 R ggplot2プロット
#+NAME: r-scatter-plot
#+BEGIN_SRC R :session *r-vis* :results graphics file :file figures/r_scatter.png :exports both
library(ggplot2)
p <- ggplot(mtcars, aes(x=wt, y=mpg, color=factor(cyl))) +
geom_point(size=3) +
theme_minimal() +
labs(title="燃費 vs 重量", x="重量 (1000 ポンド)", y="MPG")
ggsave("figures/r_scatter.png", plot=p, width=6, height=4, dpi=150)
#+END_SRC
#+RESULTS: r-scatter-plot
[[file:figures/r_scatter.png]]
ヒント: Emacsで M-x org-toggle-inline-images (C-c C-x C-v) を実行すると、生成されたグラフィックをバッファ内に表示/非表示を切り替えられます。
7. インタラクティブな実行と編集ワークフロー
必須キーバインディング
| キーバインディング | コマンド | 説明 |
|---|---|---|
C-c C-c | org-babel-execute-src-block | ポイント下のブロックを実行し、結果を挿入/更新します。 |
C-c ' | org-edit-special | ブロック本体を専用のメジャーモードバッファ(フルLSP、シンタックスハイライト、オートコンプリート)で開きます。再度 C-c ' で保存して戻ります。 |
C-c C-v b | org-babel-execute-buffer | ノートブック内のすべてのコードブロックを順次実行します(「Run All Cells」と同じ)。 |
C-c C-v s | org-babel-execute-subtree | 現在の見出し下のすべてのコードブロックを実行します。 |
C-c C-o | org-open-at-point | #+RESULTS: リンク上で、外部ビューアまたはバッファで画像または生成されたファイルを開きます。 |
M-<down> | org-babel-pop-to-session | このブロックのセッションのインタラクティブなREPLバッファに直接フォーカスを切り替えます。 |
C-c C-v v | org-babel-expand-src-block | 完全に展開されたブロック(すべての :var と noweb 展開が解決された状態)をプレビューします。 |
8. レポートのエクスポートと公開
ノートブックは、スタンドアロンのHTML、PDF、またはMarkdownドキュメントに織り込むことができます。
8.1 :exports によるエクスポートコンテンツの制御
:exports code: コードブロックのみをエクスポートします。:exports results: 出力/図のみをエクスポートします(コードを非表示にし、エグゼクティブレポートに最適)。:exports both: コードと出力の両方をエクスポートします(技術文書に最適)。:exports none: エクスポートされたドキュメントからコードと結果の両方を非表示にします(必要な場合は評価中に実行されます)。
8.2 :eval によるエクスポート評価の制御
:eval never-export: エクスポート中のブロックの再実行を防ぎ、バッファ内の既存の#+RESULTS:を使用します。エクスポートを高速化し、非決定論的な変更を防ぎます。:eval query-export: エクスポート中に実行前にプロンプトを表示します。
8.3 エクスポートコマンド
C-c C-e h h(org-html-export-to-html): HTMLレポートを生成します。C-c C-e h o(org-html-export-to-html-and-open): HTMLを生成し、ブラウザで開きます。C-c C-e l p(org-latex-export-to-pdf): LaTeX を介して組版PDFレポートを生成します。
9. 一般的な落とし穴とトラブルシューティングチェックリスト
| 症状 / エラー | 原因 | 修正 |
|---|---|---|
| Pythonブロックが結果を返さない | :results value を print(...) で使用し、return ... を使用しなかった | print(...) を return ... に置き換えるか、ヘッダーを :results output に変更する。 |
<言語> に対する org-babel-execute 関数がありません | 言語が org-babel-load-languages に登録されていない | Emacs設定の org-babel-load-languages に (<言語> . t) を追加し、評価する。 |
| 画像リンクは生成されるが画像が表示されない | Org Modeでインライン画像がオフになっている | M-x org-toggle-inline-images (C-c C-x C-v) を実行するか、#+STARTUP: inlineimages を追加する。 |
セッション出力に繰り返しプロンプト (>>>) が含まれる | REPLバッファにインタラクティブプロンプトテキストが残っている | セッションバッファ (*セッション名*) をクリーンにするか再起動する。 |
| エクスポートが遅い、またはハングする | Orgがエクスポート中にすべてのノートブックブロックを再実行している | 長時間実行ブロックに :eval never-export を設定するか、グローバルに #+PROPERTY: header-args :eval never-export を設定する。 |
| テーブル入力で水平罫線に関してエラーが発生する | 水平罫線 (` | -`) が生の行として扱われる |
10. 完全な注釈付きノートブックの例
#+TITLE: 太陽フレア頻度分析
#+AUTHOR: Dr. アレックス・リベラ
#+DATE: [2025-05-18 Sun]
#+OPTIONS: toc:nil num:nil
#+STARTUP: inlineimages nohideblocks
#+PROPERTY: header-args :session *solar* :results drawer :exports both
* はじめに
6ヶ月の観測ウィンドウで記録されたフレア頻度を分析します。
* 生観測データ
以下がベースライン測定テーブルです。
#+NAME: raw_observations
| Month | C-Class | M-Class | X-Class |
|-------+---------+---------+---------|
| Jan | 12 | 3 | 0 |
| Feb | 19 | 5 | 1 |
| Mar | 15 | 2 | 0 |
| Apr | 27 | 8 | 2 |
| May | 31 | 11 | 3 |
| Jun | 22 | 7 | 1 |
* データ処理と要約統計量
Pythonを使用して総太陽活動を計算します。
#+NAME: compute_totals
#+BEGIN_SRC python :var obs=raw_observations :colnames yes :results value table
total_events = []
for row in obs:
month = row[0]
total = sum(row[1:])
total_events.append([month, total])
return [["Month", "Total Events"]] + total_events
#+END_SRC
#+RESULTS: compute_totals
:RESULTS:
| Month | Total Events |
|-------+--------------|
| Jan | 15 |
| Feb | 25 |
| Mar | 17 |
| Apr | 37 |
| May | 45 |
| Jun | 30 |
:END:
* 可視化
プロット用の空のセッションを作成します。
#+NAME: cleanup
#+BEGIN_SRC sh :session none :results silent
mkdir -p figures
#+END_SRC
#+NAME: monthly_flare_plot
#+BEGIN_SRC python :session *solar* :results graphics file :file figures/monthly_flares.png :exports both
import matplotlib.pyplot as plt
months = [", ".join(["Jan", "Feb", "Mar", "Apr", "May", "Jun"])]
totals = [15, 25, 17, 37, 45, 30]
plt.figure(figsize=(7, 4))
plt.bar(months, totals, color="#d62728")
plt.title("月間総フレアイベント")
plt.ylabel("イベント数")
plt.tight_layout()
plt.savefig("figures/monthly_flares.png", dpi=150)
plt.close()
#+END_SRC
#+RESULTS: monthly_flare_plot
[[file:figures/monthly_flares.png]]
* 結論
プロットは4月から5月にかけて活動がピークに達し、6月に減少したことを示しています。この期間は合計169のフレアイベントが観測されました。
* Appendix: モデル診断
:PROPERTIES:
:header-args: :eval never-export
:END:
このブロックは、ドキュメントのエクスポート時には実行されません。
#+BEGIN_SRC python :session *solar*
# Diagnost コード: データ検証など
import pandas as pd
print("データセットに欠損値はありません。")
#+END_SRC
#+RESULTS:
: データセットに欠損値はありません。
付録A: org-babel 関数リファレンス
| 関数 | 説明 |
|---|---|
org-babel-execute-src-block | ポイント下のソースブロックを実行します |
org-babel-execute-buffer | バッファ内のすべてのソースブロックを実行します |
org-babel-execute-subtree | サブツリー内のソースブロックを実行します |
org-babel-pop-to-session | ソースブロックのセッションに移動します |
org-babel-goto-named-src-block | 名前付きのソースブロックに移動します |
org-babel-load-in-session | ファイル内のソースブロックをセッションにロードします |
org-babel-view-src-block-info | ソースブロックのプロパティを表示します |
org-babel-expand-src-block | ソースブロックを展開します |
org-babel-insert-header-arg | ヘッダー引数テンプレートを挿入します |
org-babel-demarcate-block | ソースブロックを分割または結合します |