Home | About | Posts | English

從零開始使用R進行完整的科學繪圖 (持續更新)

2023/01/11
最後更新:2023.01.16
在進行科學研究的時候,我們不可避免的需要繪製各種圖像來協助表達,這些需求包含但不限於口頭報告、海報、研討會以及期刊發表。一直以來,我們實驗室最常使用的應用程式就是SigmaPlot,不可否認這個程式有其厲害之處,但我認為R語言可以包辦從資料處理、分析到繪圖,是相當全面的,而且程式語言的重複使用性讓我們不用每次繪圖都重新開一個檔案畫一次,也是一項優勢。
我無意比較哪種軟體最好,只有最適合自己的方法,今天就來介紹如何使用R語言來繪製一個精美且可以進行期刊投稿的科學繪圖!

謂學不暇給者,雖暇亦不能學
—《淮南子·說山訓》

示範資料:iris

熟悉R語言的人一定都聽過這個資料集,但是由於標榜從零開始,我必須先介紹一下iris,也就是鳶尾花資料集。這是一個多變數資料集,內容包含著有關鳶尾花的不同變數,而且已經內建在每個R語言下載包內,是相當適合用於教學的資料,稍微查了一下這個資料集還跟統計學之父R.A.Fisher有關呢。
使用這個資料集的方法就是呼叫他:

data(iris)  # 呼叫iris資料集  
head(iris)  # 展示這個資料的前六個row  

執行上述程式碼後將可以獲得以下的內容

  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

這就是iris資料集內包含的五個變數,而整個iris資料集共有150筆樣本。Sepal.LengthSepal.WidthPetal.LengthPetal.Width是連續變數,而Species則是類別變數,我們在繪圖時會常用到Species這個變數,例如要將不同樣品根據土壤母質、採樣地點等特性而標上不同的顏色,因此類別變數就會拿來示範這個功能。要取出一個資料集中的某個變數,需要使用$,例如我想要Sepal.Length這個變數資料,就輸入iris$Sepal.Length即可。接下來,我就利用這個資料集來示範繪製可用於期刊發表的圖。

最基礎的plot函數

在R語言中,plot函數是個最為基本,操作起來直觀的函數,雖然陽春,但可以細微調整的地方也相當多,可以用來簡單觀察資料型態,也可以進行期刊投稿,基本上可以應付大多數的目的。
對於繪製散布圖而言,plot函數的組成是這樣的 (我只列出常會用到的參數):

plot(x, y = NULL,              # xy座標,必須輸入對應的數值 (1)
     type = "p",               # 指定線的種類 (2)
     xlim = NULL, ylim = NULL, # 指定畫出來的範圍
     log = "",                 # 輸入"x"代表x軸取對數,"y"跟"xy"同理
     main = NULL, sub = NULL,  # 標題名
     xlab = NULL, ylab = NULL, # 座標軸名,分x跟y
     ann = par("ann"),         # 要不要顯示座標軸名稱,邏輯值 (3)
     axes = TRUE,              # 要不要顯示座標軸,邏輯值
     frame.plot = axes,        # 圖要不要有外框,邏輯值
     asp = NA                  # x/y座標的長度比例 (4)
     ...)
  1. 上面這個是函數的預設組成,而裡面包含許多的參數例如x, y, type, xlim等等,如果某個參數幫你附註了= XXX,例如y = NULL,代表這個參數有預設值,你若不指定這個參數就會自動代入,相反的如果沒有預設值,就代表你必須給予x一個數值,否則函數將出現error。
  2. “p”代表點,”l”代表線,”b”代表有線串聯的點, “c”代表沒有點的的虛線,”o”代表線會畫在點上面,”s”和”S”代表階梯圖,”h”代表直線狀的長條圖,”n”則會是空的沒有圖。
  3. 邏輯值代表這個值需要輸入TrueFalse,在plot函數中如果不做任何事就會是T而顯示該項目,如果不想顯示該項目則可以用F來關掉。
  4. 最常見就是asp = 1代表xy座標比例要一樣。

首先,我們先隨意繪製一張圖:
plot(x=iris$Sepal.Length, y=iris$Sepal.Width)
image
可以看到這就是最陽春的圖,沒有設定X軸、Y軸標題,也沒有其他美編,接下來我就要從這個圖開始畫出可以拿來用於發表的圖。

以plot函數開始科學繪圖

通常需要注意的地方有幾個,可以跟著檢視自己的圖,讓我們從圖的外圍向內開始講:

  1. X軸與Y軸需要有標題,且字體要大一點
  2. X軸與Y軸是否需要客製範圍 (這裡示範將X軸改成0到8,Y軸改成0到5),且字體要大一點
  3. 座標點要黑色更大
plot(x = iris$Sepal.Length, y = iris$Sepal.Width,
     xlab = "Sepal Length", ylab = "Sepal Width",  # XY軸標題,注意文字要以" "包起來 
     cex.lab=1.3,                                  # 調整座標軸標題字體大小,數字越大字體越大
     xlim = c(0,8), ylim = c(0,5),                 # 調整座標軸範圍
     cex.axis = 1.5,                               # 調整座標軸字體大小,數字越大字體越大
     pch = 19,                                     # 將點變為黑色的(註)
     cex = 1                                       # 調整點的大小,數字越大點越大
     )

image
註:pch這個參數代表點的形狀,我通常是喜歡用黑色圓點19,想使用空心點或是方形菱形可以參考這裡

添加線及文字說明

然後我們還有一些常用的功能以及函數要介紹,首先是abline(),這是在圖上加線的函數,他的參數可以這樣設定:

abline(a = 0,   # 設定線的Y截距
       b = 1    # 設定線的斜率
       lwd = 1  # 設定線的粗細,數字越大越粗 (只要線都適用)
       )
abline(h = 3)   # 設定一條水平線,y=3
abline(v = 2)   # 設定一條水平線,x=2

接著是text(),可以幫助你加文字,參數是這樣:

text(x = 2, y = 4, # XY座標
     "1:1 Line",   # 文字內容,一樣要用" "包起來
     cex = 1.2,    # 字體大小
     pos = 1       # 字要在選定XY座標的下左上右方 (分別對應1234)
     offset = 1    # 如果有使用pos,那麼要在那個方向距離中心多遠 
     )

接著,就讓我們實際繪製一張包含有上述幾種功能的圖吧!

plot(x = iris$Sepal.Length, y = iris$Sepal.Width,
     xlab = "Sepal Length", ylab = "Sepal Width", 
     cex.lab = 1.3,
     xlim = c(0,8), ylim = c(0,8),
     cex.axis = 1.5,
     pch = 19, 
     cex = 1
     )
abline(a = 0,b = 1,lwd = 2)
text(x = 7.2, y = 6, "1:1 Line", 
     cex = 0.9)

image
只要自己調整不同參數的大小,就可以客製化一張圖,進而進行發表!

調整邊界及文字方向

到這裡,差不多就有一張較為完整的圖了,接下來在plot函數還有一點需要說明的,那就是par函數,這是有關於繪圖區域的調整,例如要一次畫兩張圖、調整邊界、調整背景顏色等等,都可以使用par函數:

par(mfrow = c(1,2),   # 將繪圖區域改成可以繪製1 row*2 column張圖
    mar=c(5,6,3,4),   # 調整圖外的邊界 (註) 
    las = 1,          # 設定座標軸的數字是水平還是垂直,預設是0代表垂直,改為1則為水平
    bg="transparent"  # 設定圖的背景
    )

註:注意是座標軸距離邊界的大小,所以設太小則座標軸標題會消失,四個數字分別對應下左上右的邊界,預設是5.1, 4.1, 4.1, 2.1。

舉例而言,我們想調整邊界讓左邊更大一點,右邊更小一點,然後把Y軸改成水平的

par(mar = c(5.1, 5, 4.1, 1.5),  # 只改動左邊跟右邊
    las = 1
    )
plot(x = iris$Sepal.Length, y = iris$Sepal.Width,
     xlab = "Sepal Length", ylab = "Sepal Width", 
     cex.lab = 1.3,
     xlim = c(0,8), ylim = c(0,8),
     cex.axis = 1.5,
     pch = 19, 
     cex = 1
     )
abline(a = 0,b = 1,lwd = 2)
text(x = 7.2, y = 6, "1:1 Line", 
     cex = 0.9)

image
是否注意到了左右邊界的調整以及Y座標軸的數字方向改變?如此這般,從最初沒有任何修飾的圖,逐漸成為了有模有樣的圖呢!

讓點根據不同標籤而有顏色

我們常需要讓點根據他們各自的特性而有不同顏色,以iris資料集為例,Species欄位中包含setosa、versicolor、virginica這三種類別,若我們想看這三種類別各自的位置,我們就需要多指定參數col

plot(x = iris$Sepal.Length, y = iris$Sepal.Width,
     xlab = "Sepal Length", ylab = "Sepal Width", 
     cex.lab = 1.3,
     xlim = c(4,9), ylim = c(1,4.5),
     cex.axis = 1.5,
     pch = 19, 
     cex = 1,
     col = iris$Species                     # 使顏色根據Species這個變數調整
     )
legend(x = 7, y = 2.7,                      # 設定legend的XY位置
       legend = unique(iris$Species),       # 挑出Species裡面的變數來作為legend
       col = 1:3,                           # 顏色是預設的1到3號,分別為黑紅綠 (後述)
       pch = 19, bty = "n")                 # 調整legend圖示,pch代表點的形狀
                                            # bty是外框,"n"代表不用外框,bty = "o"是預設的有外框

image

如果想要指定特別的顏色,我們需要在資料集中指定,這邊分享四種我常用於發表的顏色

首先我們要將顏色作為一筆資料加入iris資料集:

iris$Color <- as.character(iris$Species)
iris$Color[iris$Species=="setosa"] <- "#F8766D"
iris$Color[iris$Species=="versicolor"] <- "#7CAE00"
iris$Color[iris$Species=="virginica"] <- "#00BCF4"

接著繪圖:

plot(x = iris$Sepal.Length, y = iris$Sepal.Width,
     xlab = "Sepal Length", ylab = "Sepal Width", 
     cex.lab = 1.3,
     xlim = c(4,9), ylim = c(1,4.5),
     cex.axis = 1.5,
     pch = 19, 
     cex = 1,
     col = iris$Color
     )
legend(x = 7, y = 2.7,
       legend = c("setosa","versicolor","virginica"),
       col = c("#F8766D","#7CAE00","#00BCF4"), pch = 19, bty = "n")

image
以上就是最為基礎的plot函數使用方式,接下來會開始介紹R裡面最為有名的套件:ggplot2

R真正的力量:ggplot2

說到R語言的繪圖能力,絕對不能不提到ggplot2這個套件,這個函數可以讓你用一種不一樣的邏輯來編輯圖像,除了功能強大且更加自由外,很多人針對這個套件寫的內容都可以無縫接軌,尤其是在光譜以及數位化土壤繪圖的領域。
要使用這個套件,必須先安裝ggplot2,而且每次使用前都需要呼叫套件library(ggplot2),其名稱裡的gg是代表grammar of graphics的意思,是將圖像依據其組成元素拆解下來,讓你可以用一句話一句話編寫出來。聽起來一定很玄,我們先看一個例子:

ggplot(iris, 
       aes(x=Sepal.Length, 
           y=Sepal.Width,
           color=Species))+
  geom_point(size=5)+
  theme(axis.text=element_text(size=15))+
  xlab("Sepal Length") + ylab("Sepal Width")+
  xlim(c(4,8))+ylim(c(2,4.5))+
  scale_color_manual(values=c("#F8766D","#7CAE00","#00BCF4"))+
  guides(color=guide_legend(title="Species"))

image
這是跟上一張圖一樣的內容,用ggplot編寫出來的結果,編寫方式跟plot很不一樣。

ggplot的邏輯

可以將ggplot的編寫想像成我們在建構一張圖,這張圖需要有資料要件 (data)美學要件 (aesthetics),以及幾何要件 (geom)
首先我們給定一個資料要件:

ggplot(iris)

此時會發現圖裡什麼都沒有,然後我們還要加上美學套件aes

ggplot(iris, 
       aes(x=Sepal.Length, y=Sepal.Width, color=Species))

image
這時候圖出現了XY軸,上面這句代表著我們在指定這張圖當中,X軸是Sepal.Length這個變數,而Y軸是Sepal.Width這個變數,最後顏色則是會被Species這個變數指定,但是我們還沒有告訴R這張圖要怎麼畫,所以圖還是只有XY軸。
接下來我們透過不同的geom函數來告訴R這張圖要怎麼畫,常用的有這幾種類別:

程式之間要以+號連接,例如我們畫一個點的散布圖:

ggplot(iris, 
       aes(x=Sepal.Length, y=Sepal.Width, color=Species))+
  geom_point()

image
接下來要學的就是調整不同的參數讓這張圖更加符合我們想要的格式:

ggplot(iris, 
       aes(x=Sepal.Length, y=Sepal.Width, color=Species))+
  geom_point(pch=19,size=4.5)+                                    # 調整點的樣子跟點的大小
  guides(color=guide_legend(title="Species name"))+               # 調整legend的標題
  scale_color_manual(values=c("#F8766D", "#7CAE00", "#00BCF4"))+  # 調整顏色
  xlim(c(4,8))+ylim(c(2,4.5))+                                    # 調整XY軸的範圍
  xlab("Sepal Length")+                                           # 設定XY軸的標題
  ylab("Sepal Width")+
  theme(axis.text=element_text(size=15),                          # 調整座標軸數字的大小
        axis.title=element_text(size=15),                         # 調整座標軸標題的大小
        legend.title = element_text(size=15),                     # 調整legend標題的大小
        legend.text = element_text(size=15))                      # 調整legend子項目的大小

image
ggplot套件基本上可以辦到任何事情,並且針對需要的內容增加程式碼。

在標題輸入上標或下標

很多時候我們的座標軸需要輸入上標或下標,這可以透過experssion()函數達成,使用方法就是將原本要輸入文字的部分改成輸入這個函數,上下標的表示方法為

實際舉例說明,我們要顯示無定型鋁的下標o,以及其單位的上標-1,意即Alo (g kg-1) 這樣的內容:

  1. 輸入文字"Al"
  2. 輸入下標["o"]
  3. 因為我們用了下標,所以要接之後的文字之前要先使用*
  4. 輸入文字" (g kg"
  5. 輸入上標^-1,這裡-1是用數字方式輸入,也可以用^"-1"
  6. 因為我們用了上標,所以要接之後的文字之前要先使用*
  7. 輸入文字")"

最後的程式會長這樣expression("Al"["o"]*" (g kg"^-1*")"),把前面的圖來當作範例,修改一下座標軸:

par(mar = c(5.1, 5, 4.1, 1.5),
    las = 1)
plot(x = iris$Sepal.Length, y = iris$Sepal.Width,
     xlab = expression("Fe"["o"]*" (g kg"^-1*")"), 
     ylab = expression("Al"["o"]*" (g kg"^-1*")"), 
     cex.lab = 1.3,
     xlim = c(0,8), ylim = c(0,8),
     cex.axis = 1.5,
     pch = 19, 
     cex = 1
     )

image

圖片存檔

前面畫好這麼多漂亮的圖片,最後一步就是要將圖匯出存檔,我常使用的存檔方式有兩種。
第一種是使用tiff(),可以給定長寬以及解析度dip (發表期刊使用300 dpi),要將繪圖的程式碼夾在兩行之中,然後一起執行:

tiff("title.tiff", units="in", width=3.5, height=3.5, res=300)
# 中間包夾繪圖程式碼
dev.off()

另一種方法是使用dev.copy(),適用於已經畫在R裡面,直接使用下面兩行程式碼就可以存檔:

dev.copy(tiff,"title.tiff",units="in", width=5.5, height=3.5, res=300)  
dev.off()
# 把tiff換成png就可以改存png檔,以此類推