這篇主要會著重在 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 實作分享。
先安裝跟呼叫等一下會用到的套件
# 安裝套件
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 |
要畫樣區數隨著時間成長的趨勢圖,首先就要計算每年的樣區個數
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()
利用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("圖片檔案.png", # 檔案名稱
plot = p, # 圖片變數,如果沒有指定預設會儲存最後一張畫出來的圖
path = "E:/for R markdown", # 檔案路徑,不包含檔名
width = 12, # 圖片寬度
height = 12, # 圖片長度
dpi = 300) # dpi
這邊要注意的是,如果原本ggplot中沒有特別設定文字的大小,輸出圖檔文字、圖形的比例會受 ggsave 中圖片檔長寬設定所影響,長度跟寬度值設置的越大,圖片中文字跟圖形的比例就會變得比較小。