ggplot2: 棒グラフに置いたテキストがグラフの枠からはみ出る問題を解決したい (geom_blank)

Rのggplot2で「あー,こんなのあったら良いのになぁぁ!!!悔しいなぁぁ!!!」って時に役立ちそうな,細かいテクニックを紹介するシリーズ第1弾です。ggplotの基本的な文法が分かっている人向けの記事です。

 

今回の問題

棒グラフの外側や隣などに,各棒の値などをテキストとして置きたい状況ってよくありますよね。単純にgeom_barにgeom_textを重ね,グラフをggsaveで保存した場合,テキストがグラフの枠からはみ出て見えなくなっていることがことがよくあります(下図)。今回はその対処法を紹介します。グラフが1つの場合と,複数ある場合(facet_gridなどが必要)で,対処法が異なるので,それぞれ紹介します。

パッケージ準備

library(dplyr)
library(tidyr)
library(ggplot2)

 

グラフが1つの場合:めちゃ簡単

例として,データセットirisのSepal.Lengthの平均値をSpeciesごとに棒グラフにしてみます。グラフを6cm×8cmの大きさでggsaveで出力してみます。

iris %>%
  group_by(Species) %>%
  summarise(mean_Sepal.Length = mean(Sepal.Length)) %>%
  ggplot(aes(x = Species, y = mean_Sepal.Length, label = mean_Sepal.Length)) +
  geom_bar(stat = "identity", width = 0.5)  +
  geom_text(size = 2.4, vjust = -0.5) +
  theme(axis.text.x = element_text(size = 8),
        axis.text.y = element_text(size = 8)) -> p

はい,見事に一番右のvirginicaの数値が枠からはみ出しました。出力するグラフ自体のサイズ(cm)を大きくすれば,はみ出なくはなるのですが,自分の好きなサイズで出力できないのは問題です。

今回のようにグラフが1つの場合,scale_y_continuous関数を用いて解決できます。scale_y_continuousにlimitsを指定してあげることで, 表示するy軸のスケールの最小値,最大値を無理やり変更することが可能です。

virginicaの値が6.588なので,y軸を7まで表示できるようにしましょう。

p <- p + scale_y_continuous(limits = c(0, 7))

 

グラフが複数ある場合 (facet_gridやfacet_wrapを要する場合)

グラフが複数ある場合が,中々厄介です。解決方法を英語でググっても,それらしい記事が中々出てきません。

例として,irisのSpecies以外の4変数 (Sepal.Lengthなど)をSpeciesごとに平均したデータを用います。

iris %>%
  gather(feature, value, -Species) %>%
  group_by(Species, feature) %>%
  summarise(mean_value = mean(value)) -> df
View(df)

 

とりあえずfacet_wrapをscale="free"として,各変数ごとにx軸: Species,y軸: 平均値としてグラフを作って,7cm×17cmで出力してみます。

df %>%
  ggplot(aes(x = Species, y = mean_value, label = mean_value)) +
  geom_bar(stat = "identity", width = 0.5)  +
  geom_text(size = 2.4, vjust = -0.5) +
  facet_wrap(~feature, ncol = 4, scales = "free") +
  theme_bw() +
  theme(axis.text.x = element_text(size = 8, angle = 45, hjust = 1, vjust = 1),
        axis.text.y = element_text(size = 8)) -> p

はい,全てのパネルでテキストが枠からはみ出ました。ここで,先程のようにscale_y_continuousでlimitsを設定した場合どうなるか見てみましょう。

p <- p + scale_y_continuous(limits = c(0, 7))

一番y軸の最大値が大きい枠に合わせることになるので,上のグラフのようになり,他の枠のy軸の最大値まで統一されてしまいます。できれば,facet_wrapのscales="free"の効果をキープして,それぞれのパネルのy軸に異なるスケールを設けたいです。今回は最も楽なgeom_blankをレイヤーとして追加する方法を紹介します。

 

geom_blankによる解決

geom_blankを用いれば,グラフ上に無理やり空間を作ることができます。以下の手順で,facet_wrapのそれぞれの枠のy軸に異なる最大値を設けることができます。

  1. 枠ごとに,枠内の最大値より大きい値(1.1-1.2倍くらい)を取る変数(y_max)を用意する。ここはgroup_byとmutateで用意します。
  2. geom_blank(y = y_max)というレイヤーを追加する。
df %>%
  group_by(feature) %>%
  mutate(y_max = max(mean_value)*1.15) %>%
  ggplot(aes(x = Species, y = mean_value, label = mean_value)) +
  geom_bar(stat = "identity", width = 0.5)  +
  geom_text(size = 2.4, vjust = -0.5) +
  geom_blank(aes(y = y_max)) +
  facet_wrap(~feature, ncol = 4, scales = "free") +
  theme(axis.text.x = element_text(size = 8, angle = 45, hjust = 1, vjust = 1),
        axis.text.y = element_text(size = 8),
        axis.title = element_text(size = 8)) -> p

 

その他の解決方法

facetscalesパッケージのfacet_grid_sc関数を用いれば,facet_gridの枠ごとにスケールを設定することができます。上記のgeom_blankの方法では,facet_wrapの枠ごとに異なる軸の最小値・最大値(limits)は設定できるものの,枠ごとに異なる表示スケール(breaks)は設定できません。facetscalesを用いれば,この表示スケール(breaks)までも操作可能です。stack overflowでも使い方が紹介されています。

 

グラフの清書

最後にgeom_blankを使い,かつ体裁を最大限整えたグラフを描いてみます。論文にもそのまま使えるほど綺麗なので,参考にしてください。

library(dplyr)
library(tidyr)
library(ggplot2)
iris %>%
  gather(feature, value, -Species) %>%
  group_by(Species, feature) %>%
  summarise(mean_value = mean(value))  %>%
  group_by(feature) %>%
  mutate(y_max = max(mean_value)*1.15) %>%
  ggplot(aes(x = Species, y = mean_value, label = mean_value, fill = Species)) +
  geom_bar(stat = "identity", width = 0.5, size = 0.3, color = "grey80")  +
  geom_text(size = 2.4, vjust = -0.5) +
  geom_blank(aes(y = y_max)) +
  facet_wrap(~feature, ncol = 4, scales = "free") +
  scale_y_continuous(expand = c(0, 0)) +
  scale_fill_brewer(palette = "Set1") +
  theme_bw() +
  theme(panel.border = element_blank(),
        axis.line = element_line(color = "grey50", size = 0.2),
        axis.text.x = element_text(size = 8, angle = 45, hjust = 1, vjust = 1),
        axis.text.y = element_text(size = 8),
        axis.title = element_text(size = 8),
        strip.background = element_blank(),
        strip.text = element_text(size = 8, face = "bold"),
        axis.ticks = element_blank(),
        panel.grid.major = element_blank(), 
        panel.grid.minor = element_blank(), 
        legend.position = "none") +
  labs(x = "Species",
      y = "Mean") -> p

終わり。