2019-06-25如何使用 ggplot2 ?

作者:黃寶臣 數(shù)據(jù)科學/科學哲學碩士/本科生物狗
知乎原文:
https://www.zhihu.com/question/24779017/answer/38750383

ggplot2是最偉大的繪圖工具之一,復制代碼就可以畫出專業(yè)級的圖片,但新手經(jīng)常會遇到這樣那樣的報錯。這一篇知乎文章清楚的解釋了困擾初學者的疑惑,如aes一會兒在ggplot里,一會兒又跑到geom里,加不加括號等等。

總結(jié)來說有以下幾點:

  • ggplot2的核心理念是將繪圖與數(shù)據(jù)分離,數(shù)據(jù)相關(guān)的繪圖與數(shù)據(jù)無關(guān)的繪圖分離
  • ggplot2是按圖層作圖
  • ggplot2保有命令式作圖的調(diào)整函數(shù),使其更具靈活性
  • ggplot2將常見的統(tǒng)計變換融入到了繪圖中。

1、ggplot2的邏輯。

ggplot2的邏輯在我看來其實是真正實現(xiàn)了一個圖層疊加的概念:一句語句代表一張圖,然后再有最小的單元圖層。這個與其他命令式的繪圖完全不同,來做個比較:

#這是基于graphic包里例子
x <- rnorm(100,14,5)
y <- x + rnorm(100,0,1)
plot(x,y)
text(13,20, expression(x[1] == x[2]))

輸出的圖是這樣的:

image

我們可以看到這種繪圖方式實際上是按命令添加的,以plot開始,可以以任何方式結(jié)束,每加上一個元素,實際上都是以一句單獨的命令來實現(xiàn)的。這樣做的缺點就是,其實不符合人對于畫圖的一般認識。其次,就是,我們沒有一個停止繪圖的標志,這使得有時候再處理的時候就會產(chǎn)生一些困惑。優(yōu)勢其實也有,在做參數(shù)修改的時候,我們往往可以很方便地直接用一句單獨的命令修改,譬如對于x軸的調(diào)整,覺得不滿意就可以寫命令直接調(diào)整。而ggplot2則意味著要重新作圖。再來看ggplot2的代碼:

x <- rnorm(100,14,5) 
y <- x + rnorm(100,0,1) 
ggplot(data= NULL, aes(x = x, y = y)) +  #開始繪圖
  geom_point(color = "darkred") +  #添加點
  annotate("text",x =13 , y = 20,parse = T,
           label = "x[1] == x[2]") #添加注釋

畫出的結(jié)果如下:

image

我們可以發(fā)現(xiàn),ggplot的繪圖有以下幾個特點:第一,有明確的起始(以ggplot函數(shù)開始)與終止(一句語句一幅圖);其二,圖層之間的疊加是靠“+”號實現(xiàn)的,越后面其圖層越高。

其次就是對于分組數(shù)據(jù)的處理,其實這方面,lattice已經(jīng)做得很好了,不過我會在后面更仔細地敘述ggplot2是怎么看分組數(shù)據(jù)的繪圖的。

2. ggplot2的要素

我們這里不談qplot(quickly plotting)方法,單純談ggplot方法。不談底層的實現(xiàn)思想,我們簡單地理解,ggplot圖的元素可以主要可以概括如下:最大的是plot(指整張圖,包括background和title),其次是axis(包括stick,text,title和stick)、legend(包括backgroud、text、title)、facet這是第二層次,其中facet可以分為外部strip部分(包括backgroud和text)和內(nèi)部panel部分(包括backgroud、boder和網(wǎng)格線grid,其中粗的叫g(shù)rid.major,細的叫g(shù)rid.minor)。大致見下圖,這部分內(nèi)容的熟悉程度直接影響到對于theme的掌握,因此希望大家留心。

image

3. ggplot2圖層以及其他函數(shù)的分類

好了,現(xiàn)在把這些理念的東西講完了之后,下面來理解ggplot2里的繪圖命令。

ggplot2里的所有函數(shù)可以分為以下幾類:

  • 用于運算(我們在此不講,如fortify_,mean_等)

  • 初始化、展示繪圖等命令(ggplot,plot,print等)

  • 按變量組圖(facet_等)

  • 真正的繪圖命令(stat_,geom_,annotate),這三類就是實現(xiàn)一個函數(shù)一個圖層的核心函數(shù)。

  • 微調(diào)圖型:嚴格意義上說,這一類函數(shù)不是再實現(xiàn)圖層,而是在做局部調(diào)整。

  • scale_:直譯為標尺,這就是與aes內(nèi)的各種美學(shape、color、fill、alpha)調(diào)整有關(guān)的函數(shù)。

  • guides:調(diào)整所有的text。

  • coord_:調(diào)整坐標。

  • theme:調(diào)整不與數(shù)據(jù)有關(guān)的圖的元素的函數(shù)。

4. 繪圖
第一步:初始化。ggplot2風格的繪圖的第一步就是初始化,說白了就是載入數(shù)據(jù)空間、選擇數(shù)據(jù)以及選擇默認aes。

p <- ggplot(data = , aes(x = , y = ))

data就是載入你要畫的數(shù)據(jù)所在的數(shù)據(jù)框,指定為你的繪圖環(huán)境,載入之后,就可以免去寫大量的$來提取data.frame之中的向量。當然,如果你的數(shù)據(jù)都是向量,也可不指定,但是要在申明中標注data = NULL,不然就會得到不必要的報錯。
第二個是重頭戲,即aes,是美學(aesthetic)的縮寫。這是在ggplot2初學者眼里最不能理解的東西,甚至很多老手也會在猶豫,什么時候要把參數(shù)寫在aes里,什么時候要寫在aes外。我們做一個簡單的,不非常恰當?shù)慕忉專?strong>任何與數(shù)據(jù)向量順序相關(guān),需要逐個指定的參數(shù)都必須寫在aes里。這之后我們會進一步解釋,現(xiàn)在我們初始化的時候,最好只是把關(guān)于位置的x和y指定一下就好。

第二部,繪制圖層。

很多人在解釋ggplot2的時候喜歡說,ggplot2繪圖有兩種函數(shù),一類是geom_,繪圖用的;一類是stat_,統(tǒng)計變換用的。這樣說不是不對,只是很不恰當,很多人就會問出一些問題,比如,統(tǒng)計變換竟然是做運算用的,為什么可以用來畫圖?為什么stat_bin和geom_histgram畫出來的圖是一樣,竟然一樣,為什么要重復?
事實上,**任何一個ggplot2圖層都包括stat和geom倆部分,或者說兩個步驟(其實還包括position)。 **而stat_identity則表示不做任何的統(tǒng)計變換。

我們來舉個例子,還是上面的代碼,為了更直觀,我在此作了修改:

x <- c(rnorm(100,14,5),rep(20,20)) 
y <- c(rnorm(100,14,5) + rnorm(100,0,1),rep(20,20))
ggplot(data= NULL, aes(x = x, y = y)) +  #開始繪圖
  geom_point(color = "darkred")

做出的圖如下:

image

我們查看碼源,就知道geom_point的默認stat是identity,即不做任何統(tǒng)計變換:

> geom_point
function (mapping = NULL, data = NULL, stat = "identity", position = "identity", 
    na.rm = FALSE, ...) 
{
    GeomPoint$new(mapping = mapping, data = data, stat = stat, 
        position = position, na.rm = na.rm, ...)
}
<environment: namespace:ggplot2>

大家可以發(fā)現(xiàn),我在(20,20)這個點的數(shù)據(jù)事實上是有20個的,但由于沒做統(tǒng)計轉(zhuǎn)換(20,20)這個點被畫了20次,因此我們理論上看到的點其實是最后一次畫的那個點。可能這不夠直觀,沒關(guān)系,我們調(diào)整一下透明度到10%:

ggplot(data= NULL, aes(x = x, y = y)) +  #開始繪圖
  geom_point(color = "darkred",alpha = 0.1)

得到如下圖:

image

這樣應(yīng)該就很明顯了,由于(20,20)點被畫了20次,所以透明度會疊加為20*10% = 200%實際只展現(xiàn)100%。

我們現(xiàn)在就使用坐標轉(zhuǎn)換來重新畫這個圖:

ggplot(data= NULL, aes(x = x, y = y)) + #開始繪圖
geom_point(color = "darkred",stat = "sum")

好了,解釋一下,stat_sum實際的意思就是按照某一點占所有點出現(xiàn)頻率然后換算成大小來作圖,因此,以上代碼就可以得到下面這張圖,因為(20,20)這個點出現(xiàn)頻率為20/120=16.667%:

image

好了,我們可以發(fā)現(xiàn)了,一個單純的geom_point里面也是帶有stat_的,因此,其實geom_和stat_實際上是一回事。可能你會問了,那照我的說法,以上這幅圖用的是geom_point里的一個參數(shù),而不是再用stat_sum,這是一回事嗎?bingo!這個問題相當好,的確,按照以上的推理,應(yīng)該存在一種以stat_sum作為主函數(shù)的方法來繪制這幅圖,搞不好,里面還有個參數(shù)geom,要設(shè)置成“point”。我們來實踐一下吧:

ggplot(data= NULL, aes(x = x, y = y)) +  #開始繪圖
  stat_sum(color = "darkred",geom = "point")
image

尼瑪,還真可以,還長得一模一樣。

現(xiàn)在就講通了,對于有過經(jīng)驗的同學現(xiàn)在應(yīng)該重新修正這個觀點——stat_和geom_是兩種繪圖方法。這是錯的,其實它們是ggplot2每一個圖層繪制都必須有的,是一個圖層的一體兩面。

在這一步之中,我們也要回到我們在第一步時出現(xiàn)的問題,aes到底是什么?為什么說任何與數(shù)據(jù)向量順序相關(guān),需要逐個指定的參數(shù)都必須寫在aes里?什么時候color、shape、size、fill寫外面,什么時候?qū)懤锩妫?/p>

aes實際上做的是將aes里的向量的順序逐個地繪制。譬如以下代碼(轉(zhuǎn)自geom_point幫助文檔中的實例):

p <- ggplot(mtcars, aes(wt, mpg)) #<---- code 1
p + geom_point(aes(colour = qsec)) #<---- code 2

結(jié)果是:

image

我們來分析一下ggplot2是怎么作圖的。首先,我們來看一下mtcars這個數(shù)據(jù)集長什么樣:

> head(mtcars)
                   mpg cyl disp  hp drat    wt  qsec vs am gear carb
Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

code 1: ggplot首先載入了這個mtcars的集合,然后指定給了mpg作為其x坐標位置,wt為y坐標位置。
code 2: 指定了qsec作為其染色的標準(分組),qsec為numeric變量,因此,應(yīng)該選擇連續(xù)型的標尺,而不是分組染色。然后開始繪制,讀取mtcarsmpg[1]、mtcarswt[1],確定位置,然后為其染成mtcars$qsec[1]顏色;再繪制第二點。。。

因此,aes里的美學特征其實就是按照向量順序指定每個位置的美學特征,大家可以比較tapply函數(shù)的寫法。

好了,現(xiàn)在問題就來了。我想為所有點的顏色都染成綠色,怎么辦?其實很簡單,如果不需要指定這么一個染色的順序,而選擇將整個圖層染成一種顏色,則只需要將color寫在aes外:

p + geom_point(color = "green") 

哦,怪不得寫在aes里染出來的顏色不是綠色,但為什么寫到里面就不可以了,為了寫到里面,然出來的是粉色?

image

好了,我們再來分析一下把color = "green"寫到了aes里,到底發(fā)生了什么。

p + geom_point(aes(colour = "green"))

首先,數(shù)據(jù)的初始化跟上面那個例子是相同的。然后,因為color放到了aes里,于是ggplot開始搜索mtcars里面的向量了,發(fā)現(xiàn)沒有叫"green"的,然后又找了global,也沒有。于是,ggplot就開始把它認作了一個新的向量。等等,有個問題,我要按照這個向量來分別染色,而事實上,這個向量長度為1,怎么辦?ggplot就先把他展開成了factor(rep("green",nrow(mtcars)),levels = unique("green")),bingo!現(xiàn)在開始染色了。啊第一個數(shù)據(jù)mtcarsmpg[1]、mtcarswt[1],其顏色變量是"green",因子水平是1,染成默認調(diào)色第一種,哦,就是這個蛋蛋的粉紅色;再染第二個,還是"green",因子水平也是1,染成蛋蛋的粉紅色;... 終于完成了,咦?怎么都是蛋蛋的粉紅色。

通過舉了這個染色的例子大家應(yīng)該都弄懂了,aes到底在干什么了。其他的美學特征其實也是完全一致的。只是需要解釋group=1的意思就是說不做分組來進行繪圖。什么?還是搞不清該放aes里面還是外面?那就記著想統(tǒng)一整個圖層時就放到aes外,想分成不同組調(diào)整,并且已經(jīng)有一個與x、y長度一致的分組變量了,那就放到aes里。

在這一步里,還要要說的就是我們要講的是ggplot2大致內(nèi)置了哪些圖:

  • 點(point, text):往往只有x、y指定位置,有shape但沒有fill
  • 線(line,vline,abline,hline,stat_function等):一般是基于函數(shù)來處理位置
  • 射(segment):特征是指定位置有xend和yend,表示射線方向
  • 面(tile, rect):這類一般有xmax,xmin,ymax,ymin指定位置
  • 棒(boxplot,bin,bar,histogram):往往是二維或一維變量,具有width屬性
  • 帶(ribbon,smooth):透明是特征是透明的fill
  • 補:包括rug圖,誤差棒(errorbar,errorbarh)

然后,就是按照你的需要一步步加圖層了(使用“+”)。

第三部,加注釋。所有注釋的實現(xiàn)都是通過annotate函數(shù)實現(xiàn)的,其實annotate就是一個最簡單的geom_單元,它一次只添加一個位置上的圖形(可以通過設(shè)置向量來實現(xiàn)同時繪制多個圖形,但這個理念和注釋的理念有所偏差)。annotate的geom就是指定注釋的類型,其屬性按照geom的不同而發(fā)生變化。

第四步,調(diào)整。這里的調(diào)整主要是使用微調(diào)圖形這大類的函數(shù)做美學特征、坐標軸、標題、繪圖主題的調(diào)整。這部分也就是繼承了命令式作圖的思想,使ggplot2的靈活性增加。

如何搜索你要用什么美學特征調(diào)整函數(shù),其實就是按照美學特征的名字來,例如,你要調(diào)整的是fill,就找scale_fill_之后就有一些不同的染色方法(關(guān)于色彩,如果有時間還會添加相關(guān)知識);調(diào)整的是橫坐標標尺,就找scale_x_然后后面跟上你的橫坐標類型;其他雷同。

在調(diào)整主題這方面,值得褒獎的是,theme函數(shù)其實最妙的地方是將對于數(shù)據(jù)相關(guān)的美學調(diào)整和與數(shù)據(jù)無關(guān)的美學調(diào)整分離了。譬如說,我們要改變x軸的顏色,或者panel的底色,這個其實與數(shù)據(jù)處理無關(guān),這種分離就會使得我們可以如此流程化地操作作圖,而不需要在考慮數(shù)據(jù)的時候還要關(guān)注到與數(shù)據(jù)無關(guān)的美學參數(shù)。有人有時候會覺得ggplot2很奇怪的地方就是為什么調(diào)整legend的時候,有時要用scale_,有時又要用theme,其實這都是對于ggplot2這個設(shè)計理念的不理解,作者的設(shè)計思路是要將數(shù)據(jù)處理與數(shù)據(jù)美學分開,數(shù)據(jù)美學與數(shù)據(jù)無關(guān)的調(diào)整分開。
其次,theme函數(shù)采用了四個簡單地函數(shù)來調(diào)整所有的主題特征:element_text調(diào)整字體,element_line調(diào)整主題內(nèi)的所有線,element_rect調(diào)整所有的塊,element_blank清空。這種設(shè)計相當?shù)匕簟?/p>

由此,一個極具誠意的作圖應(yīng)該長成下面這個樣子:

ggplot(data = , aes(x = , y = )) + 
    geom_XXX(...) + ... + stat_XXX(...) + ... +
    annotate(...) + ... +
    scale_XXX(...) + coord_XXX(...) + guides(...) + theme(...)

5. ggplot2的一些缺點

  • 公式支持不好,自帶的plotmath公式無法滿足很多需求
  • 無法針對多個legends進行調(diào)整
  • 效率不高,繪圖速度較慢,這也表示二次開發(fā)的可能性不高
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容