Excelでは左右に軸があるグラフを簡単に作ることができますが、ggplot2には、2軸のグラフを簡単に作る方法が準備されていません。ggplot2で左右に軸があるグラフを作る方法についてまとめます。
表示したいデータ
以下のようなデータをグラフで示すことを考えます。
y1 = 0〜1
y2 = 100〜300
x <- c(0:10)
df <- data.frame(x = x,
y1 = 0.1*x + 1,
y2 = 20*x + 100)
df
x | y1 | y2 |
---|---|---|
0 | 1.0 | 100 |
1 | 1.1 | 120 |
2 | 1.2 | 140 |
3 | 1.3 | 160 |
4 | 1.4 | 180 |
5 | 1.5 | 200 |
6 | 1.6 | 220 |
7 | 1.7 | 240 |
8 | 1.8 | 260 |
9 | 1.9 | 280 |
10 | 2.0 | 300 |
このデータを同じグラフに表示すると、y1とy2の値が大きく異なるため、y1の値がほとんどx軸に張り付いて、データの傾向を見ることができません。
library(ggplot2)
ggplot(df, aes(x = x)) +
geom_point(aes(y = y1, colour = "y1"))+
geom_point(aes(y = y2, colour = "y2"))
このような場合、Excelでは、簡単にy1もしくはy2を右の軸に表示するように設定することができますが、ggplot2では一手間(一手間以上?)かける必要があります。この方法をまとめます。
sec.axisパラメータ
ggplot2ではscale_y_continuous()関数に、第2軸を追加するためにsec.axisというパラメータが用意されており、これを指定することで、第2軸を設定することができます。しかし、この機能は左軸の目盛値を変換して右軸に新たな目盛を示すだけで、プロットの位置を変えることはできません。データを表示するのに、単位変換した軸もあわせて表示することを想定しているのだと思います。下の図だと右軸は左軸の100分の1を値にしています。左軸はcm単位、右軸はm単位で表示するような感じです。
ggplot(df, aes(x = x)) +
geom_point(aes(y = y1, colour = "y1")) +
geom_point(aes(y = y2, colour = "y2")) +
scale_y_continuous(
sec.axis = sec_axis( ~ . /100, name = "y2")
)
今回の場合のように、データy1とデータy2を別々の軸で表現したい場合、右軸に設定するデータについては、軸の表示だけでなく、データのプロット位置も適切に調整してやる必要があります。
2軸のグラフを作る
以下のようなグラフを作成してみます。
- 左軸にy1、右軸にy2
- y1の0〜4とy2の100〜300が対応
- y1の-1〜5の範囲でグラフ化
後々、いろんな場面で使えるように、右軸に示すデータの位置を調整する関数(variable_scaler)と右軸の値を調整する関数(axis_scaler)を準備しておきます。
variable_scalerは、y1、y2の値の対応が以下となるような変換を行います。
$$
(y_{1min},y_{1max}) = (0,4)\\
(y_{2min},y_{2max}) = (100,300)
$$
プロット位置の変換は、線形変換 + 平行移動で表現できますので、
$$
y_{1}=a \times y_{2}+b
$$
の形になります。従って、
$$
y_{1min}=a \times y_{2min}+b \\
y_{1max}=a \times y_{2max}+b
$$
ですので、連立方程式を解いて
$$
a = \frac{y_{1max}-y_{1min}}{y_{2max}-y_{2min}} \\
b = \frac{y_{1min}y_{2max}-y_{1max}y_{2min}}{y_{2max}-y_{2min}}
$$
# 左軸: y1 0-4 が右軸y2 100-300に対応するようなスケーリング
yaxis1 <- c(0, 4)
yaxis2 <- c(100, 300)
#------------------------------------------------------
# variabel_scaler(y2, yaxis1, yaxis2)
# y2 : y2軸に示すデータ
# yaxis1, yaxis2 : y1軸とy2軸の対応
# 戻り値 : y2の値をy1軸相当の位置にした値
#-------------------------------------------------------
variable_scaler <- function(y2, yaxis1, yaxis2){
a <- (yaxis1[2] - yaxis1[1]) / (yaxis2[2] - yaxis2[1])
b <- (yaxis1[1] * yaxis2[2] - yaxis1[2] * yaxis2[1]) / (yaxis2[2] - yaxis2[1])
ret <- a * y2 + b
ret
}
右軸の目盛値の調整についても関数化しておきます。プロット位置の調整同様、軸の目盛値の調整も、線形変換 + 平行移動で表現できますので、
$$
y_{2}=c \times y_{1}+c
$$
の形になります。形式的にはプロット位置の調整の変換式で(y_1)と(y_2)が入れ替わった形です。
$$
y_{2min}=c \times y_{1min}+d \\
y_{2max}=c \times y_{1max}+d
$$
$$
c = \frac{y_{2max}-y_{2min}}{y_{1max}-y_{1min}} \\
d = \frac{y_{2min}y_{1max}-y_{2max}y_{1min}}{y_{1max}-y_{1min}}
$$
#------------------------------------------------------
# axis_scaler(y1, yaxis1, yaxis2)
# y1 : y1軸の値(sec_axis()の".")
# yaxis1, yaxis2 : y1軸とy2軸の対応
# 戻り値 : y2の値をy1軸相当の位置にした値
#-------------------------------------------------------
axis_scaler <- function(y1, yaxis1, yaxis2){
c <- (yaxis2[2] - yaxis2[1]) / (yaxis1[2] - yaxis1[1])
d <- (yaxis2[1] * yaxis1[2] - yaxis2[2] * yaxis1[1]) / (yaxis1[2] - yaxis1[1])
ret <- c * y1 + d
ret
}
これらの関数を使うことで、ggplot2で2軸のグラフを書くには以下のようにします。
- 2軸に表示したいデータについてvariable_scaler()関数を適用する(下のコードの3行目)
- scale_y_continuous()関数のsec.axisパラメータにaxis_scaler()関数を渡す(下のコードの5行目)
ggplot(df, aes(x = x)) +
geom_point(aes(y = y1, colour = "y1")) +
geom_point(aes(y = variable_scaler(y2, yaxis1, yaxis2), colour = "y2")) +
scale_y_continuous(
sec.axis = sec_axis( ~(axis_scaler(., yaxis1, yaxis2)),
name = "y2")
) +
coord_cartesian(ylim = c(-1, 5)) +
annotate(geom="text", x = 0.2, y = 4.3, hjust = 0, vjust = 1,
label="左軸にy1、右軸にy2", family = "HiraKakuPro-W3")+
annotate(geom="text", x = 0.2, y = 3.8, hjust = 0, vjust = 1,
label="y1の0〜4とy2の100〜300が対応", family = "HiraKakuPro-W3") +
annotate(geom="text", x = 0.2, y = 3.3, hjust = 0, vjust = 1,
label="y1の-1〜5の範囲でグラフ化", family = "HiraKakuPro-W3")
ggplotで描いたグラフの軸の名称を変える方法はこちら。
まとめ
- Rで2軸のグラフを作るにはscale_y_continuous()関数のsec.axisパラメータを設定する。
- sec.axisパラメータは右軸の目盛を書くだけなので、右軸に表示したいデータのプロット位置も適切に変換して表示する必要がある。