ggplot2 2.2.0で2軸グラフが描きやすくなりました。
描きやすくなりました、という微妙な言い方をしましたが、軸が2つつくれるだけで値のスケールは自分でやらないといけません。その辺のメモ。
2軸グラフを描きたいモチベーションは何かというと、こういう値の範囲のスケールが異なるデータを重ね合わせたいときです。
# value2はvalue1の100倍 x <- data.frame(idx = 1:100, value1 = (1:100 + runif(100, max = 100)) / 100, value2 = 1:100 + runif(100, max = 100) + 75)
これを同じY軸でプロットすると、あまりに範囲が違うのでvalue1
は地べたに張り付いてしまいます。
ggplot(x) + geom_point(aes(idx, value1), colour = "red") + geom_point(aes(idx, value2), colour = "blue")
そこで、スケールの異なる軸をもうひとつ作りたい!となるわけです。
sec.axis
引数
scale_x_continuous()
とscale_y_continuous()
にsec.axis
引数が付きました。second axisという意味らしいです。わかりづらい...
上のRStudioのブログ記事で例として載っているのはこんなやつです。
ggplot(mpg, aes(displ, hwy)) + geom_point() + scale_y_continuous( "mpg (US)", sec.axis = sec_axis(~ . * 1.20, name = "mpg (UK)") )
ドキュメントによると、sec.axis
引数に指定できるのは以下の3つのようです。
sec_axis()
関数dup_axis()
関数- formula(
~
で始まるやつ)
dup_axis()
はメインの軸と同じ軸を表示します。formulaは、上の例でいうと~ . * 1.20
を指定できます。この場合name
のようなほかの要素は指定できません。
p <- ggplot(mpg, aes(displ, hwy)) + geom_point() # 右にも左と同じY軸ができる p + scale_y_continuous( "mpg (US)", sec.axis = dup_axis() ) # 右に軸ラベルなしのY軸ができる p + scale_y_continuous( "mpg (US)", sec.axis = ~ . * 1.20 )
注意すべき点は、sec.axis
で変更されるのはラベルだけで、図中にプロットされる点の値は変化していないということです。軸が増えるのではなく、軸のラベルが増えるだけです。なので、いわゆる2軸プロットをやろうと思えば値の変換は自分でやらないといけません。
scales::rescale()
関数
そんな時に便利なのがggplot2の内部でも使われているscalesパッケージです。rescale()
という関数を使えば、数値のベクトルを指定した範囲に線形変換してくれます。
library(scales) rescale(0:10, to = c(0, 1)) #> [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
これを使って、value2
の値をvalue1
の範囲に割り付けてくれる関数をつくります。これで同じくらいの範囲に値をプロットすることができます。
scale_to_value1 <- function(values) rescale(values, to = range(x$value1))
あと、第2Y軸のメモリに元のvalue2
の値でラベルをつけるために、さらにそれをvalue2
の範囲に割り付けてくれる関数をつくります。これはたとえば、別途ylim()
とかで値の範囲を制限して取り除かれる点があると同じ結果にならないので正しくないんですが(注意!!)、そんな複雑なことをしなければこれで何とかなるはずです。たぶん...
scale_to_value2 <- function(values) rescale(values, to = range(x$value2))
2軸グラフを描いてみる
こうなりました。細かい説明は割愛。
ggplot(x) + geom_point(aes(idx, value1), colour = "red") + geom_point(aes(idx, scale_to_value1(value2)), colour = "blue") + scale_y_continuous( name = "value1", sec.axis = sec_axis(~ scale_to_value2(.), name = "value2") )
うーん、もうちょいスマートなやり方があるはず...。誰か思いついたら教えてください。