R|ggplot2(四)|stat_ geom_ 和position
引用一句Hadley在ggplot2網站上的一句話
A layer combines data, aesthetic mapping, a geom (geometric object), a stat (statistical transformation), and a position adjustment. Typically, you will create layers using a geom_ function, overriding the default position and stat if needed.
一個圖層需要指定數據集、使用數據集中的哪些內容(aes部分),做什麼樣的圖形(geom_ 指定是製作點圖還是柱狀圖)、數據的統計轉換(stat部分)、圖形位置調整(position)。
本節主要針對後三者進行介紹,我們分為以下幾個內容
- geom_和stat_之間的關係
- 一些需要注意的點
- stat的定義及使用
- position的使用
1.geom_和stat_之間的關係
- 相互替代的關係,比如geom_bar和stat_count是可以相互替代的
- 默認和改變。比如geom_bar默認stat是"count",但是可以轉化為"identity",從而使用其他類型的數據
library(ggplot2)nggplot(mpg,aes(x=class)) + geom_bar() # 使用一個變數做柱狀圖nggplot(mpg,aes(x=class)) + stat_count() # 和上面一樣nnggplot(mpg,aes(x=class,y=displ)) + geom_bar(stat="identity") # 使用兩個變數作柱狀圖nggplot(mpg,aes(x=class,y=displ)) + geom_col() # 與上面相同nggplot(mpg,aes(x=class,y=displ)) + stat_identity() # 散點圖nggplot(mpg,aes(x=class,y=displ)) + geom_point() # 等價於上一條n
上面的代碼顯示出了如下內容
- geom_bar和stat_count的相互替代,即geom_bar默認使用stat="count",stat_count默認使用geom="bar",即這種統計變換默認畫出的是柱狀圖
- 在geom_bar中更改默認的"count"為"identity"就可以接受兩個變數作圖
- geom_col也是畫柱狀圖,但是默認stat="identity"
- geom_point和stat_identity 互相默認
- 所以ggplot2包中geom與stat經常成對出現,如果不特意指定更改就可以相互替代
我們可以查看函數的幫助文檔來獲知默認參數,比如
?geom_boxplot # 輸入這條命令查看函數定義,截取如下內容ngeom_boxplot(mapping = NULL, data = NULL, stat = "boxplot",nposition = "dodge", ..., na.rm = FALSE, show.legend = NA,ninherit.aes = TRUE)nnstat_boxplot(mapping = NULL, data = NULL, geom = "boxplot",nposition = "dodge", ..., coef = 1.5, na.rm = FALSE, show.legend = NA,ninherit.aes = TRUE)n
我們可以看到geom_boxplot裡面參數stat默認為"boxplot",stat_boxplot也有一個參數geom默認是"boxplot"。
2.一些需要注意的點
我們先來看一看如下代碼
ggplot(mpg,aes(x=class,y=displ)) + geom_bar(stat="identity")nggplot(mpg,aes(x=class,y=displ)) + stat_identity(geom="bar") # 調換順序圖形不一樣了nggplot(mpg,aes(x=class,y=displ)) + stat_identity(geom="col") # 和上面一樣nggplot(mpg,aes(x=class,y=displ)) + stat_identity() # 看看散點圖的樣子nggplot(mtcars, aes(wt, mpg)) + stat_identity(geom="bar") # 換一個數據集試一試nggplot(mtcars, aes(wt, mpg)) + stat_identity() # 和它對應的散點圖做對比nn# ggplot(mpg,aes(x=class)) + geom_col(stat="count") # 報錯n
從上面結果我們可以看出如下信息
(1)看到geom和stat的相互替換現象,一個很自然的想法是,geom_bar修改stat為"identity"作圖結果,和stat_identity修改geom為"bar"應該是一樣的。但是實際上卻不一樣
後者作圖結果是什麼呢?我們可以從散點圖中得到啟發。比如第一根柱子最高是7,我們可以看到散點圖中2seater對應的點縱坐標最大也是7.所以我們猜想這樣做的結果是將點換成一個有相同高度的柱子來表示,而因為這個數據的橫坐標是離散的,很多柱子重疊在一起,無法分辨,所以我們考慮換一個橫坐標是連續的數據再試一試。mtcars數據集作圖結果正好證實了我們的猜想
出現這個問題的原因後文會進行說明
(2)接下來看,最後一行報錯的代碼。我們原來認為geom_col默認stat是"identity",所以想如果將其換成geom_bar對應的"count"是不是就可以只接受一個變數作柱狀圖,結果竟然報錯。
查找原因發現,其實geom_col並沒有stat這個參數。直接在控制台輸入geom_col查看源碼,可以看出在調用layer函數的時候,stat參數直接指定的是"identity",如果查看geom_bar函數源代碼的話,可以看到,調用layer函數的時候stat參數接的是stat即我們指定的參數對應的內容。所以geom_col是無法更改stat的
3.stat與geom的定義及使用
上面我們看到一些和我們預期不相符的結果,我們要想弄懂它們,就要理解stat和geom內部的運行機制,我們可以看ggplot2包中的User guides, package vignettes and other documentation.裡面的文章extending-ggplot2,這也可以在ggplot2的網站上找到。這盤文章介紹了如何自己創建一個新的geom和新的stat,創建新的函數需要我們遇到具體問題時才要去做的事情,在這裡我只想通過理解它的創建過程,來理解原有函數之間的關係。
(1)首先看stat的創建過程
一個完整的創建樣例代碼如下
StatChull <- ggproto("StatChull", Stat,ncompute_group = function(data, scales) {n data[chull(data$x, data$y), , drop = FALSE]n},nrequired_aes = c("x", "y")n)nnstat_chull <- function(mapping = NULL, data = NULL, geom = "polygon",n position = "identity", na.rm = FALSE, show.legend = NA, n inherit.aes = TRUE, ...) {nlayer(n stat = StatChull, data = data, mapping = mapping, geom = geom, n position = position, show.legend = show.legend, inherit.aes = inherit.aes,n params = list(na.rm = na.rm, ...)n)n}nn# 使用新的函數nggplot(mpg, aes(displ, hwy)) + ngeom_point() + nstat_chull(fill = NA, colour = "black")n
解釋如下
- 使用ggproto定義了一個StatChull對象,compute_group指定這個函數對數據進行了什麼樣的操作,required_aes表示使用這樣一個stat時aes中需要使用幾個變數作為參數。
- 下面定義了一個stat_chull函數,它的形式和參數都很像我們之前遇到的stat_count等函數,所以這就是我們想要創建的最終函數。
- 這個函數默認geom = "polygon"。函數調用了layer函數,這和我們之前查看的stat_boxplot源代碼格式相同。stat參數使用了上面創建的StatChull。
- 所以調用stat_chull函數的內在邏輯是:畫geom_polygon這個類型的圖,使用的數據這樣得到:輸入data中的x和y值,進行StatChull中的compute_group函數中的變換,使用變換之後的數據。在這裡,變換就是data[]取子集,拿取出來的點做多邊形
(2)我們看看geom的創建過程
geom創建代碼較長,就不在這裡貼出,讀者可以自行到網站上查看。過程和stat的創建大同小異。
- 先創建GeomSimplePoint對象,再創建geom_simple_point函數(最終想要的),其中調用layer函數,默認指定geom=GeomSimplePoint
- 其中GeomSimplePoint對象中也是定義了接受的aes,以及用於繪圖的函數。我們可以從中看出ggplot2中繪圖函數調用的是grid包中的相應函數
(3)查看已有geom和stat細節的方法
比如stat_count,我們進行如下幾個步驟
- 我們直接輸入命令stat_count,找到默認參數stat = StatCount,知道了存儲著統計變換函數的對象名字叫做StatCount
- 輸入StatCount列出了可以查看的元素,我們要看的是compute_group這個函數,所以輸入StatCount$compute_group
- 找到<Inner function (f)> 部分,最後生成的data.frame就是這個stat變換之後會使用的數據
- 對於stat_identity來說,找到StatIdentity,結果<Inner function (f)>內容沒有什麼變換,意味著這是使用原始數據本身
同理,查看geom_point也是先找到GeomPoint,再看GeomPoint$draw_key 查看作圖函數
(4)分析之前遺留下來的問題
geom_bar(stat="identity")和stat_identity(geom="bar")結果不一樣的問題。
通過上面的學習,我們知道了stat和geom函數其實就是相互調用的關係,都是調用的layer函數,指定geom和stat,以上兩者的這兩個參數都應該是一樣的,所以問題出現在其他參數的選擇上。
在控制台上分別輸入stat_identity和geom_bar,對比默認的參數,發現不一樣在於position參數,前者是"identity",而後者是"stack",所以我們更改默認參數就可以使作圖結果相同。
ggplot(mpg,aes(x=class,y=displ)) + geom_bar(stat="identity")nggplot(mpg,aes(x=class,y=displ)) + stat_identity(geom="bar",position="stack") n
即geom_bar默認使用stack堆疊的方式,將所有柱子堆積成一根柱子,而stat_identity默認原地放置,全部重疊在一起,只能顯示出最長的那根柱子
下面我們系統地介紹position
4.position的使用
在rstudio的控制台中輸入position_就會提示出所有類型的position,我們以position_dodge為例進行解釋
position_dodge對應PositionDodge,可以在這裡查看內部計算函數
下面我們說一說position參數設置,主要是width參數
這裡的width要和geom_bar的參數width進行區分
df <- data.frame(x = 1,n y = 1,n grp = c("A", "B"))nnp <- ggplot(data = df, aes(x = x, y = y, fill = grp)) + theme_minimal()np + geom_bar(stat = "identity", position = "dodge")n# 這裡width是兩個柱子單獨算寬度再相加np + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)np + geom_bar(stat = "identity", width = 1.5, position = position_dodge(), alpha = 0.8)n
# 多一組數來看ndf1 <- data.frame(x = c(1,1,2,2),n y = rep(1,4),n grp = c("A", "B"))npp <- ggplot(data = df1, aes(x = x, y = y, fill = grp)) + theme_minimal()n# width為1正好兩組挨在一起,再增大就會出現重疊npp + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)npp + geom_bar(stat = "identity", width = 1.5, position = position_dodge(), alpha = 0.8)n
#接下來還是使用df來看position中的widthn# 這個width配合之前的width調整一組內兩根柱子的重疊與分離n# 這裡width是兩根柱子中心距離的兩倍n# 所以當這個width大於bar中的width,柱子就會分離,小於就會有重疊np + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 1.5), alpha = 0.8)np + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 0.5), alpha = 0.8)n# 給每組的空間還是1的長度,當position中的width比較大,拉開內部兩根柱子的距離,就會和另外一組的柱子發生重疊npp + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 1.5), alpha = 0.8)npp + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 0.5), alpha = 0.8)n
# 在柱子上增加文字n# 查看幫助可知geom_text對應stat是identity,所以當不用position調整時,加的標籤全堆在點(1,1)處np <- ggplot(data = df, aes(x = x, y = y, fill = grp, label = grp)) + theme_minimal()nnp + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)+ngeom_text(size = 10)n# geom_text中指定position內width參數,調整文字間距離np + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)+ngeom_text(position = position_dodge(width = 0.3), size = 10) n# 兩個position_dodge中的width應該相同,文字才會在柱子中心np + geom_bar(stat = "identity", width = 2, position = position_dodge(), alpha = 0.8)+ngeom_text(position = position_dodge(width = 2), size = 10) # bar中position的width自動等於前面的widthnp + geom_bar(stat = "identity", width = 1, position = position_dodge(width_=1.2), alpha = 0.8)+ngeom_text(position = position_dodge(width = 1), size = 10) # bar中position指定另外一個widthnp + geom_bar(stat = "identity", width = 1, position = position_dodge(width_=1.2), alpha = 0.8)+ngeom_text(position = position_dodge(width = 1.2), size = 10) # 兩個position中width保持相同又回到中心n
專欄信息
專欄主頁:Data Analysis
專欄目錄:目錄推薦閱讀: