8 ggplot2によるデータの可視化

8.1 はじめに

ggplot2の初歩のページでは、ggplot2 が「グラフの文法」に基づいてグラフを構築する仕組みを解説しました。

具体的には、グラフの3つの必須要素である data(データ)、aes(マッピング)、geom(ジオメトリ)の役割を学び、最も基本的なグラフとして散布と棒グラフの作成方法を扱いました。また、labs() によるラベル設定、theme_bw() によるデザインテーマの適用、ggsave() によるグラフ画像の保存という、データ可視化の基本的な流れを説明しました。

ggplot2 パッケージを知らない場合はまず上記の記事を読んでください。

このページの目標

このページでは、ggplot2 が持つより応用的・発展的な機能について解説します。

まず、下記のようなさまざまなタイプのグラフの作成方法を紹介します。

次に、scale_*() ファミリーの関数を使った色・形・軸の目盛りの詳細なカスタマイズ方法や、theme() 関数を使った凡例の位置やグリッド線の表示といった、グラフの詳細なデザインの方法を示します。

最後に、facet_wrap() を用いたグラフの分割や、geom_smooth() による統計情報の追加など、より高度なデータ表現テクニックを紹介します。

8.2 散布図 (geom_point)

散布図は2つの量的変数(数値)の関係性を視覚化するのに最も適したグラフです。
geom_point() を使って作成できます。

penguins データの bill_length_mm(クチバシの長さ)と body_mass_g(体重)の関係を示す散布図を作成します。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g)) +
  geom_point()
plot(fig)

このグラフから、クチバシが長いペンギンほど、体重も重い傾向があることが読み取れます。

色を使って図に情報を追加しましょう。ここでは island(生息する島) ごとに color(色)をマッピングしてみます。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island)) +
  geom_point()
plot(fig)

色分けしたことで、生息する島によってペンギンの特徴の分布が異なることがわかります。例えば、Biscoe島(赤色の点)ではクチバシの長さと体重の相関ははっきりしていますが、Dream島では相関があまり強くなさそうだとわかります。

色だけではなく、点の形もマッピングすることができます。ここでは island(島)で色分けし、さらに sex(性別)で形を分けてみましょう。

また、大量の点があるグラフでは点同士が重なってしまい複数の点がひとつに見えてしまうことがあります。このような場合はgeom_point()の中で alpha(透明度)を設定します。alpha は 0(透明)から 1(不透明)までの値を指定します。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island, shape = sex)) +
  geom_point(alpha = 0.6)
plot(fig)

透明度を0.6 (60%) に指定したので、全体的に図が薄くなりました。複数の点が重なっている箇所は色が濃くなっています。ただ、今回のデータでは点の重なりはあまり無く、濃い箇所は多くありません。このようなデータの場合は透明度を変える意味はあまりないかもしれません。

点の形状で性別が表現されています。ただ、それぞれの点が小さいせいで形の違いがよくわかりません。そこで、geom_point()の中で size(大きさ)を設定してみます。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island, shape = sex)) +
  geom_point(size = 3)
plot(fig)

点が大きくなったことで形の区別はつきやすくなりました。しかし、点同士の重なりも強くなっています。そこで、再び透明度を指定することにします。今回は透明度を 75% に指定しました。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island, shape = sex)) +
  geom_point(size = 3, alpha = 0.75)
plot(fig)

これで、複数の色や形が重なっている箇所が見えやすくなり、それ以外の点も色が薄すぎて見えにくいということはなくなりました。

このように、図を作る際はデータが見やすくなるように色や形などの要素を試行錯誤で調整することがよくあります。R と ggplot を使えば、引数の値を変えるだけで図の見た目の変更が簡単に行えます。プログラミングによって図を作ることのメリットがここにあると言えます。

なお、上記の例では色や形のマッピングは自動で行いましたが、これを自分で指定することもできます。つまり、どの島に何色を割り当てるかや、どの性別にどの形を割り当てるかを自分で詳しく指定することができます。その方法については後で説明します。

また、デフォルトでは図の背景は薄いグレーになっていますが、背景色を変えることもできます。白背景にしたい場合、theme_bw()を使うのが簡単な方法です。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island, shape = sex)) +
  geom_point(size = 3, alpha = 0.75) +
  theme_bw()
plot(fig)

8.3 棒グラフ (geom_bar, geom_col)

棒グラフは、カテゴリ(質的変数)ごとの量や値の大小を比較するのに適しています。ggplot2には、棒グラフを作成するgeomが2種類(geom_barとgeom_col)あり、目的によって使い分ける必要があります。

geom_bar(): データの件数を自動で集計する

geom_bar() は、指定されたカテゴリに属するデータの行数(件数)を自動的にカウントし、その結果を棒の高さにします。aes() では通常、X軸(またはY軸)のみを指定します。

例えば、species(種)ごとのペンギンの数を可視化する場合は、X軸に species を指定するだけです。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = species)) +
  geom_bar(fill = "#88b5d3") +
  theme_bw()
plot(fig)

棒の色を指定する際はfillを使います(colorではないので注意)。また、theme_bw()を使って背景色を白にしています。

棒の横幅を変えるには、geom_bar() の中でwidthを指定します。デフォルト値は0.9なので、少し細めにするために0.5を指定してみます。

library(ggplot2)
library(palmerpenguins)
dat = penguins

fig = ggplot(dat, aes(x = species)) +
  geom_bar(fill = "#88b5d3", width = 0.5) +
  theme_bw()
plot(fig)

fill による積み上げと横並び

aes() で fill(塗りつぶし)を指定すると、カテゴリ内でさらに色分けすることができます。デフォルトでは棒はposition = "stack"(積み上げ)で表示されます。

fig = ggplot(dat, aes(x = species, fill = sex)) +
  geom_bar() +
  theme_bw()
plot(fig)

積み上げではなく、横に並べて比較したい場合は、geom_bar() の中でposition = "dodge"を設定します。

fig = ggplot(dat, aes(x = species, fill = sex)) +
  geom_bar(position = "dodge") +
  theme_bw()
plot(fig)

性別が欠損値であるデータが含まれているせいで、NAの棒が現れています。これが不要な場合は、データからNAの列を除外した上でグラフを作成するとよいでしょう。

dat2 = subset(dat, !is.na(sex))
fig = ggplot(dat2, aes(x = species, fill = sex)) +
  geom_bar(position = "dodge") +
  theme_bw()
plot(fig)

geom_col(): あらかじめ集計された値を使用する

上述したgeom_bar()は、指定されたカテゴリごとのデータの件数(カウント数)を表示します。一方で、カウント数以外の値を縦軸として表示したい場合もあります。たとえば、ペンギンの種ごとの平均体重を表示する、などです。

このような場合にはgeom_col()を使います。この関数を使うことで、平均値などの集計値を棒グラフにすることができます。aes() では、カテゴリを指定するX軸と、値を指定するY軸の両方が必要です。

例として、species ごとの平均体重 (body_mass_g) を可視化します。まず、aggregate() 関数(Base Rの関数)を使って集計用のデータフレームを作成します。

aggregate 関数の第一引数は集計対象の列名 ~ 集計したいカテゴリの列名という形式で指定します。dataには元となるデータを、FUNには集計値を計算するための関数(平均値であれば mean 関数)を指定します。na.rm = TRUEは欠損値(NA)を除外して計算するオプションです。

# 種類(species)ごとの平均体重を計算
df = aggregate(body_mass_g ~ species, data = dat, FUN = mean, na.rm = TRUE)
print(df)
##     species body_mass_g
## 1    Adelie    3700.662
## 2 Chinstrap    3733.088
## 3    Gentoo    5076.016

# 作成した df を使ってプロット
fig = ggplot(df, aes(x = species, y = body_mass_g)) +
  geom_col(fill = "#88b5d3") +
  theme_bw()
plot(fig)

上記の df は体重の平均値が集計された2列3行だけのデータフレームです。これを ggplot 関数のデータとして使っています。aes() 内では x 軸と y 軸に df のそれぞれの列名を指定しています。

カウント数の表示に使ったgeom_bar()では元のデータである dat(344行)をそのまま使っていたのに対し、geom_col()では集計済みのデータである df(3行だけ)を使用している点が大きな違いです。

なお、geom_col()でもfillposition = "dodge"は利用可能です。species と island ごとの平均体重を比較してみましょう。今回は「種ごと」かつ「島ごと」での平均体重を集計したいので、aggregate 関数においてbody_mass_g ~ species + islandと書きます。

# 種類(species)と島(island)ごとの平均体重を計算
df = aggregate(body_mass_g ~ species + island, data = dat, FUN = mean, na.rm = TRUE)
print(df)
##     species    island body_mass_g
## 1    Adelie    Biscoe    3709.659
## 2    Gentoo    Biscoe    5076.016
## 3    Adelie     Dream    3688.393
## 4 Chinstrap     Dream    3733.088
## 5    Adelie Torgersen    3706.373

# df を使い、island で色分けして横並びに
fig = ggplot(df, aes(x = species, y = body_mass_g, fill = island)) +
  geom_col(position = "dodge") +
  theme_bw()
plot(fig)

エラーバーの追加

エラーバーを追加するにはgeom_errorbar()を使います。この関数ではエラーバーの上端 (ymax) と下端 (ymin) を aes() で指定する必要があります。

ペンギンの種ごとの平均体重を可視化する際に、エラーバーとしてSD(標準偏差)をつけてみましょう。

そのためには、平均体重とSDの値が種ごとに集計されたデータフレームをまず準備する必要があります。

# species ごとの平均値(mean)を計算
d_mean = aggregate(body_mass_g ~ species, data = dat, FUN = mean, na.rm = TRUE)
# species ごとの標準偏差(sd)を計算
d_sd = aggregate(body_mass_g ~ species, data = dat, FUN = sd, na.rm = TRUE)
# 平均値と標準偏差を一つデータフレームにまとめる
df = data.frame(
  species = d_mean$species,
  avg = d_mean$body_mass_g,
  sd = d_sd$body_mass_g
)
print(df)
##     species      avg       sd
## 1    Adelie 3700.662 458.5661
## 2 Chinstrap 3733.088 384.3351
## 3    Gentoo 5076.016 504.1162

fig = ggplot(df, aes(x = species, y = avg)) +
  geom_col(fill = "#88b5d3", width = 0.5) +
  geom_errorbar(aes(ymin = avg - sd, ymax = avg + sd), width = 0.1) +
  theme_bw()
plot(fig)

geom_errorbar() 関数において、ymin (下端)には「平均 - SD」、ymax (上端)には「平均 + SD」を指定しています。ここで使われている avg や sd という名前は df 変数にある avg 列や sd 列のことです。

なお、geom_errorbar() の中でwidth = 0.1のように設定すると、エラーバーの横幅が棒グラフの幅(ここでは0.5)より狭くなり、見やすくなります。

8.4 折れ線グラフ (geom_line)

折れ線グラフは、X軸に沿ったデータの連続的な変化や傾向を示すのに適しています。geom_line()を使用します。X軸は、year(年)のような数値型や、日付/時刻型の変数である必要があります。

penguins データセットの year 変数を使って、年ごとの変化を見てみましょう。

geom_point()(散布図)で生のデータをプロットした場合、344個の点が描画されますが、geom_line() で生のデータをプロットすると、Rは year 順に全ての点を結ぼうとするため、意味のないグラフになります。

折れ線グラフで傾向を見る場合、棒グラフ(geom_col)の例と同様に、あらかじめデータを集計するのが一般的です。

library(ggplot2)
library(palmerpenguins)

dat = palmerpenguins::penguins
df = aggregate(body_mass_g ~ year, data = dat, FUN = mean, na.rm = TRUE)
print(df)
##   year body_mass_g
## 1 2007    4124.541
## 2 2008    4266.667
## 3 2009    4210.294

fig = ggplot(df, aes(x = year, y = body_mass_g)) +
  geom_line() +
  theme_bw()
plot(fig)

各データポイントに点を打ちたい場合はgeom_point()を追加します。size を使って線や点の太さを指定できます。色は color で指定します。

ついでに、X軸の値に2007.5など不必要な値が表示されているのを修正します。scale_x_continuous()という関数を使います(この関数については後で説明します)。

fig = ggplot(df, aes(x = year, y = body_mass_g)) +
  geom_line(size = 1.5, color = "#88b5d3") +
  geom_point(size = 4, color = "#88b5d3") +
  scale_x_continuous(breaks = c(2007, 2008, 2009)) +
  theme_bw()
plot(fig)

次に、ペンギンの種ごとに平均体重を表示してみます。aes() の中で color をマッピングすることで、グループ(カテゴリ)別に複数の折れ線グラフを描画できます。

df = aggregate(body_mass_g ~ species + year, data = dat, FUN = mean, na.rm = TRUE)
fig = ggplot(df, aes(x = year, y = body_mass_g, color = species)) +
  geom_line(size = 1.5) +
  geom_point(size = 4) +
  scale_x_continuous(breaks = c(2007, 2008, 2009)) +
  theme_bw()
plot(fig)

エラーバーの追加

ペンギンの種を区別せずに平均値とSDを計算し、折れ線グラフにエラーバーを追加する例です。

# 年(year)ごとの平均体重
d_mean = aggregate(body_mass_g ~ year, data = dat, FUN = mean, na.rm = TRUE)
# 年(year)ごとの標準偏差(sd)
d_sd = aggregate(body_mass_g ~ year, data = dat, FUN = sd, na.rm = TRUE)
# 平均値と標準偏差を一つのデータフレームにまとめる
df = data.frame(
  year = d_mean$year,
  avg = d_mean$body_mass_g,
  sd = d_sd$body_mass_g
)
print(df)
##   year      avg       sd
## 1 2007 4124.541 792.5213
## 2 2008 4266.667 789.4679
## 3 2009 4210.294 822.9070

fig = ggplot(df, aes(x = year, y = avg)) +
  geom_errorbar(aes(ymin = avg - sd, ymax = avg + sd), width = 0.1, size = 0.8, color = "#88b5d3") +
  geom_line(size = 1.5, color = "#88b5d3") +
  geom_point(size = 4, color = "#88b5d3") +
  scale_x_continuous(breaks = c(2007, 2008, 2009)) +
  theme_bw()
plot(fig)

次の例では、ペンギンの種でマッピングする例です。種ごとに平均値とSDを計算したデータフレームを用意しています。

# 種類(species)と年(year)ごとの平均体重
d_mean = aggregate(body_mass_g ~ species + year, data = dat, FUN = mean, na.rm = TRUE)
# 種類(species)と年(year)ごとの標準偏差
d_sd = aggregate(body_mass_g ~ species + year, data = dat, FUN = sd, na.rm = TRUE)
# データを一つにまとめる
df = data.frame(
  species = d_mean$species,
  year = d_mean$year,
  avg = d_mean$body_mass_g,
  sd = d_sd$body_mass_g
)
print(df)
##     species year      avg       sd
## 1    Adelie 2007 3696.429 449.8553
## 2 Chinstrap 2007 3694.231 327.6666
## 3    Gentoo 2007 5070.588 582.8506
## 4    Adelie 2008 3742.000 455.2819
## 5 Chinstrap 2008 3800.000 519.3322
## 6    Gentoo 2008 5019.565 514.8331
## 7    Adelie 2009 3664.904 475.2517
## 8 Chinstrap 2009 3725.000 330.1021
## 9    Gentoo 2009 5140.698 423.6682

fig = ggplot(df, aes(x = year, y = avg, color = species)) +
  geom_errorbar(
    aes(ymin = avg - sd, ymax = avg + sd),
    width = 0.1
  ) +
  geom_line() +
  geom_point() +
  scale_x_continuous(breaks = c(2007, 2008, 2009)) +
  theme_bw()
plot(fig)

上の図では、Chinstrap と Adelie のエラーバー同士が重なってしまっています。

このような場合、エラーバーの位置を種ごとに少し横にずらすという対処をします。geom_errorbar, geom_line, geom_point のすべてにposition = position_dodge(0.1)を指定します。0.1という部分は横ずれの量を指定しています。

fig = ggplot(df, aes(x = year, y = avg, color = species)) +
  geom_errorbar(
    aes(ymin = avg - sd, ymax = avg + sd),
    width = 0.1,
    position = position_dodge(0.1)
  ) +
  geom_line(position = position_dodge(0.1)) +
  geom_point(position = position_dodge(0.1)) +
  scale_x_continuous(breaks = c(2007, 2008, 2009)) +
  theme_bw()
plot(fig)

エラーバーの重なりが解消され、グラフが見やすくなりました。

8.5 ヒストグラム & 密度プロット (geom_histogram, geom_density)

ヒストグラムと密度プロットは、どちらも単一の量的変数(数値)が、どの範囲にどれだけ分布しているか(ばらつき)を可視化するために使用します。これらは集計済みのデータ(df)を必要とせず、元のデータ(dat)を直接使います。

geom_histogram(): データを区切り、件数を棒で示す

geom_histogram() は、数値データを一定の区間(bin: 階級)に区切り、各区間に含まれるデータの件数を棒グラフで表示します。aes() ではX軸のみを指定します。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = body_mass_g)) +
  geom_histogram(fill = "#88b5d3") +
  theme_bw()
plot(fig)

グラフを作成すると、stat_bin() が自動で階級の幅(binwidth)を決定した旨のメッセージ(例: bins = 30)が表示されることがあります。この階級の幅は binwidth 引数で明示的に設定でき、グラフの見た目が大きく変わります。

fig = ggplot(dat, aes(x = body_mass_g)) +
  geom_histogram(fill = "#88b5d3", binwidth = 250) +
  theme_bw()
plot(fig)

binwidth を調整することで、分布の形状をより詳細に、あるいはより大まかに捉えることができます。

aes() で fill(塗りつぶし)を指定すると、積み上げヒストグラムを作成できます。

fig = ggplot(dat, aes(x = body_mass_g, fill = species)) +
  geom_histogram(binwidth = 250) +
  theme_bw()
plot(fig)

積み上げではなく、グループごとのヒストグラムを重ねて表示することもできます。position = "identity"を指定します。重なったグラフの場合は透明度(alpha)を指定して重なり部分がわかるようにする必要があります。

fig = ggplot(dat, aes(x = body_mass_g, fill = species)) +
  geom_histogram(position = "identity", alpha = 0.8, binwidth = 250) +
  theme_bw()
plot(fig)

geom_density(): 分布の形状を滑らかな線で示す

geom_density() は、ヒストグラムを滑らかな曲線で表現したものです。データの密度(その値の周辺にデータがどれだけ集中しているか)を推定して描画します。

fig = ggplot(dat, aes(x = body_mass_g)) +
  geom_density(size = 2, color = "#88b5d3") +
  theme_bw()
plot(fig)

ヒストグラムと比較すると、体重の分布には左側の大きな山に加えて右側にも2箇所の盛り上がりがあることがわかりやすいです。

geom_density() は、fill を指定してカテゴリ別に色分けすると非常に強力です。alpha(透明度)を設定することで、線が重なっても見やすくなります。

fig = ggplot(dat, aes(x = body_mass_g, fill = species)) +
  geom_density(alpha = 0.6) +
  theme_bw()
plot(fig)

このグラフから、元のヒストグラムで見えていた3つの山が、実際には Adelie と Chinstrap のグループによる左側の山と、Gentoo グループによる右側の山から構成されていたことがわかります。

8.6 箱ひげ図 & バイオリンプロット (geom_boxplot, geom_violin)

箱ひげ図とバイオリンプロットは、どちらもカテゴリ(質的変数)ごとに、量的変数(数値)の分布を要約して比較するのに適しています。

geom_boxplot(): データの要約統計量を視覚化する

箱ひげ図は、データの第1四分位数(25%点)、中央値(50%点)、第3四分位数(75%点)を箱で示し、外れ値ではないとみなされる範囲の中の最大値と最小値を箱から上下に伸びる線(ひげと呼ばれえる)で表現します。ひげの外側の値は外れ値であり、それらは個別に点としてプロットされます。

箱ひげ図を見ることで、データの分布の概要を知ることができます。

geom_boxplot() 関数で箱ひげ図を作成できます。
X軸にカテゴリ(例: species)、Y軸に数値(例: body_mass_g)を指定します。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = species, y = body_mass_g)) +
  geom_boxplot() +
  theme_bw()
plot(fig)

このグラフから、Gentoo 種は他の種に比べて体重の中央値が明らかに重く、分布の範囲(箱の長さ)も広いことがわかります。

aes() で fill(塗りつぶし)を指定すると、箱の色をカテゴリ別に変更できます。

fig = ggplot(dat, aes(x = species, y = body_mass_g, fill = species)) +
  geom_boxplot() +
  theme_bw()
plot(fig)

箱ひげ図の中の横棒は中央値を示します。この図に平均値も加えたい場合はstat_summary()関数を使います。この関数の引数ではfun = "mean"(計算する関数 = 平均)とgeom = "point"(描画する図形 = 点)を指定します。さらに、shape = 18によってひし形の形状を指定しています。

fig = ggplot(dat, aes(x = species, y = body_mass_g, fill = species)) +
  geom_boxplot() +
  stat_summary(fun = "mean", geom = "point", shape = 18, size = 4, color = "gray90") +
  theme_bw()
plot(fig)

geom_violin(): データの分布の形状を視覚化する

バイオリンプロットはデータの密度プロットを左右対称に表示したものであり、形状の幅が広いほどその値を持つデータが密集している(密度が高い)ことを意味します。

geom_violin() 関数を使います。

fig = ggplot(dat, aes(x = species, y = body_mass_g, fill = species)) +
  geom_violin() +
  theme_bw()
plot(fig)

バイオリンプロットを使うことで、Chinstrap は3600付近に明確なピークがありますが、Adelie や Gentoo にはそれほどはっきりしたピークがないことがわかります。

バイオリンプロットに geom_point() を重ねることで、実際のデータがどのように分布しているかを同時に示すこともできます。

fig = ggplot(dat, aes(x = species, y = body_mass_g, fill = species)) +
  geom_violin(alpha = 0.5, width = 0.6, color = NA) +
  geom_jitter(alpha = 0.6, width = 0.05) +
  theme_bw()
plot(fig)

ここでは、バイオリンプロットから輪郭線を消し(color = NA)、色を薄め(alpha = 0.5)、さらに幅をスリムにする(width = 0.6)ことで、図を見やすくしています。

次に、この図に平均値の情報も追加してみましょう。stat_summary() 関数を使い、geom = "crossbar"を使うと横棒を描写することができます。

fig = ggplot(dat, aes(x = species, y = body_mass_g, fill = species)) +
  geom_violin(alpha = 0.5, width = 0.6, color = NA) +
  stat_summary(fun = "mean", geom = "crossbar", width = 0.3, color = "gray100") +
  geom_jitter(alpha = 0.6, width = 0.05) +
  theme_bw()
plot(fig)

最後に、バイオリンプロットに箱ひげ図を重ねて描画する例です。

fig = ggplot(dat, aes(x = species, y = body_mass_g)) +
  geom_violin(aes(fill = species), alpha = 0.5, width = 0.6, color = NA) +
  geom_boxplot(width = 0.05, fill = "white", size = 0.8) +
  theme_bw()
plot(fig)

8.7 ヒートマップ (geom_tile)

ヒートマップは、geom_tile() を使い、2つのカテゴリ変数(X軸とY軸)の組み合わせに対して、3つ目の量的変数(数値)の大小を fill(塗りつぶし)の色で表現するグラフです。

ここでは例として、year(年)と island(島)の組み合わせごとに、ペンギンの件数をヒートマップで可視化します。

geom_tile() を使う際は、あらかじめ集計されたデータフレームを用意します。下記の例では、dat をコピーした tmp 変数を用意し、count という名前の列(値はすべて1)を追加しています。このデータに対して aggregate 関数を使って count の合計値(sum)を計算することでデータの件数を集計しています。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

tmp = dat
tmp$count = 1
df = aggregate(count ~ island + year, data = tmp, FUN = sum)
print(df)
##      island year count
## 1    Biscoe 2007    44
## 2     Dream 2007    46
## 3 Torgersen 2007    20
## 4    Biscoe 2008    64
## 5     Dream 2008    34
## 6 Torgersen 2008    16
## 7    Biscoe 2009    60
## 8     Dream 2009    44
## 9 Torgersen 2009    16

fig = ggplot(df, aes(x = year, y = island, fill = count)) +
  geom_tile() +
  theme_bw()
plot(fig)

geom_text() を追加することで、count の値をタイル上にテキストとして表示できます。aes() で label = count を指定します。

fig = ggplot(df, aes(x = year, y = island, fill = count)) +
  geom_tile() +
  geom_text(aes(label = count), color = "gray90", size = 5) +
  theme_bw()
plot(fig)

タイルの色を変更するには scale_fill_…() 系の関数を追加します。たとえば、scale_fill_gradient() を使う場合、low(最小値の色)と high(最大値の色)を指定することでその2色のグラデーションを指定できます。

fig = ggplot(df, aes(x = year, y = island, fill = count)) +
  geom_tile() +
  geom_text(aes(label = count), color = "gray90", size = 5) +
  scale_fill_gradient(low = "#cccccc", high = "#dd1177") +
  theme_bw()
plot(fig)

8.8 グラフの見た目の調整

8.8.1 ラベルとタイトル (labs)

ggplot2 のグラフにタイトルや分かりやすい軸ラベルを追加するには、labs() レイヤーを + で追加します。

labs() 関数を使うことで、title(タイトル)、x(X軸ラベル)、y(Y軸ラベル)などを指定できます。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island)) +
  geom_point() +
  labs(
    title = "ペンギンの体重とクチバシの長さ",
    x = "クチバシの長さ (mm)",
    y = "体重 (g)",
    color = "観測された島"
  )
plot(fig)

凡例タイトルはcolor = "観測された島"の部分で指定しています。 aes() の中でcolor = islandとマッピングを指定したため、labs() の中で color 引数に文字列を指定すると、凡例のタイトルが変更されます。aes() で shape = sex を指定した場合は、labs(shape = “性別”) のように指定します。fill の場合も同様です。

ラベルとして日本語を使った場合に文字化けすることがあります。その場合は以下のように対処してください。

  1. RStudioのメニューバーから Tools を選択します。
  2. Global Options… を選択します。
  3. 開いたウィンドウで、左側の General を選択し、右側の Graphics タブをクリックします。
  4. Backend が Default になっている設定を AGG に変更します。
  5. OK または Apply を押して設定を保存します。

設定を変更したら、RStudioを一度再起動してください。

8.8.2 色と形のカスタマイズ (scale_*)

aes() を使って color(色)、fill(塗りつぶし)、shape(形)をデータにマッピングすると、ggplot2 は自動的にデフォルトの色や形を割り当てます。

これらの割り当てを明示的に制御・変更するのが scale_*() ファミリーの関数です。

  • aes(color = ...)を変更:scale_color_...()
  • aes(fill = ...)を変更:scale_fill_...()
  • aes(shape = ...)を変更:scale_shape_...()

カテゴリ変数のカスタマイズ

scale_color_manual() で色を手動で指定します。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island)) +
  geom_point() +
  scale_color_manual(values = c("darkorange", "purple", "cyan4"))
plot(fig)

scale_color_brewer() を使うと、RColorBrewer パッケージに基づいたデザイン性の高い配色パレットを適用できます。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island)) +
  geom_point() +
  scale_color_brewer(palette = "Set2")
plot(fig)

次に、棒グラフ (geom_bar) でfill = sexをマッピングしたグラフを例にします。

fig = ggplot(dat, aes(x = species, fill = sex)) +
  geom_bar(position = "dodge") +
  scale_fill_manual(values = c("#FFC107", "#03A9F4"))
plot(fig)

fig = ggplot(dat, aes(x = species, fill = sex)) +
  geom_bar(position = "dodge") +
  scale_fill_brewer(palette = "Pastel1")
plot(fig)

aes(shape = …) をマッピングした場合、scale_shape_manual() を使って形を手動で指定できます。

形は番号で指定します(例: 0=四角, 1=丸, 2=三角)。よく使われるのは 15〜18 の塗りつぶされた形です。 (15=■, 16=●, 17=▲, 18=◆)

以下の例では色と形を手動で指定しています。形は、female に 16 (●)、male に 17(▲) を割り当てています。

dat2 = subset(dat, !is.na(sex)) # NAを除外
fig_shape = ggplot(dat2, aes(
  x = bill_length_mm, y = body_mass_g, shape = sex, color = sex)) +
  geom_point(alpha = 0.7, size = 3) +
  scale_color_manual(values = c("#ff4455", "#4488ff")) +
  scale_shape_manual(values = c(16, 17))
plot(fig_shape)

連続変数(数値)のカスタマイズ

ヒートマップでの場合のように、aes(fill = count) で数値(連続値)を色にマッピングした場合、scale_fill_gradient() で色のグラデーションを変更できます。

# ヒートマップ用の集計データを作成
tmp = dat
tmp$count = 1
df = aggregate(count ~ island + year, data = tmp, FUN = sum)

fig = ggplot(df, aes(x = year, y = island, fill = count)) +
  geom_tile() +
  scale_fill_gradient(low = "lightblue", high = "darkblue")
plot(fig)

以下は散布図の点の color に、体重 body_mass_g(連続値)をマッピングした例です。

fig = ggplot(dat, aes(x = bill_length_mm, y = flipper_length_mm, color = body_mass_g)) +
  geom_point(alpha = 0.7) +
  scale_color_gradient(low = "green", high = "purple")
plot(fig)

色と形のカスタマイズのまとめ

scale_*() 関数は、aes() で何をマッピングしたか(color, fill, shape)と、マッピングした変数の型(カテゴリカル or 連続)によって、使用する関数が決まります。

1. aes(color = …) と aes(fill = …) (色と塗りつぶし)

カテゴリ変数(species, island など)の場合:

  • scale_color_manual(values = c(...)): 色を手動で指定します。
  • scale_color_brewer(palette = "..."): RColorBrewer のパレットを適用します。
  • fill の場合はscale_fill_manual()scale_fill_brewer()

連続変数(count, body_mass_g など)の場合:

  • scale_color_gradient(low = "...", high = "..."): 2色のグラデーションを指定します。
  • fill の場合はscale_fill_gradient()

2. aes(shape = …) (形)

カテゴリ変数(sex など)の場合:

  • scale_shape_manual(values = c(...)): 形(数値)を手動で指定します。

連続変数 の場合: - 数値のような連続変数を「形」にマッピングすることは一般的ではありません。そのため、scale_shape_gradient() に相当する関数は ggplot2 には用意されていません。

8.8.3 軸の調整 (xlim, ylim)

ggplot2 は自動で適切な軸の範囲や目盛りを設定しますが、グラフの特定の部分を拡大(ズーム)したい場合や、目盛りの間隔を自分で指定したい場合があります。

xlim() と ylim(): 軸の表示範囲を指定する

xlim() と ylim() を追加すると、X軸とY軸の表示範囲(最小値, 最大値)を指定できます。

散布図のグラフを例にこの問題を考えます。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g)) +
  geom_point(color = "#00afcc") +
  theme_bw()
plot(fig)

上記のデフォルトの状態では、横軸も縦軸も中途半端な値の範囲になっています。そこで、キリの良い値でグラフの範囲を指定してみます。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g)) +
  geom_point(color = "#00afcc") +
  xlim(30, 60) +
  ylim(2000, 7000) +
  theme_bw()
plot(fig)

これで横軸は30から60、縦軸は2000から7000の範囲を取るようになりました。

軸の表示範囲と目盛りを調整する

軸の「範囲」ではなく「目盛り」を調整したい場合は、scale_x_continuous() や scale_y_continuous() を使います。

それぞれの関数において、limits 引数で軸の範囲を指定し、breaks 引数で目盛りの数値を指定します。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g)) +
  geom_point(color = "#00afcc") +
  scale_x_continuous(
    limits = c(30, 60),
    breaks = c(30, 35, 40, 45, 50, 55, 60)
  ) +
  scale_y_continuous(
    limits = c(2000, 7000),
    breaks = c(2000, 3500, 5000, 6500)
  ) +
  theme_bw()
plot(fig)

注意点として、scale_x_continuous() や scale_y_continuous() を使う際は、xlim() や ylim() は使わないようにするということです。

なぜかと言えば、xlim(30, 60)といった命令は、実際には scale_x_continuous(limits = c(30, 60)) として ggplot の中では扱われるからです。xlim と scale_x_continuous を同時に使ってしまうと、後から記述した方の関数が先に書いた方の関数を上書きしてしまいます。これによって意図しない設定でグラフが描画されてしまいます。

実用上、グラフの見た目を調整する際は軸の範囲だけではなく目盛りも同時に指定するべき場面が多いです。したがって、xlim() や ylim() は使わずに常に scale_x_continuous() や scale_y_continuous() を使うようにすると良いと思われます。

8.8.4 テーマの適用 (theme_* / theme)

ggplot2 の「テーマ (Theme)」は、グラフのデータと無関係なすべての視覚的要素(背景色、グリッド線、文字フォント、凡例の位置など)を制御します。

8.8.4.1 プリセットテーマの適用 (theme_*()){-}

theme_*() 関数を使うと、グラフ全体のデザインを一括で変更できます。これまでしばしば使っていた theme_bw() もそうした関数のひとつです。

散布図にラベルを指定したグラフをベースに、いくつかのテーマを適用してみます。まずはデフォルトの灰色の背景のグラフです。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island)) +
  geom_point(alpha = 0.8) +
  labs(
    title = "Penguin Body Mass and Bill Length",
    x = "Bill Length (mm)",
    y = "Body Mass (g)",
    color = "Island"
  )
plot(fig)

theme_bw() (Black & White) は背景が白、グリッド線が灰色の、論文などでよく使われるシンプルなテーマです。

fig2 = fig + theme_bw()
plot(fig2)

theme_minimal() は、theme_bw() よりもさらにシンプルで、背景の枠やグリッド線が最小限に抑えられたテーマです。

fig2 = fig + theme_minimal()
plot(fig2)

theme_classic() は古典的な科学グラフのように、グリッド線がなくX軸とY軸の線だけを持つテーマです。

fig2 = fig + theme_classic()
plot(fig2)

個別設定の調整 (theme())

theme() 関数を使うと、グラフの要素を個別に細かく調整できます。上述した theme_*() で全体を変更した後、theme() で微調整を行うのが一般的です。

theme() 関数の中で、設定したい見た目の指示を行います。たとえば、凡例の位置を変更する legend.position 引数で凡例の位置を指定できます。

  • “top”, “bottom”, “left”, “right”: 上下左右に配置
  • “none”: 凡例を非表示にする
fig2 = fig + 
  theme_bw() +
  theme(legend.position = "top")
plot(fig2)

文字のサイズやスタイルを変更する element_text() を使って、文字のサイズ (size)、スタイル (face = “bold” など)、色 (color) を変更します。

  • plot.title: グラフ全体のタイトル
  • axis.title: 軸ラベル(XとYの両方)
  • axis.text: 軸の目盛りテキスト
  • legend.title: 凡例のタイトル
fig2 = fig + 
  theme_bw() +
  theme(
    plot.title = element_text(size = 20, face = "bold"), # タイトルを太字・20ptに
    axis.title = element_text(size = 16), # 軸ラベルを16ptに
    axis.text = element_text(size = 14, color = "darkred"), # 目盛りを赤色に
    legend.position = "none" # 凡例を非表示に
  )
plot(fig2)

ここまで、最初に作成した fig 変数にテーマを追加する方式でコードを書いていましたが、最初からすべてをまとめて書く場合は以下のようになります。

fig = ggplot(dat, aes(x = bill_length_mm, y = body_mass_g, color = island)) +
  geom_point(alpha = 0.8) +
  labs(
    title = "Penguin Body Mass and Bill Length",
    x = "Bill Length (mm)",
    y = "Body Mass (g)",
    color = "Island"
  ) + 
  theme_bw() +
  theme(
    plot.title = element_text(size = 20, face = "bold"),
    axis.title = element_text(size = 16),
    axis.text = element_text(size = 14),
    legend.position = "top",
    legend.text = element_text(size = 12), # 凡例の文字サイズ
    panel.grid.major = element_blank(), # 主要なグリッド線を消す
    panel.grid.minor = element_blank() # 補助的なグリッド線を消す
  )
plot(fig)

上記の例では凡例の文字サイズやグリッド線なども調整しています。このように、theme() 関数を使うことで図のさまざまなスタイルを制御することができます。

8.8.5 レイヤーの順序

ggplot2 では、geom 系の関数を+記号を使って追加するたびに、グラフにレイヤー(層)が追加されます。後から追加された要素ほど、グラフでは手前に表示されます。

この違いを、棒グラフにエラーバーを追加する例をもとに説明します。

library(ggplot2)
library(palmerpenguins)
dat = palmerpenguins::penguins

# 種(species)ごとの体重の平均値と標準偏差を計算
d_mean = aggregate(body_mass_g ~ species, data = dat, FUN = mean, na.rm = TRUE)
d_sd = aggregate(body_mass_g ~ species, data = dat, FUN = sd, na.rm = TRUE)
df = data.frame(
  species = d_mean$species,
  avg = d_mean$body_mass_g,
  sd = d_sd$body_mass_g
)

# ベースとなるプロットを作成
fig = ggplot(df, aes(x = species, y = avg))

例1: 棒グラフが上に来る場合(非推奨)

geom_errorbar() を先に追加し、geom_col() を後から追加します。

geom_col()(棒)が最後に描画されたため、棒がエラーバーの上に重なっています。これにより、エラーバーの下半分(棒グラフの内部にある部分)が棒によって隠されてしまい、上側のT字部分しか見えなくなっています。

fig1 = fig +
  geom_errorbar(aes(ymin = avg - sd, ymax = avg + sd), width = 0.2) +
  geom_col()

plot(fig1)

例2: エラーバーが上に来る場合(推奨)

geom_col() を先に追加し、geom_errorbar() を後から追加します。

geom_errorbar() が最後に描画されたため、エラーバー(T字の全体)が棒の上に正しく描画されます。統計的なばらつきを示すためには、このようにエラーバーが隠れずに全体が見えることが重要です。

fig2 = fig +
  geom_col() +
  geom_errorbar(aes(ymin = avg - sd, ymax = avg + sd), width = 0.2)

plot(fig2)

このように、レイヤーの順序はグラフの可読性に直接影響します。geom_col()(棒)と geom_errorbar()(エラーバー)を重ねる場合や、geom_violin()(バイオリン)と geom_boxplot()(箱ひげ図)を重ねる場合も同様です。

どちらの要素を手前に表示したいかを意識し、+ で追加する順序を決定する必要があります。