前言

這篇主要會著重在 ggplot2 的架構,以及實際操作時,程式語言撰寫的文法。在開始本篇之前,先推這一篇淺談資料視覺化以及ggplot2實踐,裡面談了很多視覺化實踐的概念,關於 ggplot2 的架構也講得很詳細 (詳細到覺得推完這篇就可以結束惹XD“),本篇部分的概念也都是從這篇來的。


談到資料視覺化 (非互動式的圖),在 R 語言當中數一數二常用的 package,非 ggplot2 莫屬了!主要的原因在於它有很大的彈性,可以用簡單幾行的程式碼來瞭解資料的狀態,也可以透過調整多項參數來修改每一項在圖上所看到的項目,來輸出看起來很專業的圖。


ggplot2 程式碼的架構是圖層式的,主要分成 7 個層次,在撰寫 ggplot 的時候,會由下往上一層一層建立。7 層的架構看起來雖然很複雜,不過在實際出圖的時候,並不是每一層都需要去設定參數,最基本的架構只要給定最下面的三層,Data、Aesthetics 以及 Geometries 的設定,其他層採用預設值就可以出一張圖了。

  • Data:資料層,也就是我們要拿來視覺化的資料。

  • Aesthetics:視覺變數層,中文翻譯起來有點難懂,不過簡單來說就是告訴 ggplot 我們的 x 跟 y 分別對應到資料當中的哪個欄位,會用 aes() 來包住。

  • Geometries:幾何圖形層,用不同的函數來指定我們的資料要以什麼樣的圖形呈現,像是要呈現點的話對應的函數是 geom_point()、長條圖對應到 geom_bar()、盒鬚圖對應到 geom_boxplot() 等。ggplot的各種幾何圖形層函數可以見 ggplot2 Cheet sheet 裡面的 geom_ 系列。

Image Credit: DataCamp - Data Visualization with ggplot2 (Part 1)



假設今天我們有一個資料表如下,包含物種名稱 (Species) 與隻數 (Count)

Species Count
Kentish Plover 1000
Common Teal 300
Little Egret 120

最簡單的指令只要像下面這樣,就可以輸出一張長條圖來呈現不同物種的隻數

ggplot() +
  geom_col(data = bird.data, 
           aes(x = Species, y = Count))

在上方的程式碼中,ggplot() 用來開啟一張新的空白圖層、 geom_col() 是幾何圖形層,col 代表繪製的是長條圖,裡面的 aes(x = Species, y = Count) 則是視覺變數層,告訴 ggplot2 我們的 x 軸要看 Species 這個欄位,y 軸要看 Count 這個欄位。其中對於 資料層 以及 視覺變數層 的指定,可以包在 geom_col() 裡面,也可以放在 ggplot() 裡面,改寫成像下面這樣。

ggplot(data = bird.data, 
       aes(x = Species, y = Count)) +
  geom_col()


不過由於在實際應用時,很多時候視覺化的呈現需要結合多個資料表,這種時候資料層 (data) 以及視覺變數層 (aes()) 就要放在幾何圖形層 (geom_col()) 當中來指定,也就是第一種表示法才行。所以雖然對單一資料表的出圖來說,兩種表示法都可以,但我自己還是習慣用第一種方式來撰寫,一來讓程式碼的文法一致,二來也可以減少出錯的機會。



資料視覺化依目的可以簡單分為

  • 探索資料

  • 說故事

在探索資料的階段,主要是讓資料分析者自己了解資料的特性,有些時候可以直接拿原始資料來畫畫看資料的趨勢。不過當到了說故事的階段,要透過視覺化來詮釋資料的特徵給別人時,往往就會需要經過一番資料角力 (data wrangling) (其實就是資料整理,不過角力聽起來比較潮XD“”),經過資料篩選、清理、型態轉換等流程,才能產出要繪圖的資料,再進一步用這個繪圖資料來出圖。所以對資料視覺化這件事來說,不單是繪圖用的 ggplot2,資料角力相關的套件如 dplyr、data.table 也需要了解熟悉。 以下範例也會包含簡單的資料角力,不過在細節上不會著墨太多,想了解細節的部分請參照 data.table教學,或是期待(?)之後應該也會寫(但可能會拖搞拖很久(艸))的一些小型 project 實作分享。



ggplot2 實作

環境設定

先安裝跟呼叫等一下會用到的套件

# 安裝套件
list.of.packages <- c("data.table", "magrittr", "tidyr", "ggplot2")
new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
if(length(new.packages)) install.packages(new.packages)

# 呼叫套件
library(data.table)
library(magrittr)
library(tidyr)
library(ggplot2)


資料整理

這邊我們用第一篇 data.table 教學使用的 BBS 資料來作範例,資料取得的方法以及資料整理的細節請參考 data.table教學 囉!

讀取資料

BBS <- 
  fread("E:/R markdown/BBS2009-2015/occurrence.txt",
        sep = "\t", encoding = "UTF-8") 


資料篩選

BBS.select <- BBS %>%
  # 擷取需要的欄位並且重新命名
  .[, list(Species = vernacularName,
           Count = individualCount,
           Recorder = recordedBy, 
           eventID, 
           Long = decimalLongitude, 
           Lat = decimalLatitude
           )] %>%
  # 從eventID欄位中,用separate來分離出有用的資訊 (eventID: TWBBS_2015_A33-13_05_02)
  separate("eventID", c("Dataset", "Year", "Site", "Point", "Order"), sep = "_") %>%
  # 移除Dataset欄位
  .[, Dataset := NULL]

初步整理完的資料表長這樣

Species Count Recorder Year Site Point Order Long Lat
麻雀 12 廖自強 2015 A33-13 05 02 120.5573 23.92991
麻雀 8 廖自強 2015 A33-13 06 02 120.5594 23.92730
白頭翁 2 廖自強 2015 A33-13 03 02 120.5574 23.92519
珠頸斑鳩 1 廖自強 2015 A33-13 04 02 120.5556 23.92750
灰頭鷦鶯 1 廖自強 2015 A33-13 06 02 120.5594 23.92730


範例一

從 2009-2015 年樣區數量的成長趨勢 (長條圖)

要畫樣區數隨著時間成長的趨勢圖,首先就要計算每年的樣區個數

data.01 <- 
  BBS.select[, .(nSite = uniqueN(Site)),  # uniqueN計算Site的項目個數
             by = Year]
Year nSite
2015 354
2009 148
2010 140
2011 253
2012 311
2013 291
2014 293

接著我們就用 data.01 來繪圖

ggplot() +
  geom_col(data = data.01,
           aes(x = Year, y = nSite))


在 BBS 的樣區中有分低中高三個海拔段,在樣區編號中第一個英文字母代表的就是樣區所屬的海拔段,所以也可以來看不同海拔段的樣區數,他們的成長趨勢分別為何。 在畫圖之前我們要先產生繪圖需要的資料,也就是不同海拔段每年的調查樣區數。

data.02 <- 
  # 利用substr取Site這個欄位的第一個字元,作為elevation的值
  BBS.select[, elevation := substr(Site, 1, 1)] %>%
  # 利用uniqueN計算每個海拔段每年樣區的項目個數
  .[, .(nSite = uniqueN(Site)),
    by = list(elevation, Year)]
elevation Year nSite
A 2015 306
B 2015 34
C 2015 14
A 2009 108
B 2009 30

利用新的 data.02 來製圖,這邊除了要呈現每年的數量外,還要區分不同的海拔段,所以在 aes() 中多了 group 來指定資料分組的方式,以及透過 fill = elevation 來指定不同海拔段的資料在圖中以填入不同的顏色的方式來區分。

ggplot() +
  geom_col(data = data.02,
           aes(x = Year, y = nSite, 
               group = elevation, fill = elevation))


另外可以透過 position = "dodge" 來讓不同海拔段的樣區數長條圖以併排的方式來呈現。

ggplot() +
  geom_col(data = data.02,
           aes(x = Year, y = nSite, 
               group = elevation, fill = elevation),
           position = "dodge")


也可以透過更多參數的設定來修改既有的圖示還有坐標軸的資訊。
坐標軸、圖片標題等參數的設置方式有很多種,選擇自己寫的最順手的方式就好~

ggplot() +
  geom_col(data = data.02,
           aes(x = Year, y = nSite, 
               group = elevation, fill = elevation),
           position = "dodge") +
  scale_fill_manual(name = "海拔段", # 新的圖示名稱
                    breaks=c("A", "B", "C"), # 原始分類值
                    labels=c("低海拔", "中海拔", "高海拔"), # 新的分類值
                    values = c("red", "orange", "blue")) + # 指定新顏色 
  labs(x = "時間", y = "樣區數", title = "樣區數隨時間變化") + # 指定x, y軸以及標題內容
  theme_bw() + # 設置版面樣式
  theme(plot.title = element_text(size=18, face="bold", color = "blue"), # 指定標題文字大小、樣式
        axis.title = element_text(size=14)) # 指定坐標軸文字大小、樣式


注意這邊我設定圖示相關參數的函數是 scale_fill_manual(),是呼應 aes() 內的 fill = elevation設定。如果今天是要讓不同分組的資料以不同形狀 (shape) 來區分,對應的函數會是 scale_shape_manual(),要用不同線條顏色 (color) 來區分則是 scale_color_manual()

在各項參數設置時要注意前後順序,如同一開始所說的,ggplot2 的架構是圖層式的,不管是繪製的圖層或是參數的設置,前面的設定都會被後面的設定覆蓋掉,像是上面程式碼最後的 theme()theme_bw() 的前後順序如果反過來,輸出的圖會是不一樣的,有興趣的話可以試試把他們反過來的效果~


除了讓海拔段的資訊以長條圖併排的方式呈現外,我們也可以依海拔段分成多張小圖來看,這個時候就會用到 facet 這一層的設置。 facet 有兩大家族:facet_wrap() 以及 facet_grid(),兩個的用途差不多,這邊就先示範 facet_wrap()

ggplot() +
  geom_col(data = data.02,
           aes(x = Year, y = nSite)) +
  facet_wrap(~ elevation) # 指定依照elevation來分別作圖


也可以進一步在 facet_wrap 中加入更多參數來調整輸出的圖片

ggplot() +
  geom_col(data = data.02,
           aes(x = Year, y = nSite)) +
  facet_wrap(~ elevation, 
             ncol = 1, # ncol指定輸出的圖只排成一欄
             scale = "free_y") + # scale讓三張分圖y軸的範圍可以隨資料調整
  theme_bw()



範例二

用 ggplot2 製作樣區地圖

利用BBS資料當中的樣區經緯度坐標,來呈現樣區在空間上的分布。 由於資料數量比較多,我們就先單看每年每個樣區的第一個樣點,在第一旅次調查時回傳的坐標位置

資料準備

# 準備樣點資料
data.03 <- 
  BBS.select[Point == "01" & Order == "01"]

# 準備台灣地圖資料
library(rgdal)
TW.data <- 
  # 讀取台灣縣市界的shapefile
  readOGR("E:/R markdown/COUNTY", "TW_COUNTY") %>%
  # 透過fortify()將shapefile的空間資訊轉成ggplot2可讀的data.frame
  fortify(TW, region = "COUNTYNAME") # 透過region來綁定原本shapfile屬性表的欄位


fortify 會擷取原本 shapefile 圖層中每個 polygon 的坐標點位,假設原本的 polygon 是由 6 個點位所組成,經過 fortify() 轉換後的資料,就會是 6 列的坐標值,並且在 group 欄位中,會擁有相同的值。像下圖的資料表中 group 同為 宜蘭縣.1 的這些資料就是代表組成同一個 polygon 的點位坐標。

long lat order hole piece id group
121.9606 24.98822 1 FALSE 1 宜蘭縣 宜蘭縣.1
121.9607 24.98822 2 FALSE 1 宜蘭縣 宜蘭縣.1
121.9608 24.98824 3 FALSE 1 宜蘭縣 宜蘭縣.1
121.9609 24.98827 4 FALSE 1 宜蘭縣 宜蘭縣.1
121.9610 24.98830 5 FALSE 1 宜蘭縣 宜蘭縣.1
121.9611 24.98830 6 FALSE 1 宜蘭縣 宜蘭縣.1
121.9611 24.98826 7 FALSE 1 宜蘭縣 宜蘭縣.1
121.9613 24.98818 8 FALSE 1 宜蘭縣 宜蘭縣.1
121.9613 24.98818 9 FALSE 1 宜蘭縣 宜蘭縣.1
121.9615 24.98805 10 FALSE 1 宜蘭縣 宜蘭縣.1

地圖繪製

ggplot() +
  # 台灣底圖 (底圖在下層,所以要寫在前)
  geom_polygon(data = TW.data, 
               aes(x = long, y = lat, group = group), # group一定要設,不然ggplot會不知道那些點要連成一個polygon 
               color = "black", fill = "white") + # 用color設定邊線顏色,fill設定區塊填滿的顏色,放在aes()之外
  # 樣點點位 (樣點點位在上層,所以要寫在後)
  geom_point(data = data.03,
             aes(x = Long, y = Lat, color = Year)) + # color讓不同年份的樣點以不同顏色來呈現
  coord_fixed() # 固定坐標的長寬比例,沒有設的話地圖很容易會變形

另外也可以透過前面提到的 facet_wrap() 來分年份產生樣點地圖

p <- ggplot() +
  geom_polygon(data = TW.data, 
               aes(x = long, y = lat, group = group), 
               color = "black", fill = "white") + 
  geom_point(data = data.03,
             aes(x = Long, y = Lat)) +
  coord_fixed() +
  facet_wrap(~ Year) # 利用facet_wrap來產生分年的地圖

p  # 畫出來看一下

最後介紹圖片的輸出,ggsave()

ggsave("圖片檔案.png",  # 檔案名稱
       plot = p,  # 圖片變數,如果沒有指定預設會儲存最後一張畫出來的圖
       path = "E:/for R markdown", # 檔案路徑,不包含檔名
       width = 12, # 圖片寬度
       height = 12, # 圖片長度
       dpi = 300) # dpi

這邊要注意的是,如果原本ggplot中沒有特別設定文字的大小,輸出圖檔文字、圖形的比例會受 ggsave 中圖片檔長寬設定所影響,長度跟寬度值設置的越大,圖片中文字跟圖形的比例就會變得比較小。