Quartoを使ってJupyterより綺麗なPythonコード入りドキュメントを作る
何かしらのデータを解析するなどする場合に、Jupyter系をいままでは利用してきました。
もちろん、Jupyterを個人で単なる実行環境と利用する分にはそこまで大きな問題はないと思います。
ただ、Jupyter系はMarkdownを書くのには流石にあまり向いていないこと、pdfなどの出力がイマイチ綺麗にいかないことから、「分析してそれをそのままアウトプットしたい」という時にやや不満が残る点がありました。
そこで調べてみたところ、Quartoというものが使えそうだったので、今回セッティングと少し使ってみようと思った次第です。
まずは、公式のGalleryを見てみてください。結構いい感じと思います。
また、出力HTMLをGithub PagesへPublishすることができるなど、そのあたりも活用できればかなり良さそうです。インストール
セットアップ超概略
Python + VsCode編
quarto CLIのインストール
続けて、GetStartedの手順にしたがって、VsCodeでの環境をセットアップします。
quartoのVsCode拡張のインストール
quartoで調べたら出てくるので追加します。
(quarto関係ないですが) Python環境の作成+VsCodeインタープリタ設定
今回はpipenvで環境を作ります(python3自体はある前提)
適当に使いそうなものをインストールしておきます。
quartoがバックエンドにjupyterを利用するらしく、jupyter自体はインストールする必要があります。
exportとtouchあたりはカレントディレクトリに.venvを作成するためのおまじないです。
export PIPENV_VENV_IN_PROJECT=1
touch Pipfile
pipenv --python 3
pipenv install pandas scipy matplotlib numpy jupyter beautifulsoup4 requests
インストールが終わったらVsCodeを一度閉じて再度開いておきます(pythonインタープリタの読み込みがうまくいかなかったりすることがあります)
上記設定をすると以下のようなファイル構成になっていると思います。
|-.venv/
|-Pipfile
|-Pipfile.locks
ところで、quartoがレンダリングに使うpythonは以下の仕組みで選択されるようです。
- vsCodeのpython拡張機能の、pythonPathの0番目を取得
- pythonPathの仮想環境をactivate
- activate後にpython or python3の名前で引けるpythonインタプリタが選択され、quartoのレンダリングに使用される。
要は、pipenvで作成した./venv配下のインタプリタを、VsCodeのpython拡張機能(ms-python)で選択するようにしてあげれば、quarto側は勝手にそれを使ってレンダリングをします。
この辺りドキュメントに記載が見つからなかったので、結構迷う人もいる気がしました。(私は結局ソースコードを読みました)
具体的には、workspaceの設定で.venv配下の環境を使うように設定してあげます。
{
"python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe",
}
確認したソースコード。長いので折りたたみ
export function pythonExecForCaps(
caps?: JupyterCapabilities,
binaryOnly = false,
) {
if (caps?.pyLauncher) {
return ["py"];
} else if (isWindows()) {
return [binaryOnly ? "python" : caps?.executable || "python"];
} else {
return [binaryOnly ? "python3" : caps?.executable || "python3"];
}
}
const quarto = "quarto"; // binPath prepended to PATH so we don't need the full form
const cmd: string[] = [
this.quartoContext_.useCmd ? winShEscape(quarto) : shQuote(quarto),
isShiny ? "serve" : "preview",
shQuote(
this.quartoContext_.useCmd
? target.fsPath
: pathWithForwardSlashes(target.fsPath)
),
];
// extra args for normal docs
if (!isShiny) {
if (!doc) {
// project render
cmd.push("--render", format || "all");
} else if (format) {
// doc render
cmd.push("--to", format);
}
cmd.push("--no-browser");
cmd.push("--no-watch-inputs");
}
const cmdText = this.quartoContext_.useCmd
? `cmd /C"${cmd.join(" ")}"`
: cmd.join(" ");
this.terminal_.show(true);
// delay if required (e.g. to allow conda to initialized)
// wait for up to 5 seconds (note that we can do this without
// risk of undue delay b/c the state.isInteractedWith bit will
// flip as soon as the environment has been activated)
if (requiresTerminalDelay(this.previewEnv_)) {
const kMaxSleep = 5000;
const kInterval = 100;
let totalSleep = 0;
while (!this.terminal_.state.isInteractedWith && totalSleep < kMaxSleep) {
await sleep(kInterval);
totalSleep += kInterval;
}
}
this.terminal_.sendText(cmdText, true);
export function requiresTerminalDelay(env?: PreviewEnv) {
try {
if (env?.QUARTO_PYTHON) {
// look for virtualenv
const binDir = dirname(env.QUARTO_PYTHON);
const venvFiles = ["activate", "pyvenv.cfg", "../pyvenv.cfg"];
if (
venvFiles.map((file) => path.join(binDir, file)).some(fs.existsSync)
) {
return true;
}
// look for conda env
const args = [
"-c",
"import sys, os; print(os.path.exists(os.path.join(sys.prefix, 'conda-meta')))",
];
const output = (
child_process.execFileSync(shQuote(env.QUARTO_PYTHON), args, {
encoding: "utf-8",
}) as unknown as string
).trim();
return output === "True";
} else {
return false;
}
} catch (err) {
console.error(err);
return false;
}
}
export class PreviewEnvManager {
constructor(
outputSink: PreviewOutputSink,
private readonly renderToken_: string
) {
this.outputFile_ = outputSink.outputFile();
}
public async previewEnv(uri: Uri) {
// get workspace for uri (if any)
const workspaceFolder = workspace.getWorkspaceFolder(uri);
// base env
const env: PreviewEnv = {
// eslint-disable-next-line @typescript-eslint/naming-convention
QUARTO_LOG: this.outputFile_, QUARTO_RENDER_TOKEN: this.renderToken_,
};
// QUARTO_PYTHON
const pyExtension = extensions.getExtension("ms-python.python");
if (pyExtension) {
if (!pyExtension.isActive) {
await pyExtension.activate();
}
const execDetails = pyExtension.exports.settings.getExecutionDetails(
workspaceFolder?.uri
);
if (Array.isArray(execDetails?.execCommand)) {
env.QUARTO_PYTHON = execDetails.execCommand[0];
}
}
quartoでの文章の作成
まずはquarto GetStartedに載っている例を見てみます。
---
title: "Quarto Basics"
format:
html:
code-fold: true
jupyter: python3
---
For a demonstration of a line plot on a polar axis, see @fig-polar.
```{python}
#| label: fig-polar
#| fig-cap: "A line plot on a polar axis"
import numpy as np
import matplotlib.pyplot as plt
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
fig, ax = plt.subplots(
subplot_kw = {'projection': 'polar'}
)
ax.plot(theta, r)
ax.set_rticks([0.5, 1, 1.5, 2])
ax.grid(True)
plt.show()
```
先頭の---で囲まれた部分がドキュメントレベルでの設定(以降は単にオプションと呼びます)になります。
それ以降が文章本体です。```{python}で開始した部分にpythonコードを埋め込むことができます。
jupyterとの差異は、labelやfig-capの指定によって、latexのような相互参照の指定、キャプションの指定ができるなど、主に組版っぽい機能が充実しています。
今回はquartoの形式である、qmdファイルから作成していますが、quartoではipynbファイルからの変換などもできるらしく、実行に優れるipynb(quartoはレンダリングの分実行の機敏性は落ちます)で分析の大枠を決めたのち、quartoで文章に落とし込んでいくといったこともできそうです。
以降は、quartoドキュメントから有用そうな設定や機能をひろいつつ、サンプル文章を拡充していきます。
quartoオプションの基本
quartoは実態としてはpandocの拡張のようなもので、pandocとほぼ同等の設定を引き継いでいるようです。
quartoのサンプルでは、htmlフォーマットを利用していますが、それ以外にもさまざまな出力形式がサポートされています。
フォーマットとしては主に以下等があります。
- HTML
- OpenOffice
- ePub
- Presentations(Revealjs、Powerpoint,Beamer)
個人的に割と利用機会が多いのは、HTML、PDFあたりでしょうか。
このうち、HTMLについては、そこまで困ることはなく、ドキュメントを単純に参照すれば良いのですが、唯一PDFは詰まるポイントがあります。
PDFについては詰まりポイントがある関係で後述とし、まずはHTMLについて確認してみます。
HTMLオプション
主なオプション
主なオプションとしては以下です。一部Referenceにはあまりちゃんとした説明がなかったりするケースもあるので、Guideも合わせて確認する必要があります。
オプション名 | 概略 |
---|---|
title,subtitle,author,date,abstract | 説明不要と思います。 |
theme | テーマ設定です。詳しくはこちら |
css | CSSファイルを指定して読み込めます。 |
anchor-sections | true/falseで章タイトルにホバーしたときに章へのリンクが表示されるかを切り替えます。 |
smooth-scroll | true/falseでスムーススクロールを切り替えます。 |
html-math-method | 数式レンダリングエンジンが設定できます。 |
toc,toc-depth,toc-location, | |
number-sections | 目次関連です。toc-locationはデフォルトrightですが、leftやbodyが選択できます。 |
code-fold | trueとすると、コードを折りたたみにすることができます。trueでは初期状態で折り畳まれた状態ですが、showにすると、初期状態が表示された状態で折りたたみ可となります。デフォルトはfalse(折りたたみ無効)です。 |
code-overflow | コードが横に長すぎる時の挙動です。scroll,wrapから選択します。 |
code-line-numbers | true/falseでコードの横に番号表示するかを切り替えます。 |
code-block-border-left,code-block-bg | コードブロックのスタイルです。hexで指定できます。 |
highlight-style | シンタックスハイライトのスタイルです。 |
echo | trueにするとpythonコード自体がドキュメントに含まれます。falseにすると実行結果のみが含まれます |
bibliography | 参考文献関連です。 |
lang | 言語です。日本語はjaです。 |
include-before-body | ヘッダ部分にファイルなどから埋め込むことができます。 |
citations-hover,footnotes-hover | true/falseで切り替えます。参考文献や注釈部分にマウスをホバーすると参照先情報が表示されるようになります。 |
qmdファイルサンプルと出力例
qmdファイル
---
title: "Quarto Basics"
subtitle: "サブタイトルを長々とつけて、サブタイトルの折り返しが発生する場合の挙動を見てみようと思います"
author: MosaMosa
date: "2022/12/31"
abstract: "jupyternotebookに比べ、即時の実効性は劣るものの、TexやHTMLなど組版としての機能にはQuartoの優位性があります。特にipynbをpdfなどに出力すると改ページの挙動などがイマイチで悩んでいたのですが、そういった悩みは改善されそうです。"
format: html
anchor-sections: true
smooth-scroll: true
toc: true
toc-depth: 3
number-sections: true
code-fold: true
code-overflow: wrap
code-line-numbers: true
bibliography: ./references.bib
citations-hover: true
footnotes-hover: true
editor:
render-on-save: true
jupyter: python3
---
# 章
## 節
### 項
#### toc-depth 3だと目次に表示されない見出し
quarto[@quarto]での日本語入力のテスト @fig-polar.
長い文章を入力した時に日本語だとうまく折り返し処理をしてくれないことがあるが、そういったことが起こらないかの確認のための文章。
```{python}
#| label: fig-polar
#| fig-cap: "A line plot on a polar axis"
import numpy as np
import matplotlib.pyplot as plt
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
fig, ax = plt.subplots(
subplot_kw = {'projection': 'polar'}
)
ax.plot(theta, r)
ax.set_rticks([0.5, 1, 1.5, 2])
ax.grid(True)
plt.show()
```
See also
PDFオプション
PDF出力について
pdf出力に使うquartoにてエンジンはさまざま選択できますが、このうちメインの一つとなるのはTex系列のエンジンと思います。(この辺りで察される方も多いと思いますが)、Tex、特に日本語を扱うTexはquartoというよりもTexの問題でちょっと大変なポイントがあります。
Texのインストール
quartoはpdf-engineオプションにて、Texを指定することができますが、そのためにはtexエンジンにPathが通っていることもしくは絶対パスでの指定が必要となります。
ちなみに、quarto公式ではtinyTexでのインストールが推奨されていますが、日本語Texでパッケージをケチると思わぬエラーを吐いて苦しむことが多いため、個人的にはTexLiveのフルインストールを推奨します。
TexLiveのfullインストール相当のインストールをしていれば、特に問題はありませんでしたが、最小構成でのインストールなどをしている場合はパッケージの不足が生じるかもしれません。
Texエンジンの選択
quartoではpdf-engineに、Tex系ではxelatex, pdflatex, lualatex, tectonic, latexmkを選択することができます。デフォルトはxelatexです。xelatexはutf-8での組版が可能なため、一見日本語使用が問題なく見えますが、実際は組版上の問題が生じるようです。参考
そのため、日本語を利用する場合はlualatexを利用することとしました。
lualatexでの日本語設定
lualatexで日本語を使用するための設定については、他により良い説明例がありますので、細かい説明は割愛しますが、要はluatex-ja関連のパッケージを使うように設定してあげれば問題ありません。
quartoでは、オプションでheader部分にファイルから読み込んだ文字列を挿入する機能があるため、それを利用して上記パッケージを読み込むようにします。
具体的には以下のようなファイルを別途作成し、
% luatex_headerというファイルで作成
\usepackage{luatexja}
\usepackage{luatexja-fontspec}
\usepackage[hiragino-pro]{luatexja-preset}
quartoドキュメントの設定部分で、
include-in-header:
file: ./luatex_header
として読み込んであげます。markdown中にTex記法を混在させるとややこしいことになってしまいますが、文章全般設定やフォント類に関わるパッケージはかなり自由に追加可能と思います。
そのほかトラブルシューティング
- 途中で謎のエラーによりTexのコンパイルが通らなくなる事象に苦しみましたが、TexLiveのバージョンが低いことに起因していました。TexLiveの最新版をインストールし直すことで改善しました。
- lualatexがずっと実行される(6回、7回、、、と続く。通常は3回)事象が発生しました。原因としては、文献への参照での参照先ラベルが.bibファイルに存在しなかったことによります。quarto規定のlatexmkの設定では最大実行回数に上限がない(か、上限がかなり高い)ようなので、latex-max-runsオプションを5程度に設定して抑制しました。
そのほか便利そうなpdfオプション
オプション名 | 概略 |
---|---|
title,subtitle,author,date,abstract | 説明不要と思います。 |
pdf-engine | 上記の通り日本語の場合はluatexを利用しましょう |
documentclass,pagesize | お馴染みのやつです。Texでは\documentclass[a4paper]{jsarticle}なんて書くと思いますが、quartoでは別々のオプションになっています。 |
include-in-header | 上記の通りパッケージの読み込みに使えます。 |
toc,toc-depth,number-sections | 目次の生成関連です。number-sectionsをfalseにすると第四階層の見た目が悪いので、trueが良いかなと思っています。 |
code-block-bg | コードブロックの背景色をhexで指定できます。 |
echo | trueにするとpythonコード自体がドキュメントに含まれます。falseにすると実行結果のみが含まれます |
fig-pos | \figureでやる、htbpとかHとかの例のアレです。わからない人はHでいいんじゃないかなと思っています。 |
bibliography、cite-method、biblatexoptions | 参考文献関連です。 |
latex-max-runs | 上記の通りで最大実行回数を制限できます。 |
lang | このlangをjaに設定するとTex内での参照がFigure→図、 Table→表など日本語になります。これを設定しただけで日本語対応するような代物ではないです。 |
highlight-style | シンタックスハイライトのスタイルです。 |
qmdファイルサンプルと出力例
折り畳み内に記載したファイルを、.qmd形式で作成し、VsCode拡張を利用している場合はCtrl+Shift+Kでpdfが出力されます。
Markdown+Pythonの割にはかなり良い感じと思います。
qmdファイル
---
title: "Quarto Basics"
subtitle: "サブタイトルを長々とつけて、サブタイトルの折り返しが発生する場合の挙動を見てみようと思います"
author: MosaMosa
date: "2022/12/31"
abstract: "jupyternotebookに比べ、即時の実効性は劣るものの、TexやHTMLなど組版としての機能にはQuartoの優位性があります。特にipynbをpdfなどに出力すると改ページの挙動などがイマイチで悩んでいたのですが、そういった悩みは改善されそうです。"
format: pdf
pdf-engine: lualatex
documentclass: ltjsarticle
papersize: a4
include-in-header:
file: ./luatex_header
toc: true
toc-depth: 3
number-sections: true
code-block-bg: "#F6F6F6"
echo: true
fig-pos: "H"
bibliography: ./references.bib
cite-method: biblatex
biblatexoptions: style=numeric-comp
latex-max-runs: 5
lang: ja
highlight-style: github
editor:
render-on-save: true
jupyter: python3
---
# 章
## 節
### 項
#### toc-depth 3だと目次に表示されない見出し
quarto[@quarto]での日本語入力のテスト @fig-polar.
長い文章を入力した時に日本語だとうまく折り返し処理をしてくれないことがあるが、そういったことが起こらないかの確認のための文章。
```{python}
#| label: fig-polar
#| fig-cap: "A line plot on a polar axis"
import numpy as np
import matplotlib.pyplot as plt
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
fig, ax = plt.subplots(
subplot_kw = {'projection': 'polar'}
)
ax.plot(theta, r)
ax.set_rticks([0.5, 1, 1.5, 2])
ax.grid(True)
plt.show()
```
See Also
おわりに
上記で基本的なドキュメント作成はできるようになりました。
サポートされているMarkdown記法の一覧や、そのほか機能をみつつ使っていこうと思っています。
Discussion