ggplotで箱ひげ図よりもシンプルに可視化する方法

# はじめに
この記事は [MicroAd Advent Calendar 2018](https://qiita.com/advent-calendar/2018/microad) の19日目の記事です。

データの分布をわかり易く可視化する方法として、箱ひげ図があります。
箱ひげ図は、何らかの水準ごとにデータの分布を確認するときに重宝しますが、1つの要因内の水準数が多くなってくると箱ひげ図の数が多すぎてグラフが見づらくなってしまいます。
そんなときに、箱ひげ図の代替になる可視化をggplot2によって実現する方法を考えてみました。

# データの準備
まず、描画に利用するダミーデータを作成しておきましょう。以下ではこのデータを利用していきます。

```r
library(dplyr)
library(ggplot2)
library(RColorBrewer)
####################################
# ダミーデータの作成
####################################

# 水準数
n_level <- 300 # 3, 50, 300 に変えてそれぞれ実行する 

# 各水準のサンプルサイズ
n_sample_level <- 100

# 全水準のサンプルサイズ
n_sample <- n_level * n_sample_level

# 水準ごとの分布の平均値
set.seed(0)
mean_values <- rnorm(n=n_level, mean=0, sd=10)

# 各サンプルが属する水準
level <- rep(seq(1, n_level), times= rep(n_sample_level, n_level)) %>% as.factor()

# 各水準のサンプルデータ
value <- c()
for (level_index in 1:n_level){
  # シードの固定
  set.seed(level_index)

  # 乱数を生成し、その水準のデータとする
  tmp <- rnorm(n=n_sample_level, mean=mean_values[level_index], sd=5)
  
  # 全水準のデータをまとめる
  value <- c(value, tmp)
}

# データフレームの生成
df <- data.frame(level = level,
                 value = value)
```

# グラフの描画
## 箱ひげ図
箱ひげ図は ```geom_boxplot()```を使って書くことができます。データの水準数によって見た目がどう変化するかみていきましょう。
### 水準数=3 の場合
3水準程度なら箱ひげ図を描いておけば何も問題はありません。

```r
####################################
# 箱ひげ図の描画
####################################
ggplot(data = df) +
  geom_boxplot(aes(x = level, y = value)) 
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153124/052ab335-94d6-bfa8-764e-5b9a0d3f9761.png)


### 水準数=50 の場合
50水準くらいになると、箱ひげ図の数が多くなって多少見づらくなってきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153124/2dba2e3b-3323-e9cb-7c78-7ca0fdc0cc9e.png)


### 水準数=300
ここまでくると、もはや箱ではなくただの線ですね。もう少しシンプルにしたいところです。
このような可視化をするときは、分布の分布の様子を確認したい場合などでしょうか。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153124/4adbefbe-6cde-1330-a817-f7d2e549a0b1.png)


##点と線だけで
水準数=300の場合をどうにかしてみましょう。
箱ひげ図で表現される統計量をもう少しシンプルに描画することで、分布の（分布の）様子を把握しやすくします。

箱ひげ図に平均値を重ねて描画するときは```stat_summary()```を使いますが、中央値などの統計量もこの関数をつかて表示してみます。さらに、```geom_segment()```を使うと、線分をグラフ上の任意の場所に描くことができます。これも使ってみましょう。

縦線を引きたいときに使う関数としては```geom_vline()```がよく知られていますが、```geom_segment()```であれば端点の位置も直感的に指定することができます。

ここで確認したいデータの統計量は「最小値」「第1四分位数」「中央値」「平均値」「第3四分位数」「最大値」とします。簡単のため、箱ひげ図のように外れ値を分離して表示することは考えません。

平均値は赤色の×、その他の各統計量は○で表し、「第1四分位数」と「中央値」、「中央値」と「第3四分位数」のそれぞれを線で結んでみます。

```r
####################################
# 統計量を可視化
####################################

# カラーマップ
library(RColorBrewer)
cols <- rainbow(9, start = .7, end = .1)
cols <- gray.colors(9, start = 0.1, end = 0.9)

# 四分位数を計算する関数
q1_val <- function(x){return(quantile(x, probs = 0.25))}
q3_val <- function(x){return(quantile(x, probs = 0.75))}

# levelごとに統計量を計算しておく
df_stats <- 
  df %>% 
  group_by(level) %>% 
  summarise(min = min(value),
            q1_val = q1_val(value),
            median = median(value),
            mean = mean(value),
            q3_val = q3_val(value),
            max = max(value)
            ) %>% 
  ungroup()

# 描画
ggplot(data = df, aes(x = level, y = value)) +
  stat_summary(fun.y = min, geom = "point", shape = 21, colour = "black", fill = cols[1]) +
  stat_summary(fun.y = q1_val, geom = "point", shape = 21, colour = "black", fill = cols[3]) +
  stat_summary(fun.y = median, geom = "point", shape = 21, colour = "black", fill = cols[5]) +
  stat_summary(fun.y = mean, geom = "point", shape = 4, colour = "black", fill = "green", size=1) +
  stat_summary(fun.y = q3_val, geom = "point", shape = 21, colour = "black", fill = cols[7]) +
  stat_summary(fun.y = max, geom = "point", shape = 21, colour = "black", fill = cols[9]) +
  xlab("水準(level)") +
  ylab("value")+
  geom_segment(data = df_stats,
               aes(x = level, xend=level, y = q1_val, yend = median),
               size = 0.25, lineend = "butt", color = cols[4]) +
  geom_segment(data = df_stats,
               aes(x = level, xend=level, y = median, yend = q3_val),
               size = 0.25, lineend = "butt", color = cols[6]) 
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/153124/15fdf85d-020d-eb6d-d054-9bc3ac3815de.png)

残念ながら箱ひげ図の場合と同じか、それ以上にゴチャゴチャしています。

ここからさらに、分布の様子を踏まえて横軸の水準を並び替えてみましょう。各水準の中央値の大きさで水準を並び変えます。

```r
# 描画

ggplot(data = df, aes(x = reorder(x = level, X = value, FUN = median), y = value)) +
  stat_summary(fun.y = min, geom = "point", shape = 21, colour = "black", fill = cols[1]) +
  stat_summary(fun.y = q1_val, geom = "point", shape = 21, colour = "black", fill = cols[3]) +
  stat_summary(fun.y = median, geom = "point", shape = 21, colour = "black", fill = cols[5]) +
  stat_summary(fun.y = mean, geom = "point", shape = 4, colour = "red", size=1) +
  stat_summary(fun.y = q3_val, geom = "point", shape = 21, colour = "black", fill = cols[7]) +
  stat_summary(fun.y = max, geom = "point", shape = 21, colour = "black", fill = cols[9]) +
  xlab("水準(level)") +
  ylab("value") +
  geom_segment(data = df_stats,
               aes(x = level, xend = level, y = q1_val, yend = median),
               size = 0.25, lineend = "butt", color = cols[4]) +
  geom_segment(data = df_stats,
               aes(x = level, xend = level, y = median, yend = q3_val),
               size = 0.25, lineend = "butt", color = cols[6]) 
```

（変更前）```x = level```
（変更後）```x = reorder(x = level, X = value, FUN = median)```
このように```ggplot()```の中で指定する```aes()```の```x=```の部分を変更するのがポイントです。[^1]

![image.png](https://qiita-image-store.s3.amazonaws.com/0/153124/82588336-185c-c016-9f2d-2c0069965a36.png)

さて、これでだいぶスッキリしました。赤色の×で平均値、線でつながれた3つの点（○）が、各水準の第1四分位数、中央値、第3四分位数を表しています。その外側にあるのが最小値と最大値です。

この水準数になると、各水準がどうというよりも、全体的にどのような傾向かが知りたくなります。
そんなときにこのような可視化をすると、各水準での分布が全体ではどのように分布しているのかを直感的に把握でき（る気がし）ますね。

[^1]: [この記事](https://qiita.com/kazutan/items/7840f743d642122d1219#%E4%BD%95%E3%81%8B%E5%A4%96%E7%9A%84%E3%81%AA%E5%9F%BA%E6%BA%96%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%84%E3%81%A6%E4%B8%A6%E3%81%B9%E6%9B%BF%E3%81%88%E3%81%9F%E3%81%84%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)を参考にさせてもらいました。