ユーザ用ツール

サイト用ツール


メモ:rmarkdown

Rmarkdown

主にRmarkdownからPDFを生成する際のメモ。Latexを経由するためLatexの知識も必要だが、RmarkdownからLatexにコマンドなどを渡す際の方法など、直接Latexを記述する場合とは異なるtipsがある。また、Rmarkdownでloopを回してチャンクを作成する方法など、効率化する手段も色々ありそう。

PDF出力用の基本設定

新しいコマンドを理解するたびに拡張している。とりあえず2020/05/05現在のもの。

ヘッダ部分

title: "タイトル"
author: "作成者"
date: "2020/05/05"
output:
  pdf_document:
    keep_tex: yes
    latex_engine: xelatex
    number_sections: yes
always_allow_html: true
# bxjsarticleクラスはgeometryパッケージを呼んでいるらしくここで使うとエラーになったため「no」を設定。
# どこかのバージョンアップにて設定項目があるとエラーになるようになったため削除
#geometry: no
header-includes:
- \usepackage{amsmath,amssymb}

# 別のパッケージと競合して1ページ目に無駄な文字列が出力されるので無効に
#- \usepackage{mathspec}

- \usepackage{indentfirst}
- \parindent = 1em
- \usepackage{zxjatype}
- \setmainfont{Century}
- \setsansfont{Century}

# 日本語フォント設定
# 太字指定の場合のフォントをオプションで指定
# 太字指定(\textbf)でゴシック体にする。kableExtraのrow_specなどのBold設定の場合など
- \setjamainfont[BoldFont=IPAexGothic]{IPAexMincho}
- \setjasansfont{IPAexGothic}
- \usepackage{float}
- \setpagelayout * {margin=20mm}
- \renewcommand{\baselinestretch}{0.9}
- 
- \usepackage[compact]{titlesec}
- \titlespacing{\section}{0pt}{5pt}{3pt}
- \titlespacing{\subsection}{0pt}{5pt}{3pt}

- \usepackage{secdot} # 章番号の後ろに「.」を追加するパッケージ。
- \sectiondot{section} # セクション番号に「.」を追加
#- \renewcommand{\thesection}{\arabic{section}} # セクション番号のフォーマットを変更するとき

- \renewcommand{\labelitemi}{■}
- \renewcommand{\labelitemii}{$\circ$}
- \usepackage{caption} 
- \captionsetup[table]{skip=5pt}
- \captionsetup[figure]{skip=2mm}

- \renewcommand{\textfraction}{0.001}
- \renewcommand\floatpagefraction{0.99}
- \setcounter{topnumber}{10}
- \setcounter{bottomnumber}{10}
- \setcounter{totalnumber}{20}

- \makeatletter
- \setlength{\@fptop}{0pt}
- \setlength{\@fpsep}{8pt}
- \setlength{\@fpbot}{0pt plus 1fil}
- \makeatother

- \makeatletter
- \renewcommand{\theequation}{% 式番号の付け方
- \thesection-\arabic{equation}}
- \@addtoreset{equation}{section}
- \renewcommand{\thefigure}{% 図番号の付け方
- \thesection-\arabic{figure}}
- \@addtoreset{figure}{section}
- \renewcommand{\thetable}{% 表番号の付け方
- \thesection-\arabic{table}}
- \@addtoreset{table}{section}
- \makeatother

- \usepackage{xcolor} % kableExtraのkable_stylingでlatex_optionsをstripedで表を縞模様にする場合に必要(以前はいらなった)
#- \definecolor{my_color}{rgb}{0.95,0.98,1.00} % stripe_color = "my_color"で縞模様の色を自分で作って指定する場合

# ページを横置きにするコマンドを定義:\blandscapeで開始、\elandscapeで戻す
- \usepackage{lscape}
- \newcommand{\blandscape}{\begin{landscape}}
- \newcommand{\elandscape}{\end{landscape}}

# header、footerのカスタマイズ
# 余白を調整するとそのままでは本文文字にページ番号などが被る場合がある
# fancyhdrパッケージで調整する。複雑なヘッダ、フッタも作成できるが、とりあえず余白との兼ね合いを調整するためだけに読み込み
#- \usepackage{fancyhdr}
#- \pagestyle{fancy} # ページスタイルを適用
#- \lhead{} # ヘッダ左
#- \chead{} # ヘッダ中央
#- \rhead{} # ヘッダ右・・・いずれも空にする
#- \renewcommand{\headrulewidth}{0.0pt} # ヘッダの区切り線を消す
#- \cfoot{\thepage} # フッタ中央にページ番号記載

# footerの位置調整
- \setlength{\footskip}{10mm}

documentclass: bxjsarticle
subparagraph: yes
classoption: xelatex,ja=standard

最初のsetupチャンク

PDFファイルを作成するとき、ggplotで日本語を含む図表を美しく出力するために図表をcairo PDFで作成する設定や、全体のチャンクオプションを一括して設定する。

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE)
knitr::opts_chunk$set(warning = FALSE, message = FALSE)

# Latexのfloatを使う場合の図表のfloatオプションを設定
# 図を強制的にその位置に出力
knitr::opts_chunk$set(fig.pos = 'H')

# キャッシュの設定。執筆中は時間短縮のためキャッシュを有効に。最後にOff
knitr::opts_chunk$set(cache = TRUE)

# 出力フォーマットが TeX(PDF含む)の場合のみ対処する

if (knitr::opts_knit$get("rmarkdown.pandoc.to") %in% c("beamer", "latex")) {
  # conversion failure on '...' in 'mbcsToSbcs' の Warning 発生の workaround
  options(
    device = function(file, width = 17, height = 17, ...){
      cairo_pdf(tempfile(), width = width, height = height, ...)
    }
  )
  ## 1. cairo_pdf を使う方法
  # * family には OS にインストールされているフォント名を指定する。
  knitr::opts_chunk$set(dev="cairo_pdf", dev.args=list(family="IPAexMincho"))
}
```

図のチャンクオプション

詳細な設定はもっといろいろあるが、基本の設定。PDFの出力が前提なので、ベクタ画像を利用するため解像度はあまり意識しなくてよいようなので、out.widthで横幅を設定し、縦は横幅との相対的な比率で調整するようにした。図は中央寄せ。

```{r チャンク名, fig.cap="キャプション", out.width="90%", fig.asp=0.5, fig.align="center"}

```

パラグラフ間の余白(パラグラフ送り)

パラグラフ間の字送り(余白)の設定。標準では1行空きになっているようだ。全体の設定を変更するにはヘッダ部分に以下を記述。

- \setlength{\parskip}{1.5em}

Loopでチャンク作成

図表を連続して作成する場合など、チャンク内のLoopから別のチャンクを生成したい場合がある。

まずテンプレートとなるチャンクを作成する。ループで回すように、必要な個所は変数化し該当箇所を「{{変数名}}」にしておく。以下の例では、チャンク名とキャプション、ggplotのy軸に使う変数名を外部から与えるパラメータにした。

```{r チャンク名_{{prm_chankname}}, fig.cap="{{fig_name}}", out.width="90%", fig.asp=0.45, fig.align="center"}
ggplot(data=DAT, aes(x=時間, y={{target_var}}) +
geom_line() ...

```

呼び出し側はknitr::knit_expandを利用する。第1引数はテンプレート名、第2引数以降は設定したパラメータを列挙する。ループなどを使う場合は、変数でパラメータを渡してやればよい。他にもif_elseなどで条件別にパラメータを変更したりなど、いろいろできる。Knit_expandでテンプレートに変数を埋め込んだ状態のコードをオブジェクトにいったん保存する。次に、knitr::knitでオブジェクトに入れておいたチャンクを解釈したうえでcatでRmarkdown側に出力する。これで、ループを使って連続的にチャンクを生成できる。デバッグをする場合などは、インラインで図表を確認できない(生成されたチャンクのコードが表示される)のでちょっと不便。

rmd <- paste(
  knitr::knit_expand(
    "sub.Rmd",
    prm_chankname="チャンク名",
    fig_name="キャプション",
    target_var="工場1"
  ),
  collapse = "\n"
)
rendered <- knitr::knit(text = rmd, quiet = TRUE)
cat(rendered, sep="\n")

コードチャンク内からLatexの改ページを出力

ループなどを使って連続的にグラフを出力するとき、グラフ以外にLatexのコードも出力したい場合がある。

改ページを出力する場合。チャンク出力の変更と、Latexコマンド前後に改行を入れて、Latexコードに変換された際に適切に解釈できるようにすることが重要。

# チャンクの出力をasisにして生のRの結果を出力
```{r results="asis"}
  # catで\newpageを出力
  # 前後に改行を入れ、コマンド冒頭の「\」をエスケープ
  cat("\n\n\\newpage\n")

```

半角スペース

日本語を出力する関係でtexエンジンはxelatexを使うため、半角スペースは適切にエスケープしておかないと出力されない。エスケープの仕方は場所によって異なる。例えばknitr::kableのcaptionに設定する場合ならば

caption="日本語のテキストに\\ 半角スペースを入れます。"

のように、半角スペースの前に「\\」を入れる。他方で、ggplotなどでグラフを出力する際に、チャンクオプションに指定する「fig.cap」では以下のようにする。

... fig.cap="日本語のテキストに\\\ 半角スペースを入れます"

こちらではは「\」が3つ必要。おそらく1つ目はLatexコード認識のため、2つ目は3つ目の「\」のためのエスケープ。

テーブルの5行ごとに縦スペースを追加(しない)

Rで作成した表は、knitr::kableおよびkableExtrパッケージを利用すると、比較的容易に美しくPDFに出力できる。

Latexの機能かRmarkdown、pandocの機能かは調べていないが、標準ではLatexに変換した段階で表の5行ごとに\addlinespaceが入っている。場合によっては等間隔にしたい。これを解除するためには、kableに以下のようにオプションを渡す。

kable(...., linesep="")

linesep=““はkableのヘルプに記載されいないが、kable_latexに引数がそのまま渡されて解除されるようだ。

テーブルなどのラベルを改行する

Latexに変換するときに改行を保持するための関数を使わないと、R側の改行が無視される。

col.names = linebreak(c("男性","女性","クラス\n全体"))

このように、改行「\n」が入った文字列やベクトルをlinebreakに渡してやるとよい。

回帰分析の結果出力

回帰分析の結果の出力は、texregパッケージを使うと手間なく美しくできる。類似パッケージもあるが、とりあえずこのパッケージを使っている。

ここでは主にPDFに出力するために使っているので、要するにLatexのコードを出力するために利用している。

単純に回帰分析の結果を出力するだけなら以下。

# lm()の結果がres.lmに代入されているとして
texreg(
  list(res.lm),
  digits = 3,
  caption = "回帰分析の結果",
  float.pos = "h"
)

関数の第一引数に回帰分析の結果が入ったオブジェクトをリストで渡してやればよい。複数の結果を与えると、自動的に横に並べて表示してくれる。2つのモデルの結果で同じ変数があれば、同じ行に示してくれる。モデル内の変数が異なる行は、存在しない場合は空白になって表示される。その他によく使うのは、小数点以下の桁数、キャプション、Latex側でfloatで表を挿入する際のオプション指定など。これ以外にも、モデル名を設定(custom.model.names)や変数名を設定(custom.coef.names)、変数の順序(reorder.coef)、表示しない変数(omit.coef:正規表現で除外変数を指定)など細かく指定できる。

なお、開発中は「texreg」の部分を「screenreg」にしておくと、インラインで見やすいように出力してくれる。

ロジスティック回帰を行ったときなど、結果の係数を指数変換してオッズ比にしたい場合などがある。その場合は、次のように係数をいったん取り出して、オーバーライドしてやればよい。

# res.glmにglm()の結果が代入されているとして

# texregパッケージのextract関数で各種係数を取り出す
# ロードしていてもパッケージ名を指定しないと同名の関数が被ることがよくある
res.glm.ext <- texreg::extract(res.glm)

texreg(
  list(res.glm),
  # 上のリストで与えたモデルと同じ順で上書きする係数の入ったオブジェクトをリストで渡す
  #  ここでは先にextractで取り出した係数を指数変換してオッズ比にしている
  override.coef = list(exp(res.glm.ext@coef)),
  override.se = list(exp(res.glm.ext@se)),
  digits = 3,
  caption = "ロジスティック回帰の結果",
  float.pos = "h"
)

数値の桁そろえ

texregでも、次のトピックのようにPDF出力時にsiunitxを利用して桁そろえができる。

オプションに以下を与えれば自動的に適用される。指定しなければ、有意水準のマークがつくとずれてしまう。

siunitx = TRUE

数字の桁そろえ

RmarkdownからPDに表を出力する際に、表内の数値の桁をそろえる場合の方法。数値だけの表であれば、基本的にKableで設定できるが、有意水準を示す「***」マークなどを含める場合に、マークの分だけずれてしまう。理想的には、小数点の位置など、マークを抜いた数値部分の桁が揃ってほしい。それを実現するパッケージがLatexの「siunitx」。

使い方は、YAMLでパッケージを読み込む。

- \usepackage{siunitx}
#- \newcolumntype{d}{S[table-format=3.2]}
#- \newcolumntype{d}{S[table-format=+3]}

次に、kableのalignオプションで、桁そろえを適用する列に「S」を設定する。ないしは「S[オプション]」のように、siunitxのオプションも一緒に設定する。オプションは、上記のように別の文字にカラムタイプを設定し、その文字を設定してもよい。

knitr::kable(
  caption="表のタイトル",
  align = c("l", "S", "l", "S"),
  digits = 3,
  format.args = list(nsmall=3)
  ...

ついでに、数字だけの桁そろえは、小数点以下の桁数はdigitsで指定し、その桁まで少数がない場合に0で埋めるには、format.argsにnsmallを与えると、その桁まで必ず表示される。

なお、siunitxを利用すると”striped”とは相性が良くないらしく、縞模様がおかしくなる。そこで、先にsiunitxを適用してから行の色を指定する。

# 色を付ける行にextra_latex_afterでLatexのオプションを与えて色を付ける。
## なお、ここでの指定はヘッダではなく表の内容の1行目が「0」として、色を付ける行数を指定する。
row_spec(c(2, 4)-1, extra_latex_after="\\rowcolor{gray!6}")

図のテキスト回り込み

例によってRmarkdownからPDFを出力する際の細かい設定は、RmarkdownからLatexを制御しなければならないため、やや複雑になる。他に利用しているパッケージによってはうまく動かないなど、実用レベルではない気もする。とりあえず現状のメモ。

まず、Latexでwrapfigを読み込む。

header-includes:
  - \usepackage{wrapfig}

ただし、titlesecパッケージを読み込むとうまく動かない。

  1. \usepackage[compact]{titlesec}

bxjsarticleのクラスオプションにeveryparhook=compatを指定しないとうまく動かない。

documentclass: bxjsarticle
classoption: xelatex,ja=standard,everyparhook=compat

次に、Rmarkdownで図を出力する際にLatex側に指定する環境を書き換える(たぶん)

```{r, include = F}
defOut <- knitr::knit_hooks$get("plot")  # save the default plot hook 
knitr::knit_hooks$set(plot = function(x, options) {  # set new plot hook ...
  x <- defOut(x, options)  # first apply the default hook
  if(!is.null(options$wrapfigure)) {  # then, if option wrapfigure is given ...
    # create the new opening string for the wrapfigure environment ...
    wf <- sprintf("\\begin{wrapfigure}{%s}{%g\\textwidth}", options$wrapfigure[[1]], options$wrapfigure[[2]])
    x  <- gsub("\\begin{figure}", wf, x, fixed = T)  # and replace the default one with it.
    x  <- gsub("{figure}", "{wrapfigure}", x, fixed = T)  # also replace the environment ending
  }
  return(x)
})
```

 図を出力するチャンクでwrapfigureオプションを指定する。ここでは図を右寄せで行幅の70%の大きさに指定している。

```{r echo = F, warning = F, message = F, fig.width=7, fig.height = 6, out.width = ".7\\textwidth", fig.cap = "My Flowchart", fig.align="right", wrapfigure = list("R", .7)}
plot(mpg ~ hp, data = mtcars)
```

ということで、何とか回り込みを設定できるものの、いろいろと問題が多い。素直にRmarkdownから指定できるようになってほしい。

knitrボタンとrender()

Rstudioのknitボタンではうまくいくのに、コンソールからrender()関数でRmdファイルを呼び出してPDFを生成するとき、kableExtraでテーブルにいろいろやっている場合にエラーが出る。スマートな解決ではないが、一応原因らしきものがわかった。原因の追究に役立ったのは以下。

基本的には、knitボタンとrender()関数は同じはずだが、render()関数のenvirの設定などで異なる場合があるらしい。しかし、envir=new.env()としても、エラーは変わらない。上記URLで言及されているが、kableExtraパッケージは、ロードされた段階でYAMLヘッダに必要なLatexパッケージをロードする処理が入っているらしい。knitボタンを押すと、これらが正常に機能するのでうまくPDFまで変換できる。これをreder()関数で処理すると、不足するLatexパッケージをロードしないため、LatexからPDFに変換するところで未定義のコマンドなどが生じてエラーとなる。

YAMLヘッダのpdf_documentのオプションで「keep_tex: yes」として、knitボタンとrender()関数で同じRmdファイルを処理して途中経過のtexファイルを比較すると、render()関数で処理した方のプリアンブルに、knitボタンで処理した場合に含まれる\usepakage行がないことがわかる。この差分をRmdファイルにあらかじめ書き込んでやれば、問題なくPDFに変換できるようになる。本来はkableExtraパッケージが適切に処理をしてくれるはずなのだが、キャッシュなどの関係でうまくいっていないらしい。現時点で解決もしていないようだ。

kableExtraパッケージが必要とするLatexパッケージは、同パッケージのkableExtra_latex_packages()関数で確認できる。ここで出力されるパッケージをあらかじめYAMLヘッダに含めておくと問題が発生しない。

> kableExtra::kableExtra_latex_packages()
header-includes:
  - \usepackage{booktabs}
  - \usepackage{longtable}
  - \usepackage{array}
  - \usepackage{multirow}
  - \usepackage{wrapfig}
  - \usepackage{float}
  - \usepackage{colortbl}
  - \usepackage{pdflscape}
  - \usepackage{tabu}
  - \usepackage{threeparttable}
  - \usepackage{threeparttablex}
  - \usepackage[normalem]{ulem}
  - \usepackage[utf8]{inputenc}
  - \usepackage{makecell}
  - \usepackage{xcolor}

キャッシュが問題らしいということで、もう少し検証を進めてみた。RstudioでRセッションを再起動し、その直後(環境に何もロードしていない状態)にしてrender()関数を呼ぶと、上記の処理をしなくても問題なくPDFに変換される。ただし、同じ関数をもう一度呼ぶとエラーになる。このとき、既に環境としてkableExtraがロードしているため問題になるようだ。render()関数のenvirにnew.env()を設定しても、完全に真っ新な環境から始まるわけではないため、既にkableExtraが読み込まれた状態だと判断されるのだろうか。

この問題に行き当たったのは、連続的にPDFを生成しようとしてrender()関数をループで呼び出したときにエラーが出たことである。knitボタンを使う場合、Rstudioの新しい環境を用意したうえで作業が行われるが、Rスクリプトからrender()関数を呼ぶ場合は、new.env()を与えても、結局は現在のglobal環境から派生した新しい環境になる(Rスクリプトがわで用意した変数に、renderで呼び出したRmdファイル内からアクセスできている)ので、kableExtraパッケージがロードされている前提でうごいてしまい、YAMLヘッダにLatexパッケージが読み込まれないような気がする。rstudioapi::restartSession()などで、一度環境をクリアする方法も考えたが、そうするとループ処理の範囲が大きくなり、同じデータの前処理を無駄に何度もやらなければならない。したがって、YAMLヘッダにあらかじめ必要なLatexパッケージを書き込むのが、いまのところ最善策だろう。

丸囲み文字

Rmarkdownで丸囲み文字を出力するには、Latexのコマンドを使って以下のようにする。

\raise0.2ex\hbox{\textcircled{\scriptsize{1}}}

しかし、行頭で用いるとエラーになってしまう。

! You can't use `\raise' in vertical mode.

はっきりわかっていないが、\raiseコマンドを行頭に持ってくると「垂直モード」で利用していると判定されてエラーになってしまうようだ。\raiseは無くてもかまわないが、行が若干ずれるため見栄え的には入れたい。そこで色々試した結果、行頭に持ってくる場合は以下の設定があまり問題ないようである。

\indent\raise0.2ex\hbox{\textcircled{\scriptsize{4}}}
または
\noindent\raise0.2ex\hbox{\textcircled{\scriptsize{4}}}  

インデントを入れてやることで、行内だと判断できるようになるようだ。どちらを使うかは、行頭のインデントの絶対設定と合わせておけばよいだろう。

メモ/rmarkdown.txt · 最終更新: 2021/03/06 21:36 by Wiki Editor