Org Babel을 사용한 계산 노트북 작성
Org Babel은 Emacs Org Mode를 대화형 다중 언어 계산 노트북 환경으로 변환합니다. Jupyter나 Mathematica와 마찬가지로 Org Babel은 실행 가능한 코드, 라이브 실행 결과, 그림, 표, 설명 텍스트를 함께 배치합니다. JSON 기반 노트북과 달리 Org 노트북은 일반 텍스트로 사람이 읽을 수 있으며, Git을 통해 네이티브로 버전 관리할 수 있고, 단일 문서 내에서 여러 프로그래밍 언어를 혼합하여 사용할 수 있습니다.
1. 핵심 아키텍처 및 관심사 분리
모든 Org Babel 노트북은 헤더 인수(header arguments)에 의해 제어되는 다섯 가지 구별되는 관심사로 작동합니다. 이들을 혼동하는 것이 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 <val> 문을 사용해야 합니다. 모든 print() 출력은 폐기됩니다. | 종료 코드 또는 문자열 값을 반환합니다. |
:results output (shell, 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:path]]를 생성합니다.
5.4 처리
replace(기본값): 이전#+RESULTS:블록을 대체합니다.append: 기존 결과 아래에 새 결과를 추가합니다.prepend: 기존 결과 위에 새 결과를 삽입합니다.silent: 결과를 Org 버퍼에 쓰지 않고 Emacs 에코 영역/미니버퍼에 표시합니다.
6. 노트북의 시각화 및 그림
노트북에 직접 렌더링되는 플롯과 그림을 생성하려면:
6.1 Python Matplotlib 플롯
#+NAME: plot-normal-distribution
#+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: plot-normal-distribution
[[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 lbs)", 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 | 노트북의 모든 코드 블록을 순차적으로 실행합니다("모든 셀 실행"과 유사). |
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 블록이 결과를 반환하지 않음 | print(...)와 함께 :results value를 사용했지만 return ...을 사용하지 않음 | print(...)를 return ...로 바꾸거나 헤더를 :results output으로 변경하십시오. |
<lang>에 대한 No org-babel-execute function 오류 | org-babel-load-languages에 언어가 등록되지 않음 | Emacs 설정의 org-babel-load-languages에 (<lang> . t)를 추가하고 평가하십시오. |
| 이미지 링크는 생성되지만 이미지가 표시되지 않음 | Org Mode에서 인라인 이미지가 꺼져 있음 | M-x org-toggle-inline-images (C-c C-x C-v)를 실행하거나 #+STARTUP: inlineimages를 추가하십시오. |
세션 출력에 반복적인 프롬프트(>>>)가 포함됨 | REPL 버퍼에 남아 있는 대화형 프롬프트 텍스트 | 세션 버퍼(*session-name*)를 정리하거나 다시 시작하십시오. |
| 내보내기가 느리거나 중단됨 | Org가 내보내기 중에 모든 노트북 블록을 다시 실행 중 | 장기 실행 블록에 :eval never-export를 설정하거나 #+PROPERTY: header-args :eval never-export를 통해 전역적으로 설정하십시오. |
| 테이블 입력이 가로 규칙에서 오류를 생성함 | 가로 규칙(` | -`)이 원시 행으로 처리됨 |
10. 완전한 주석 노트북 예제
#+TITLE: 태양 플레어 빈도 분석
#+AUTHOR: Dr. Alex Rivera
#+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
| 월 | C-Class | M-Class | X-Class |
|-------+---------+---------+---------|
| 1월 | 12 | 3 | 0 |
| 2월 | 19 | 5 | 1 |
| 3월 | 15 | 2 | 0 |
| 4월 | 27 | 8 | 2 |
| 5월 | 31 | 11 | 3 |
| 6월 | 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 [["월", "총 이벤트 수"]] + total_events
#+END_SRC
#+RESULTS: compute_totals
:RESULTS:
| 월 | 총 이벤트 수 |
|------+-------------|
| 1월 | 15 |
| 2월 | 25 |
| 3월 | 17 |
| 4월 | 37 |
| 5월 | 45 |
| 6월 | 30 |
:END:
* 시각화
Matplotlib를 사용하여 월별 총 이벤트를 표시합니다:
#+NAME: plot-monthly-totals
#+BEGIN_SRC python :session *solar* :results graphics file :file figures/monthly_totals.png :exports both
import matplotlib.pyplot as plt
months = ["1월", "2월", "3월", "4월", "5월", "6월"]
totals = [15, 25, 17, 37, 45, 30]
plt.figure(figsize=(8, 4))
plt.bar(months, totals, color="tab:orange")
plt.title("월별 총 태양 플레어 이벤트")
plt.xlabel("월")
plt.ylabel("이벤트 수")
plt.savefig("figures/monthly_totals.png", dpi=150)
plt.close()
#+END_SRC
#+RESULTS: plot-monthly-totals
[[file:figures/monthly_totals.png]]
* 결론
5월에 피크 활동이 관찰되었으며, 45개의 이벤트(주로 C-Class 및 M-Class)가 기록되었습니다. 다음 관찰 기간 동안 X-Class 이벤트를 지속적으로 모니터링해야 합니다.