Raincloud plotの描き方:ggplot2

久々に描こうと思ったら方法を思い出せなかったので、ついでにまとめておきます

Raincloud plotとは

「数量的なデータの分布を表しつつ、1つ1つのデータポイントも同時に描いてしまおう」という欲張りなプロットです。たとえば、有名なirisデータを使うと、下のようになります。

密度プロット(「雲」)の下にデータポイント(雨粒)がプロットされているので、raincloud plotと呼ばれています。




ちなみに、類似のプロットとして箱ひげ図やヴァイオリンプロットがあります。ggplot2だと、それぞれgeom_boxplot()geom_violin()という関数に対応しており、すぐにプロットできます。

ただし箱ひげ図は、ヴァイオリンプロットよりも情報量が少ないです。見た目も少しもっさりしています。

ヴァイオリンプロットは、分布の形状をより良く表してくれますが、知名度が低いため理解されづらいという欠点がありますWikipedia「バイオリン図」)。実際、「ヴァイオリンプロットってどうやって読み取ればいいの?」と言う人を、自分も見かけたことがあります。たしかに、初見だと「なぜ分布が線対称に広がっているのだろう」とか色々考えてしまって混乱しそうです。

その点raincloud plotなら、情報量も多く、しかも直観的に解釈しやすいです。そんなわけで、自分はraincloud plotをよく使います。


3通りの描き方

結論から言うと、ggdistを使う3つ目の方法が個人的なおすすめです。その結論に至るまでのプロセスに関心のある方だけ、このパートをお読みください。




1. raincloudplotsというRのパッケージを使う

まさに文字通りのパッケージです。ただ、raincloudplotsの公式ページを少し読んだ限りだと、raincloud plot流のコードの書き方を新たに覚えないといけないように見えました。

Raincloud plotのためだけに関数を学ぶほどのモチベーションは自分にはありません。なので、この方法は見送りました。


2. ggridgesを使う

ggridgesというパッケージ、これは便利です。どんなパッケージかについては、公式ページのチュートリアルを読んでみてください。

Raincloud plotを描くには、geom_density_ridges()という関数内でposition = "raincloud"と指定すればOKです。

「だったらこの書き方で解決じゃん!(ー完ー)」

……といきたいところですが、ggridgesには、本当にちょっとだけ微妙なところがあります。それは「密度プロットの“尾根”が左右の端までずっと伸びてしまう」という点です。たとえば、irisデータを使うとこんな感じになります。

library(tidyverse)
library(ggridges)

ggplot(iris, aes(x = Sepal.Length, y = Species)) +
  geom_density_ridges(position = "raincloud", scale = 0.9)

左右の両端まで、ずずっと線が伸びているのがわかります。もちろん、見栄えという点ではそんなに致命的ではないです。

しかし、下のケースではどうでしょうか?

set.seed(1)
1:3 %>%
  imap_dfr(~ list(
    name = .y,
    value = runif(n = 100, min = 0.1, max = 2)),
  ) %>%
  ggplot(aes(x = value, y = factor(name))) +
  geom_density_ridges(position = "raincloud", scale = 0.8)
## Picking joint bandwidth of 0.186

ここでは、下限0.1〜上限2.0の一様分布から100個×3系列の乱数を発生させ、各系列の分布を描いています。分布の下限が0.1なので、乱数の値が0以下になることはありえません。

しかし、上のプロットでは「尾根」が0以下まで伸びてしまっています。正の値しか取りえないデータ(例:身長や体重)でこのようなことが起こるのは避けたいです。

なお、色々工夫してやればこの問題も解決できるようです。ただ、raincloud plotにそこまでの労力をかけたくありません。なので、自分はこの方法も見送りました。


3. ggdistを使う

これらの問題が解消されているのが、ggdistというパッケージです。このパッケージのstat_halfeye()という関数を使えば、かなり楽にraincloud plotを描けるようになります。


ggdistを使って実際にraincloud plotを描いてみる

実際に、ggdistのstat_halfeye()を使ってraincloud plotを描いてみましょう。

library(ggdist)


1. 分布

まず、stat_halfeye()でいきなり分布を描いてしまいます。

ggplot(iris, aes(x = Sepal.Length, y = Species)) +
  stat_halfeye()

これだけでも十分通用するような気もしますが、もうちょっとカスタマイズしてみましょう。とりあえず、

  • 信頼区間っぽい部分を消す
  • 分布同士の余白が狭いので、分布の高さを少し縮める

ことにします。

ggplot(iris, aes(x = Sepal.Length, y = Species)) +
  stat_halfeye(point_color = NA, .width = 0, height = 0.6)

stat_halfeye()に3つの引数を追加しました。具体的には、

  • point_color:信頼区間の点。NAにすれば消える
  • .width:信頼区間の幅。0にすれば消える
  • height:デフォルトの高さ(1)に対してどれくらいの比率にするか

に対応しています。


2. データポイント

つぎにデータポイントを付け足します。素朴にgeom_point()を使い、位置をジッターさせてやればOKです。

ggplot(iris, aes(x = Sepal.Length, y = Species)) +
  stat_halfeye(point_color = NA, .width = 0, height = 0.6) +
  geom_point(position = position_jitter(width = 0, height = 0.1, seed = 1))

ただし、分布とデータポイントが重なってしまっています。分布の位置をy軸方向にずらして(nudgeして)みましょう。

ggplot(iris, aes(x = Sepal.Length, y = Species)) +
  stat_halfeye(
    point_color = NA, .width = 0, height = 0.6,
    position = position_nudge(y = 0.3)
  ) +
  geom_point(position = position_jitter(width = 0, height = 0.1, seed = 1))


3. 箱ひげ図(オプション)

ついでですが、箱ひげ図も付け足します。分布とデータポイントの間に滑り込ませてやりましょう。なお、outlier.shape = NAにしないと、外れ値がプロットされてしまい、データポイントと混ざってしまいます。

あと、さすがに色がないと寂しいので、irisの種ごとに色を付けてあげます。

ggplot(iris, aes(x = Sepal.Length, y = Species, color = Species)) +
  stat_halfeye(
    point_color = NA, .width = 0, height = 0.6,
    position = position_nudge(y = 0.3)
  ) +
  geom_point(position = position_jitter(width = 0, height = 0.1, seed = 1)) +
  geom_boxplot(
    position = position_nudge(y = 0.2),
    width = 0.1, outlier.shape = NA
  )

これで完成です。

箱ひげ図とデータポイントを重ね合わせて描くバージョンもあるみたいですが、その辺はお好みの範疇だと思います。


まとめ

ggdistを使うと、かなり楽にraincloud plotを描けることがわかりました。現時点では、これがかなりおすすめの方法です。

関連項目