Technological Congruence

いろんな記録に

gtableを使ってggplot2のグラフ部分の大きさを揃えてknitrで出力する

論文で使うグラフを作るのにggplot2は大変に強力なのですが、
そのグラフの大きさを指定するにはコツがあります。

問題

私はR Sweave+knitrで論文を書くことが多いのですが、
たとえばchunk optionにfig.height=6, fig.width=6を設定して

library(ggplot2)
g0 <- ggplot(data=iris, aes(x=Sepal.Length, y=Petal.Length, color=Species))
+ geom_point() + scale_colour_grey() + theme_classic()
print(g0)

したりすると

f:id:lshenqi:20140526142008p:plain

こんなグラフになります。

一見問題ないように見えるんですが、縦軸と横軸の長さが合ってなかったりするんです。

g0 + theme(legend.position='none')

して凡例を非表示にすると

f:id:lshenqi:20140526142510p:plain

このようによくわかります。

つまり、凡例があるかないか、あるいはその大きさによって、
グラフ自体の大きさが変わってしまうということです。 普通のレポートならこれでもいいのですが、やはり論文となると、
カッチリ全部揃った形で出力したくなります。

どうするか

結論からいうと、gtableパッケージを使います。 gistにラッパー関数を用意しました。

使い方

grid.newpage()
grid.draw(combine_plots(list(g0), T, 6, 1.5))

f:id:lshenqi:20140526151307p:plain

簡単でしょ?

解説

ひとつひとつ解説していきます。

まず、

grid.newpage()

ですが、自身でgtableオブジェクトを作っていますので手動で呼ぶ必要があります。 (そうしないとグラフが重なって描画されます)

次に、

grid.draw(combine_plots(list(g0), T, 6, 1.5))

ですが、grid.draw()関数にgtableオブジェクトを渡して描画します。
combine_plots()関数がそれに当たります。
引数は、

  1. リスト
  2. 論理値
  3. 数字
  4. 数字

になっています。

まず最初のリストですが、そうです、リストです。
ggplotオブジェクトが格納されたリストを投げます。
実はcombine_plots()は大きさを揃えるだけではなく、
複数のグラフを結合することもしちゃいます。

次の論理値ですが、最初で渡されたリストを結合する向きを示します。
TRUEで横向きに結合、FALSEで縦に結合します。
今のところは横向きしか対応してないのでFを渡すとエラーを吐きます。

最後の数字二つですが、最初は、グラフ部分の長さ(=高さ=幅)
次が、凡例の幅になっています。
この場合6×6のグラフ+凡例の幅が1.5になるということです。

実践

combine_plotsだけでも便利っちゃ便利なんですが、
このままだと毎回毎回きちんとchunkでfig.widthなりfig.heightなりを指定してあげないといけません。
これはめんどくさい。

なので、自分でデバイスに出力してそれを読み込むことにします。 例のごとくirisを使います。

g1 <- ggplot(data=iris, aes(x=Sepal.Width, y=Petal.Width, color=Species))
+ geom_point() + scale_colour_grey() + theme_classic() + theme(legend.position='none')
g2 <- ggplot(data=iris, aes(x=Sepal.Width, y=Petal.Length, color=Species))
+ geom_point() + scale_colour_grey() + theme_classic()
g <- combine_plots(list(g1,g2), T, 6, 1.5)
wt <- foreach(wt = g$widths, .combine='+') %do% wt
ht<- foreach(ht = g$heights, .combine='+') %do% ht
pdf(file='figures/ggplot.pdf', width=wt, height=ht)
grid.draw(g)
dev.off()

f:id:lshenqi:20140526153855p:plain

Petal.Widthに対してSepal.WidthとSepal.Lengthから相関があるか調べます。

このように、二つのグラフの大きさは揃えながら、一つだけ凡例を表示するなんかもできます。

あとはこれを<img>タグなり\begin{figure}なりしてやればよいということになります。 しかしそれも手動でいろいろ指定するのはめんどくさいです。
ここまで来たらそれも自動化しましょう。

埋め込み自動化

latex_draw <- function(identifier, caption="", landscape = TRUE, fig.pos='!hp'){
  t <- paste0("\\begin{figure}[", fig.pos, "]\n\\caption{", 
        caption, "\\label{fig:", identifier, "}}\n\\includegraphics[width=\\maxwidth]{figures/",
        identifier, ".pdf}\n\\end{figure}")

  t <- ifelse(landscape == TRUE, paste("\\begin{landscape}\n", t, "\n\\end{landscape}"), t)
  cat(t)
}

このような関数を定義します。 (ファイル名もpaste0()を使って全部関数化しておくとさらに便利です。)
横方向に結合したグラフは普通のレポートでは見難いので、pdflscapeパッケージを使います。
プリアンブルに\usepackage{pdflscape}を追加し、
chunkにresults='asis'を指定した上で、

latex_draw('ggplot', 'Patal.Width against Sepal')

すると、この通り。

f:id:lshenqi:20140526155554p:plain

快適な論文ライフを!