ggplot2で 集計されていないデータフレームから 離散変数の頻度棒グラフを描く(1変数&2変数)

すでに他の方々が紹介された内容の繰り返しばかりですが,勉強を兼ねた自分用のメモです。
自分用ではありますが,同じくらいのR歴の方のお役に立てばいいなと思います。
間違いやもっと良い方法など,お気づきの場合は私のtwitterアカウント@imuyaoyiにお知らせいただければとっても助かります。

● 使用するパッケージ

library(ggplot2)
library(dplyr)

● 1変数

手順は以下通りです。
1. tableas.data.frameを使ってデータフレーム型の度数分布表を作成する
2. 通常通り,ggplotで好きなようにプロットする

irisデータを使ってやってみます。
str(iris)
## 'data.frame': 150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

度数分布表の作成

度数分布表を作成する関数はtableです。
dplyr::selectで変数を選択し,%>%でtableにつなげます。このときオブジェクトクラスはtable型です。
このままではデータフレーム型しか扱えないggplotに渡すことができません。

(ftab1 <- iris %>%
  dplyr::select(Species) %>% # 手順1.変数を選択する
  table)                      # table形式の度数分布表が作成される
## .
##     setosa versicolor  virginica 
##         50         50         50
class(ftab1)
## [1] "table"

as.data.frame()を使ってデータフレーム形式に変換し,ggplotに渡せるようにします。
作成された度数分布表のデータフレームの列名は「.」=選択した変数「Freq」=観測度数と設定されます。

(ftab1 <- ftab1 %>% as.data.frame())
##            . Freq
## 1     setosa   50
## 2 versicolor   50
## 3  virginica   50

あとはggplotに渡すだけ。

ggplot

(p1 <- ftab1 %>%
  ggplot(aes(x =  ., y = Freq))+
  geom_bar(stat = "identity"))

デザイン

(p1 <- p1 %>%
  +geom_text(aes(x = ., y = Freq,  label = Freq, vjust = -0.5), size = 5)+ # 数値ラベル
  theme_classic(base_size = 18)+ # 全体のテーマ
  labs(title = "Species" , y = "")) # グラフタイトルの設定とy軸ラベルの削除

関数の作成

BarPlot1 <- function(data, i){
  Data <- data[!is.na(data[,i]),] # 選択した変数に欠損値がない行を使用する
  VName1 = colnames(Data)[i]
  
  P <- Data %>%
  dplyr::select_(VName1) %>%
  table %>% as.data.frame() %>%
  ggplot(aes(x =  ., y = Freq))+
  geom_bar(stat = "identity")+
  geom_text(aes(x = ., y = Freq,  label = Freq, vjust = -0.5), size = 5)+
  theme_classic(base_size = 18)+
  labs(title = colnames(Data)[i] , y = "")
  
  print(P) #
  
}

BarPlot1(iris, 5)

● 2変数

2変数では,集合グラフ( position = "dodge")積み上げグラフ( position = "stack")の2通りを紹介します。

irisデータにカテゴリー変数をひとつ追加したデータ(iris2)を使ってやってみます。

iris2 <- iris
V1 <- rep(LETTERS[1:5], c(15,45,30,25,35))
V1 <- V1[order(rnorm(length(V1)))]

iris2$V1 <- V1

str(iris2)
## 'data.frame': 150 obs. of  6 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ V1          : chr  "D" "C" "A" "B" ...

・ 度数分布表の作成

主な手順は,1変数の時に比べて 以下の2点が加わります。

  • はじめにdplyr::group_byでグループ化する
  • 度数分布表の作成時,列名に元の変数名が与えられる

dplyr::group_bydplyr::selectで度数プロットをしたい変数を指定します。
このとき,2つの変数はどちらで指定しても同じ内容の度数分布になります。(当たり前ですが)

1変数の時と違って,観測度数の列名が「Freq」であることは同じですが,変数名は元の変数名が与えられます。

(ftab2 <- iris2 %>%
   dplyr::group_by(Species) %>%
   dplyr::select(V1) %>%
   table %>% as.data.frame())
##       Species V1 Freq
## 1      setosa  A    3
## 2  versicolor  A    6
## 3   virginica  A    6
## 4      setosa  B   18
## 5  versicolor  B   11
## 6   virginica  B   16
## 7      setosa  C    6
## 8  versicolor  C   12
## 9   virginica  C   12
## 10     setosa  D   15
## 11 versicolor  D    6
## 12  virginica  D    4
## 13     setosa  E    8
## 14 versicolor  E   15
## 15  virginica  E   12

・集合グラフ

ほぼggplot2逆引きの記事の通りです。
集合グラフの場合は,1変数の手順に次のが手順が加わるだけです。

  • geom_barで「position = "dodge"」を指定する
  • geom_textでaes(group = グループ化したい変数名),と**position = position _dodge(width = 0.9)**を指定する

ggplot

ftab2 %>%
  ggplot(aes(x =  Species, y = Freq, fill = V1))+
  geom_bar(stat = "identity", position = "dodge")+ # プロット位置を"dodge"に指定
  geom_text(aes(x = Species, y = Freq,  label = Freq, vjust = -0.5,
                group = V1), # 数値ラベルの位置をグループの水準ごとの位置に配置する
            position = position_dodge(width = 0.9))# 指定しないとエラーが表示される

デザイン

ftab2 %>%
  ggplot(aes(x =  Species, y = Freq, fill = V1))+
  geom_bar(stat = "identity", position = "dodge")+
  geom_text(aes(x = Species, y = Freq,  label = Freq, vjust = -0.5,
                group = V1),
            position = position_dodge(width = 0.9), size = 5)+
  theme_classic(base_size = 18)+
  labs(title = "Species * V1" , x = "", y = "")

関数の作成

BarPlot2 <- function(data, i, j){
  
  Data <- data[!is.na(data[,i]),]
  VName1 = colnames(Data)[i]
  VName2 = colnames(Data)[j] 
 
  P <- Data %>%
    dplyr::group_by_(VName1) %>%
    dplyr::select_(VName2) %>%
    table %>% as.data.frame() %>%
    ggplot(aes_string(x =  VName1, y = "Freq", fill = VName2))+
    geom_bar(stat = "identity", position = "dodge")+
    geom_text(aes_string(x = VName1, y = "Freq",label = "Freq", vjust = -0.5,
                         group = VName2),
              position = position_dodge(width = 0.9), size = 5)+
    theme_classic(base_size = 18)+
    labs(title = paste(VName1, " * ", VName2) , x = "", y = "")
  
  print(P)
    
}

BarPlot2(iris2, 6, 5)

・ 積み上げグラフ

集合グラフで数値ラベルを入れるのは比較的簡単ですが,積み上げグラフの場合は
ちょうど良い位置に数値ラベルを配置しようとすると少し工夫が必要です。
kazutan先生のggplot2逆引きを参考にさせていただき,
度数が集計されていないデータフレームからでも作図できるようにやってみました。

度数分布表を改造

  • x軸にとりたい変数で再度グループする
  • 数値ラベルを配置するy軸の位置を計算し,mutateでデータフレームに列を追加する
(ftab3 <- iris2 %>%
   dplyr::group_by(V1) %>%
   dplyr::select(Species) %>%
   table %>% as.data.frame() %>%
   dplyr::group_by(V1) %>% # x軸にとりたい変数で再度グループ
   dplyr::mutate(Pos = cumsum(Freq) - (Freq * 0.5))) #  積み上げの各部位の中央になる位置を計算し,列に追加
## Source: local data frame [15 x 4]
## Groups: V1 [5]
## 
##        V1    Species  Freq   Pos
##    (fctr)     (fctr) (int) (dbl)
## 1       A     setosa     3   1.5
## 2       B     setosa    18   9.0
## 3       C     setosa     6   3.0
## 4       D     setosa    15   7.5
## 5       E     setosa     8   4.0
## 6       A versicolor     6   6.0
## 7       B versicolor    11  23.5
## 8       C versicolor    12  12.0
## 9       D versicolor     6  18.0
## 10      E versicolor    15  15.5
## 11      A  virginica     6  12.0
## 12      B  virginica    16  37.0
## 13      C  virginica    12  24.0
## 14      D  virginica     4  23.0
## 15      E  virginica    12  29.0

ggplot

(ftab3 %>%
  ggplot(aes(x =  V1, y = Freq, fill = Species))+
  geom_bar(stat = "identity", position = "stack")+  # プロット位置のを"dodge"に指定
  geom_text(aes(label = Freq, y = Pos))) # 数値ラベル位置をPosで指定

デザイン

(ftab3 %>%
   ggplot(aes(x =  reorder(x = V1, X = Freq, FUN = sum), y = Freq, fill = Species))+ # 頻度の多い順に並べる
   geom_bar(stat = "identity", position = "stack", alpha = 0.7)+ # グラフを透過させる
   coord_flip()+ # 縦と横を入れ替える
   guides(fill = guide_legend(reverse = TRUE))+ # 凡例の位置を積み上げ順と同じにする
   geom_text(aes(label = Freq, y = Pos), size = 5)+
   theme_classic(base_size = 18)+
   theme(panel.grid.major = element_line(color = "lightgray"),
         panel.grid.major.y = element_blank(), # y軸のグリッドを消す
         plot.background = element_rect(color = "gray", size = 1))+
   labs(title = "V1 * Species" , x = "", y = ""))

関数の作成

BarPlot3 <- function(data, i, j){
  
  Data <- data[!is.na(data[,i]),]
  VName1 = colnames(Data)[i]
  VName2 = colnames(Data)[j] 
  
  P <- Data %>%
    dplyr::group_by_(VName1) %>%
    dplyr::select_(VName2) %>%
    table %>% as.data.frame()%>%
    dplyr::arrange_(VName1)%>%
    dplyr::group_by_(VName1)%>%
    dplyr::mutate(Pos = cumsum(Freq) - (Freq * 0.5))%>%
    ggplot(aes_string(x =  paste("reorder(x = ", VName1, ",X = Freq, FUN = sum)"), y = "Freq", fill = VName2))+
    geom_bar(stat = "identity", position = "stack", alpha = 0.7)+
    coord_flip()+
    guides(fill = guide_legend(reverse = TRUE))+
    geom_text(aes(label = Freq, y = Pos), size = 5)+
    theme_classic(base_size = 18)+
    theme(panel.grid.major = element_line(color = "lightgray"),
          panel.grid.major.y = element_blank(),
          plot.background = element_rect(color = "gray", size = 1))+
    labs(title = paste(VName1, " * " ,VName2), x = "", y = "")
  
  print(P)
  
  }


BarPlot3(iris2, 5, 6)

・ おまけ

どうせなら,集合も積み上げもオプションで選べる関数にしたい。

BarPlot4 <- function(data, i, j, type){
  
  Data <- data[!is.na(data[,i]),]
  VName1 = colnames(Data)[i]
  VName2 = colnames(Data)[j] 
  switch (type,
          "dodge" = BarPlot2(Data, i, j),
          "stack" = BarPlot3(Data, i, j))
  }

 
BarPlot4(iris2, 6, 5, type = "dodge")
BarPlot4(iris2, 6, 5, type = "stack")

● 感想等

現時点で,geom_ bar(stat = ""count))やstat_ summaryをうまく使えばもっと効率よくできるのでは?と思ってます。。
なので,中途半端なものをさらしてすみません。。 そもそもplotly使えば苦労して数値ラベル入れる必要もないのではと思ったりも。
さらには、そもそもなぜこんなことしてるんだろうと思ったりも・・・。 それでも,ひとまずggplot2で好みのグラフが描けて気持ちいいです。
とくに,2変数の積み上げグラフはお気に入り。

● 参考にさせていただきました

逆引きからとくに抜粋