読者です 読者をやめる 読者になる 読者になる

ggplot2 2.2.0で2軸グラフを描くときのメモ

R ggplot2

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")

f:id:yutannihilation:20161002005511p:plain:w450

そこで、スケールの異なる軸をもうひとつ作りたい!となるわけです。

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")
  )

f:id:yutannihilation:20161002011049p:plain:w450

うーん、もうちょいスマートなやり方があるはず...。誰か思いついたら教えてください。