ggplot2で縦に並べたグラフの横幅を揃える

(※2015/06/03 20:27追記あり)
下記の記事に基づく発言に関連してどうも某所で勘違いが発生しているようなので。www.exegetic.biz

使用データ

下記のように生成したものを用いる。

x <- 0:100
x <- data.frame(x = x, y1 = sin(x * pi / 10), y2 = x^2)

なぜ横幅がずれるのか

まず、これをbaseで普通にプロットする。

layout(matrix(1:2, 2))
plot(y1 ~ x, type = "l", data = x)
plot(y2 ~ x, type = "h", data = x)
par(old)

f:id:Rion778:20150602235943p:plain
グラフの横幅はずれない。
baseを用いたプロットでは、あえてずらすような細工をしない限り、「グラフの横幅のずれ」は発生しない。
よって、縦軸を揃えるためにマージン等を設定する必要は無い(そのままでは余白が大きくなりすぎるという問題はあるが)。
ちなみに、y軸の目盛りラベルが話にならない*1ので、las = 1を設定して再確認してみるもやはりずれない。

layout(matrix(1:2, 2))
plot(y1 ~ x, type = "l", data = x, las = 1)
plot(y2 ~ x, type = "h", data = x, las = 1)
par(old)

f:id:Rion778:20150603000700p:plain
ずれないが、下のグラフのy軸ラベルと目盛りラベルが重なってしまっている。
といったところでピンとくるかもしれないが、gridの作図では*2、baseの作図と以下の点が異なる。

  • y軸の目盛ラベルは水平方向がデフォルト(las = 1を指定した場合と同様)。
  • y軸の目盛ラベルの幅に応じて余白が自動調整される。

これは通常のプロットではほとんど問題にならないが、y軸ラベルの長さが異なるグラフを縦に並べると、調整なしでは横幅が必ずずれるという弊害になって現れる。

library(ggplot2)
library(gridExtra) # grid.arrange()関数のために必要
p1 <- ggplot(x, aes(x = x)) + geom_line(aes(y = y1)) + theme_classic()
p2 <- ggplot(x, aes(x = x)) + geom_bar(aes(y = y2), stat = "identity") + theme_classic()
grid.arrange(p1, p2)

f:id:Rion778:20150603001835p:plain
gridの作図ではlayoutを用いたレイアウトはできないので、grid.arrangeなどの関数を用いる必要がある*3
なお、最初に提示したリンク先の例では上下のグラフの高さも設定しているので、これとは出力が異なっている。

解決策

gridの作図では、作図オブジェクト(graphical object; grob)というオブジェクトが生成され、これを通じて描画の制御ができる。
よって、横幅を揃えるためには、まずgrobから横幅や縦幅といったレイアウトの情報を取り出す*4

g1 <- ggplot_gtable(ggplot_build(p1)) 
g2 <- ggplot_gtable(ggplot_build(p2))

ggplot_build関数は描画に必要な全ての情報をplotオブジェクトから生成し、ggplot_gtable関数はそこから横幅や縦幅といったレイアウトに必要な情報を含むgtableオブジェクトを生成する*5
gtableオブジェクトから横幅の情報を取り出し、これをp1とp2のどちらかに合わせる。合わせる基準としては、幅の広い方を選択することにする(文字の重なりを防ぐため)。
gridパッケージには単位を含むオブジェクトの要素ごとに大小を比較するunit.pmax、unit.pmin関数が含まれているので、これを用いて横幅の値の大きい方を取り出す。

maxWidth <- unit.pmax(g1$widths, g2$widths)

この情報を用いてグラフのパラメータを上書きすることで、横幅を揃えることができる。

g1$widths <- maxWidth
g2$widths <- maxWidth
grid.arrange(g1, g2)

f:id:Rion778:20150603003838p:plain
要するに、gridに起因する横幅のずれの調整にggplot2に含まれる関数が使えるという話。

追記


ということを教えていただいた。
size=に"max"や"min"を指定するとエラーが出るが、gtableをこれhttps://github.com/baptiste/gtableに差し替えれば動作する様子。first/last指定でも大抵は事足りるとは思うが。

*1:そもそもどうしてこの方向がデフォルトなのか今ひとつ納得出来ない。

*2:つまるところggplot2が元凶ではない。

*3:grid.arrangeはarrangeGrob関数のラッパーで、arrangeGrob関数はかなり色々なカスタマイズが便利にできる関数の様だが話がずれるのであまり調べていない。

*4:ggplot2はこの2つの関数を含むので、gridに起因する横幅問題の解決が容易になる、というのが元記事の要点である。

*5:と思うがこれもあまり良く調べていない。