まずは蝋の翼から。

学んだことを書きながら確認・整理するためのメモブログ。こういうことなのかな?といったことをふわっと書いたりしていますが、理解が浅いゆえに的はずれなことも多々あると思うのでツッコミ歓迎

ggplotで別々のデータを使った2軸グラフを使いたい

ggplot2で異なるグラフを重ねて、yの値が左右で異なるグラフを2軸グラフという。 イメージ的には、下記の2グラフ(平均価格をpoint、平均カラット数を棒グラフ)を1画面に収めるイメージ(※棒グラフの数値テキストは、以降での値を確かめる用なので除く)。

~ 個人的に2軸グラフは見づらくなるしゴチャゴチャしやすいので書くべきではないと思いますが、他からの指示で出さないといけないときがあるんや・・・。 ~

library(tidyverse)
library(patchwork)

# データ作成
df <- sample_n(diamonds,1000) %>% 
  group_by(clarity) %>% 
  summarise(mean_price = mean(price), mean_carat = mean(carat)) %>% 
  ungroup()

# clarity mean_price mean_carat
# <ord>        <dbl>      <dbl>
#   1 I1           2705.      1.00 
# 2 SI2          5076.      1.09 
# 3 SI1          4192.      0.876
# 4 VS2          3580.      0.729
# 5 VS1          4027.      0.785
# 6 VVS2         2611.      0.559
# 7 VVS1         2141.      0.476
# 8 IF           2848.      0.466

point <- df %>%
  ggplot(aes(color = clarity, fill = clarity, group = clarity)) +
  geom_point(aes(clarity, mean_price),color = "red", size = 5) +
  labs(x = "clarity", y = "mean price") 


bar <- df %>%
  ggplot(aes(color = clarity, fill = clarity, group = clarity)) +
  geom_bar(aes(clarity, mean_carat), stat = "identity") +
  geom_text(aes(clarity, mean_carat, label = round(mean_carat,2)),color="black") +
  labs(x = "clarity", y = "mean carat") 

point | bar

f:id:chito_ng:20190228084114p:plain

どうやって別データへの2軸グラフを作るか

単純な方法

scale_y_...にsec.axisという、2軸目に対して目盛を振る方法があるのでそれを利用する。 ggplot2: Secondary Y axis

sec.axisでは1軸目の数値「.」を用いた計算式マッピングで目盛を記載する。

例えば、1軸目が0~1000で100刻みであるなら、~ ./100 を計算式に書くと2軸目は目盛が0~10で1刻みとなる。
2軸目に0から1.2までで目盛を取りたいのであれば、1軸目の最大値で割った値に取りたい最大値をかければいい。つまり、 ~./1000 * 1.2を計算させる。

今回は、1軸目が0~4500、2軸目を0~1.2で取りたいので計算式は ~/4500 * 1.2 となる。

注意点として、sec.axisはあくまで2軸目に数値をプロットしているだけなので2軸目と対応させたいグラフのスケールは1軸目に対応している。下図では、2軸目の目盛はうまくいっているが対応させたい棒グラフ自体は1軸目準拠でスケーリングされるので、どれも0~1.2程度なことからほぼ0の棒グラフになっている。

df %>%
  ggplot(aes(color = clarity, fill = clarity, group = clarity)) +
  geom_point(aes(clarity, mean_price), color = "red", size = 5) +
  geom_bar(aes(clarity, mean_carat), stat = "identity") + 
             # 2軸設定
             scale_y_continuous(sec.axis = sec_axis(~./4500 * 1.2, #2軸目の一番上を1.2にする
                                                    name = "mean carat")) + 
             labs(x = "clarity", y = "mean price") 

f:id:chito_ng:20190228084428p:plain

これを避けるためには、棒グラフの数値自体を2軸目のスケーリングに合わせることで上手くいく。
最大値が2軸目は1.2で、1軸目は4500なので1.2で割り4500をかけると棒グラフの数値は1軸目と同じスケーリングになる。

df %>%
  ggplot(aes(color = clarity, fill = clarity, group = clarity)) +
  geom_point(aes(clarity, mean_price),color = "red", size = 5) +
  geom_bar(aes(clarity, mean_carat / 1.2 * 4500 ), stat = "identity") + # 2軸目の数値を1軸目にスケーリングする
  # 2軸設定
  scale_y_continuous(sec.axis = sec_axis(~./4500 * 1.2, #2軸目の一番上を1.2にする
                                         name = "mean carat")) + 
  labs(x = "clarity", y = "mean price") 

f:id:chito_ng:20190228084449p:plain

点と棒グラフが重なって鬱陶しい場合は棒グラフの数値を1/2にし、軸目盛の最大値を2倍にする。

df %>%
  ggplot(aes(color = clarity, fill = clarity, group = clarity)) +
  geom_point(aes(clarity, mean_price),color = "red", size = 5) +
  geom_bar(aes(clarity, (mean_carat / 1.2 * 4500 )/2), stat = "identity") + # 2軸目の数値を1軸目にスケーリングする
  # 2軸設定
  scale_y_continuous(sec.axis = sec_axis(~(./4500 * 1.2) * 2, #2軸目の一番上を1.2 * 2にする
                                         name = "mean carat")) + 
  labs(x = "clarity", y = "mean price") 

f:id:chito_ng:20190227203319p:plain

参考: ggplot2: Secondary Y axis

scales::rescale()関数の利用

上記の方法は、1軸目の数値⇔2軸目の数値の変換があまりスマートではない。

scales::rescale()は任意の範囲に対応したマッピングをおこなってくれる。

rescale function | R Documentation

df$mean_carat
# 1.0009091 1.0887931 0.8755349 0.7294000 0.7846452 0.5590110 0.4756716 0.4662162

scales::rescale(
  x = df$mean_carat, # スケールしたい値
  to = c(0,4500) # スケールする範囲
#4500.0000 3327.0853 2052.9968 2085.9671 1236.9952  888.3146  107.5510    0.0000

また、range関数は引数の値の最小値、最大値を返すため、スケールする範囲はrange関数を用いると便利とのこと。

ggplot2 2.2.0で2軸グラフを描くときのメモ - Technically, technophobic.

ただ、上手くいかない。

df %>%
  # priceのスケールに合わせたcaratの値を持つ
  mutate(scaled_mean_carat = scales::rescale(x = mean_carat, ## scaleしたい値
                                             to = mean_price_range ## スケールする範囲(price範囲)
                                             )) %>% 
  ggplot(aes(color = clarity, fill = clarity, group = clarity)) +
  geom_point(aes(clarity, mean_price),color = "red", size = 5) +
  # スケールを合わせたcarat値で棒グラフを書く
  geom_bar(aes(clarity, scaled_mean_carat), stat = "identity") +
  # 2軸設定
  # 目盛の数値を元のcarat値にするために戻す
  # おそらく、0~ ではないのでなんかおかしくなる
  scale_y_continuous(sec.axis = sec_axis(~scales::rescale(x = ., #1軸目
                                                          to = mean_carat_range),
                                         name = "mean carat"))  +
  labs(x = "clarity", y = "mean price") 

f:id:chito_ng:20190227203716p:plain

おそらく、マッピングの関係で2軸目の目盛範囲が0.518 ~ 1.156、つまり0からではないのが原因と考えられる。

> scales::rescale(x = df$mean_price, #1軸目
+                 to = mean_carat_range)
[1] 0.7877944 1.1374783 0.8538765 1.1565000 0.8536595 0.9455982 0.5189286 0.5648220

> min(df$mean_carat)
[1] 0.5189286

ただ、sec.axis = c(0,1.1565)としても上手く行かなかった。sec_axis関数の式記述部分の挙動の理解がもうちょい必要そう。

そのうち追記します。