Org Babelによる多言語データパイプラインと相互運用性
Org Babelの最も強力な機能の一つは、言語に依存しない相互運用性です。データは、同一ドキュメント内で、異なるプログラミング言語、Orgテーブル、数式の間をシームレスに流れることができます。
単一のノートブックで、SQLによるデータクエリ、Pythonによる変換とクリーニング、Rによる統計モデリング、Shellによるシステムコマンドの実行、GnuplotやMatplotlibによる可視化を、Org Modeをユニバーサルデータバスとして実現できます。
1. ユニバーサルデータ受け渡しメカニズム :var
:var ヘッダー引数は、サポートされているすべての言語で同一の構文を持ちます。
:var 変数名=代入
:var 変数名=ファイル:参照先
1.1 :var でサポートされる代入の種類
| 入力ソース | 構文例 | 備考 |
|---|---|---|
| リテラル値 | :var n=42 text="sample" | 数値、真偽値、文字列。 |
| 名前付きOrgテーブル | :var data=raw_measurements | 2次元テーブルをネストされたリスト/データフレームとして渡します。 |
| 名前付きプレーンリスト | :var items=todo_list | トップレベルのリストアイテムを配列/ベクターとして渡します。 |
| 引数なしのコードブロック | :var stats=compute_stats() | compute_stats を実行し、その戻り値を渡します。 |
| 引数付きのコードブロック | :var res=calc(rate=0.05, n=12) | 指定されたパラメータでターゲットブロックを評価します。 |
| ブロック内容 / リテラル | :var template=config_template[] | サンプルまたはコードブロックの本文をそのまま渡します。 |
| Emacs Lisp評価 | :var file=(buffer-file-name) | ( [ ' ` で始まる値はElispとして評価されます。 |
| 外部Orgファイル | :var ext=other.org:data_table | 外部ファイル内の参照を解決します。 |
2. Orgテーブルの受け渡しとフォーマット
Orgテーブルは自動的にネイティブな構造(Pythonのリストのリスト、Rのデータフレームなど)に変換されます。ヘッダー引数でテーブルの前処理を制御できます。
2.1 テーブル前処理用引数
:colnames yes | no | nil(デフォルト:nil):nilの場合: テーブルの1行目に水平線がある場合、Babelはヘッダーを削除し、行を変換し、返されるテーブルに再適用します。yesの場合: 1行目を明示的に列名/ヘッダーとして扱います。noの場合: 1行目を通常のデータとして扱います。
:rownames yes | no(デフォルト:no):yesの場合: 処理前に最初の列を削除し、戻り値で行名として復元します。
:hlines yes | no(デフォルト:no):no(推奨) の場合: データをインタープリタに渡す前に、すべての水平線(|-...)を削除します。これにより、型エラー(数値ルーチンでのNoneや'hline'文字列のクラッシュなど)を防ぎます。yesの場合: 水平線を'hlineシンボルとしてそのまま残します。
2.2 :var でのテーブルのスライスとインデックス指定
0ベースのインデックス記法 [行, 列] を使用して、:var 宣言内で直接行と列をスライスできます。
#+NAME: full_dataset
| ID | Age | Score |
|----+-----+-------|
| 1 | 25 | 88.5 |
| 2 | 30 | 92.0 |
| 3 | 22 | 79.0 |
| 4 | 35 | 95.5 |
# 'Score'列のみを渡す場合 (列インデックス2、行1から最後まで):
#+BEGIN_SRC python :var scores=full_dataset[1:, 2] :results value
return sum(scores) / len(scores)
#+END_SRC
#+RESULTS:
: 88.75
- インデックス構文:
data[0]: 最初の行。data[,1]: 全行の2列目。data[1:3, :]: 行1から3まで、全列(包括)。data[-1]: 最後の行。data[*]またはdata[0:-1]: 全行。
- 重要: インデックス指定は、
:colnamesや:hlinesによる削除の前に行われます。
3. 多言語パイプラインパターン: SQL -> Python -> R -> グラフィックス
以下は、完全な多言語データワークフローの例です。
#+TITLE: 多言語パイプライン
* ステップ 1: データベースクエリ (SQLite)
生のイベントカウントを抽出:
#+NAME: raw_events
#+BEGIN_SRC sqlite :db telemetry.db :results table :colnames yes
SELECT device_id, event_type, count(*) as count
FROM events
WHERE event_date >= '2025-01-01'
GROUP BY device_id, event_type;
#+END_SRC
#+RESULTS: raw_events
| device_id | event_type | count |
|-----------+------------+-------|
| dev-01 | heartbeat | 1420 |
| dev-01 | alert | 12 |
| dev-02 | heartbeat | 1380 |
| dev-02 | alert | 45 |
* ステップ 2: データクリーニングと正規化 (Python)
SQLiteテーブルをPythonで処理:
#+NAME: clean_telemetry
#+BEGIN_SRC python :var events=raw_events :colnames yes :results value table
cleaned = []
for row in events:
dev_id, ev_type, count = row[0], row[1], int(row[2])
rate = round(count / 1440.0, 4) # 1分あたりのイベント数
cleaned.append([dev_id, ev_type, count, rate])
return [["Device", "Event", "Count", "RatePerMin"]] + cleaned
#+END_SRC
#+RESULTS: clean_telemetry
| Device | Event | Count | RatePerMin |
|--------+-----------+-------+------------|
| dev-01 | heartbeat | 1420 | 0.9861 |
| dev-01 | alert | 12 | 0.0083 |
| dev-02 | heartbeat | 1380 | 0.9583 |
| dev-02 | alert | 45 | 0.0313 |
* ステップ 3: 統計モデリング (R)
Pythonでクリーニングされた出力をRで処理:
#+NAME: r_stat_summary
#+BEGIN_SRC R :var df=clean_telemetry :colnames yes :results value table
summary_df <- aggregate(RatePerMin ~ Event, data=df, FUN=mean)
colnames(summary_df) <- c("Event", "AvgRatePerMin")
summary_df
#+END_SRC
#+RESULTS: r_stat_summary
| Event | AvgRatePerMin |
|-----------+---------------|
| alert | 0.0198 |
| heartbeat | 0.9722 |
4. ブロックのプログラム的呼び出し: #+CALL: とインライン call_
名前付きブロックを再利用可能な関数として扱い、ブロックを書き直すことなく呼び出すことができます。
4.1 構文
# ブロック形式:
#+CALL: <名前>[<内部評価ヘッダー>](<引数>) <結果ヘッダー>
# インライン形式:
... call_<名前>[<内部評価ヘッダー>](<引数>)[<結果ヘッダー>] ...
<名前>: ターゲットブロックの#+NAME:。<引数>: 関数形式のパラメータ。例:(n=5, metric="accuracy")。[<内部評価ヘッダー>]: 実行時に適用されます(例:[:results output])。[<結果ヘッダー>]: 返された結果のフォーマットに適用されます(例::results raw)。
4.2 例
#+NAME: power_calc
#+BEGIN_SRC python :var base=2 exp=3 :results value
return base ** exp
#+END_SRC
# カスタム引数での呼び出し:
#+CALL: power_calc(base=3, exp=4)
#+RESULTS:
: 81
# インライン呼び出し:
総容量は call_power_calc(base=2, exp=8) 単位です。
5. リアクティブスプレッドシート: テーブル数式でのBabel (org-sbe)
Org Modeのテーブルには、組み込みのスプレッドシート計算機能 (#+TBLFM:) があります。org-sbe マクロを使用すると、任意のテーブルセル数式から、任意の言語で書かれたBabelブロックを呼び出すことができます。
(org-sbe "ブロック名" (パラメータ1 値1) (パラメータ2 値2))
;; 短縮形:
(sbe "ブロック名" (パラメータ1 値1))
例: PythonとRの数式で動作するテーブル
#+NAME: py_tax_rate
#+BEGIN_SRC python :var country="US" :results value
rates = {"US": 0.08, "UK": 0.20, "DE": 0.19}
return rates.get(country, 0.0)
#+END_SRC
#+NAME: invoice_table
| Item | Country | Base Price | Tax Rate | Total Price |
|-------------+---------+------------+----------+-------------|
| Server Node | US | 1200 | 0.08 | 1296.0 |
| Storage SAN | UK | 3000 | 0.2 | 3600.0 |
| Gateway | DE | 800 | 0.19 | 952.0 |
#+TBLFM: $4='(org-sbe "py_tax_rate" (country $"$2")) :: $5=$3 * (1 + $4)
- テーブルで、
#+TBLFM行にカーソルを合わせC-u C-c C-cと入力すると、すべての数式が再計算され、各行に対してPythonブロックが動的に評価されます。 $"$2"は、2列目の値を文字列リテラルとして渡します。
6. :post による結果の後処理
:post ヘッダー引数は、ブロックの出力をドキュメントに挿入する前に、2番目のフォーマットブロックにチェーンします。プライマリブロックの出力は *this* にバインドされます。
# カスタムLaTeXエクスポート属性を追加する後処理ブロック:
#+NAME: wrap_latex_figure
#+BEGIN_SRC emacs-lisp :var data="" caption="Figure"
(format "#+CAPTION: %s\n#+ATTR_LATEX: :width 0.8\\textwidth\n%s" caption data)
#+END_SRC
# :post を使用したプライマリプロットブロック:
#+BEGIN_SRC python :results file link :file figures/out.pdf :post wrap_latex_figure(caption="Model Accuracy", data=*this*)
import matplotlib.pyplot as plt
plt.plot([1, 2, 3], [4, 5, 6])
plt.savefig("figures/out.pdf")
plt.close()
return "figures/out.pdf"
#+END_SRC
7. Library of Babel: 共有グローバル関数ライブラリ
Library of Babel を使用すると、コードを複製することなく、複数の異なる .org ファイル間で名前付きブロックをインポートして共有できます。
- 名前付きブロックを含む共有ライブラリファイル(例:
~/org/shared_babel.org)を作成します。 - Emacsセッションでライブラリを取り込みます:
またはキーボードショートカット:(org-babel-lob-ingest "~/org/shared_babel.org")C-c C-v i(org-babel-lob-ingest)。 - 任意のバッファで、
#+CALL: block_name(args)を使用してそのファイルのブロックを呼び出せます。
8. 多言語環境での一般的な落とし穴とベストプラクティス
| 落とし穴 | 結果 | 対策 |
|---|---|---|
| 列ヘッダーの不一致 | ヘッダー文字列が0行目のデータとして読み取られるため、数値計算が失敗する | テーブル入力を消費するブロックでは、常に :colnames yes を指定する。 |
| テーブルの水平線 (` | -`) によるクラッシュ | 数値が期待される場所に 'hline' シンボルが渡され、Python/Rがクラッシュする |
:var での名前なしブロック | Reference 'xyz' not found in buffer エラー | 入力ブロックまたはテーブルの直前に、対応する #+NAME: xyz キーワードがあることを確認する。 |
org-sbe でのテーブルセル変数の引用 | 引用符なしのテキストセルは、未定義のLispシンボルとして読み取られる | 列参照を文字列で囲む: (country $"$2")。 |
| ヘッダーを使用した場合のインデックス順序 | data[0] がインデックス時にヘッダー行を返す | テーブルのスライスは :colnames による削除の前に行われることに注意。 |