StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

Rで棒グラフと折れ線グラフを重ねた2軸グラフを描く

さっき、Rで棒グラフと折れ線グラフを重ねたものを作ろうとして、けっこう手間取りました。最終的に描いたのは↓のようなものなのですが。


f:id:midnightseminar:20200811212316p:plain


「2軸グラフの書き方」「種類の異なるグラフの重ね方」についていろいろ調べたところ、barplot()とplot()を組み合わせるやり方もあるんですが、ggplot2でやるほうがやりやすかったです。以下、まず単なる2軸グラフの書き方をおさらいした後で、棒と折れ線を組み合わせる方法をメモしておきます。
 
 

2軸グラフの作り方

左右の軸をつかってスケールの異なるグラフを重ねたいだけなら、さほど難しくはなく、plot()ですぐできます。
例えば折れ線グラフを2つ重ねたいのだとしたら、まず1つめの変数のグラフを軸無しで書いたあとに、x軸と左軸を描く。その後、par(new=T)で、2つめの変数のグラフを軸なしで重ね描きした上で、右軸を描けばいいです。
で、最後にbox()で枠線を入れ、凡例を付けたければ付けます。

以下、適当に乱数でつくった変数で実行例を書いておきますが、冒頭の成果物にあわせてx軸の変数を日付型にしてるので、そこだけ多少ややこしくなっています。ただの数字であれば、x軸を描くときにaxis.Dateを使う必要はないです。

# 練習用の変数を適当に乱数でこしらえる
x.date <- seq(from=as.Date('2020-04-01'), to=as.Date('2020-05-31'), by=1)
y1 <- sin(seq(length(x))/7)+rnorm(n=length(x), mean=0, sd=0.2) + 2
y2 <- sin(seq(length(x))/5)*100 + rnorm(n=length(x), mean=0, sd=20) + 200

# y軸の範囲をそれぞれ決めておく
y1.lim <- c(min(y1), max(y1))
y2.lim <- c(min(y2), max(y2))

# グラフの左右の余白を少し多めにするためparを設定しとく(特に右)
par(oma = c(0, 1, 0, 3))

# 1枚目のグラフをかく(y1)
# いったん軸なしにするためaxes=Fにしてる
plot(x=x.date, y=y1, ylim=y1.lim, type='l', lwd=1.5,
     xlab='Date', ylab='y1', 
     axes = F,
     main='plot関数での二軸グラフ')

# x軸を追加(日付データなのでaxis.Dateを使う)
axis.Date(1,at=seq(min(x.date), max(x.date),"week"),format="%m/%d")

# 左の軸をかく
axis(2)

# 2枚目のグラフを重ねる(y2)
par(new=T)
plot(x=x, y=y2, ylim=y2.lim, type='l', lty='dotted', lwd=1.5,
     xlab='', ylab='', axes = F)

# 右側の軸の名前をかく
mtext('y2', side = 4, line = 3)

# 右側の軸を表示
axis(4)

# 枠をかく
box()

# 凡例
legend("bottomleft", legend = c("y1", "y2"), lty = c('solid', 'dotted'), lwd=1.5)


f:id:midnightseminar:20200811212338p:plain
 
 

ggplot2で棒グラフを折れ線グラフを重ねる

さて、今度は種類の異なるグラフを重ねるやり方ですが、barplot()とplot()を重ねるやり方だと、今回は日付のデータを使ってることもあって、x軸のコントロールが難しかったので、ggplot2でやることにしました。
あと、今日気づきましたが、MacのRStudioだとplotするときに余白が足りませんというエラーが出まくるのが、ggplot2だと出ないんですね。いままで基本的にplot派でしたが、ggplot派に改宗しようかなと思いました……。


さて作図ですが、半分ぐらいは、西浦博(8割おじさん)氏が5月ぐらいに出していた、新型コロナの実行再生算数を計算するプログラムの作図のところを参考にさせて頂きました。
最大のポイントは、左右の軸のスケールの調整です。
上述の「plotの2軸化」の場合、右側の軸の幅は、2つめの変数の値の幅がそのまま反映されていました。plotはそもそもスケールの異なるグラフを重ねることができるようになってて*1、今回の場合でいうと、後で描いたグラフの主軸を単に右側表示にしただけというわけです。


一方、ggplot2の場合は、2軸グラフを描くときも、y軸のスケール(軸の最小値と最大値)はあくまで共通になります。今回の場合、y軸の縦幅はあくまで、1つめの変数y1の最小〜最大の幅に合わせた尺度で固定される感じになります。
じゃあ、そこにどうやって2つ目の変数を重ねるのかというと、両変数の縮尺を先に計算しておいて、2つ目の変数を1つ目の変数にあわせて縮めたり伸ばしたりして収めるわけです。
で、その後で、右側の軸のところに好きなように(ただし左軸からの変換という形で)目盛りを打つことができるので、この目盛りを、もとの第2変数に対応するものにしておけばよい。


少し分かりづらいですが、たとえば左の軸で表現したい第1変数が0〜10ぐらいのレンジで分布していて、右の軸で表現したい第2変数が0〜100ぐらいのレンジで分布してるとすると、縮尺は1:10になるので、

  1. グラフ領域はまず第1変数にあわせて、y軸が0〜10になるように描く。目盛りは左軸に表示される。
  2. 第1変数をプロットする。
  3. 第2変数を0.1倍して縮め、同じ領域に第2変数のグラフを重ねる。
  4. sec.axisというオプションをつかって、左軸を好きなように変換した軸を右側に設定することができるので、ここで「左軸を10倍する」という変換設定をする。
  5. breaksで目盛りも適当な間隔で設定する。


という手順で、2軸グラフが描かれるわけです。


以下の実行例では、さっき乱数でつくった変数をもっかい使ってるのですが、y1(左軸)y2(右軸)の縮尺を先に計算してscalerという変数に入れてあります。

# データフレームにまとめる
d <- data.frame(Date=x.date, Y1=y1, Y2=y2)

# 各軸の範囲をきめる
# yも最小値〜最大値の形で設定してよいが明示的に与えたい場合が多い気がする
x.lim  <- c(min(d$Date), max(d$Date))
y1.lim <- c(0, 4)
y2.lim <- c(0, 400)


# 左軸と右軸の関係を表すスケーラをつくる
# 各軸の最大最小差の比をとっている
scaler <- (y1.lim[2] - y1.lim[1])/(y2.lim[2] - y2.lim[1])

d %>% 
  ggplot() + 
  geom_bar(aes(x=Date, y=Y1), stat='identity', width=0.7) +
  geom_line(aes(x=Date,y=Y2*scaler, colour = "Y2"), size=1) +
  scale_x_date(date_labels="%m/%d",date_breaks="7 day", 
               limits=x.lim, expand=c(0, 0)) +
  scale_y_continuous(limit=y1.lim, expand = c(0, 0), 
                     sec.axis=sec_axis(trans = ~ ./scaler, 
                        breaks=seq(from=y2.lim[1], to=y2.lim[2], by=50), 
                        name="\nY2\n")) +
  theme(text=element_text(size=12, family="MS Gothic",color="black"),
        axis.text=element_text(size=10, family="MS Gothic",color="black"),
        legend.position="top",
        plot.subtitle=element_text(size=10, color="#666666")) + 
  labs(x="\nDate\n", y="\nY1\n", color = "",
       title='\nggplot2で重ねたグラフ', 
       subtitle='(折れ線を複数追加することもできます)')


f:id:midnightseminar:20200811214800p:plain


sec.axisの中のtransというところには、左軸と右軸の対応関係をformula形式で書くのですが、「.」はデータ全体を表してて、これをスケーラで割るという変換を設定してあります。
x軸が日付なので、scale_x_dateをつかって日付表示の設定をしています。
breaksってところで、右2軸の目盛りを設定しています。水平のグリッドがある場合、左軸の目盛りと右軸の目盛りが噛み合ってたほうが綺麗なので、最初にy1とy2の幅を設定する時に、いい感じの公約数がある値を選ぶのがいいと思います。
themeでフォントを指定してるのは、日本語を文字化けなく表示させるためです。


冒頭に貼った成果物のように、折れ線を2本引きたいときは、geom_line()をもう1個プラスすればいいですね。
グラフのタイトルや軸のタイトルの前後に改行(\n)を入れているのはなんとなく隙間を開けるためです。

*1:だから逆に、重ねる時にスケールが揃ってないことを忘れたりすることがありますねw