前言
多次有人問我,如何使用 R 來繪制多個 Y 軸。那我就研究了下,稍微介紹一下學(xué)習(xí)所得。
當(dāng)我們需要展示的數(shù)據(jù),包含相同的 X 軸,但是具有兩個不同的 Y 軸變量時,我們會選擇在右側(cè)添加一條軸,在 ggplot2 中,可以使用 sec_axis 來設(shè)置。
那如果 Y 軸變量有多個呢?我們該如何繪制?
一種簡單的思路就是,在圖像繪圖區(qū)域的左右兩側(cè)添加兩條軸,多余的軸可以添加到右側(cè),圖像還是填充到繪制區(qū)域。
那如何實(shí)現(xiàn)這一功能呢?下面我們將介紹今天的主角 —— gtable,搭配 grid 包,可以輕松的為 ggplot 添加上多個 Y 軸
gtable
1. 介紹
gtable 是基于 grid 包的布局引擎,可以用來抽象化地創(chuàng)建網(wǎng)格視圖,每個網(wǎng)格內(nèi)都可以放置不同的圖形對象
gtable 可以很容易地將圖形元素的對齊,組合成復(fù)雜的圖形。同時,完美兼容 ggplot2 圖形
首先,當(dāng)然是安裝和導(dǎo)入了,不必多說
# 從 CRAN 上安裝
install.packages("gtable")
# 從 GitHub 上安裝
# install.packages("remotes")
remotes::install_github("r-lib/gtable")
導(dǎo)入
library(gtable)
library(ggplot2)
library(grid)
2. 創(chuàng)建布局
創(chuàng)建布局的函數(shù)主要有如下幾個:
-
gtable: 創(chuàng)建grob(grid object) 的table布局 -
gtable_matrix: 矩陣布局 -
gtable_col: 單列 -
gtable_row: 單行 -
gtable_row_spacer/gtable_col_spacer:
2.1 gtable
table 布局可以將 grob 以表的排列形式放置。它支持跨行、跨列,還提供了一些工具來自動計(jì)算出正確的尺寸
gtable(widths = list(), heights = list(), respect = FALSE,
name = "layout", rownames = NULL, colnames = NULL, vp = NULL)
其中前兩個參數(shù)指定了表格布局,例如
> a <- gtable(unit(1:3, c("cm")), unit(5, "cm"))
> a
TableGrob (1 x 3) "layout": 0 grobs
> class(a)
[1] "gtable" "gTree" "grob" "gDesc"
創(chuàng)建了一個寬度分別為 1cm、2cm、3cm,長度為 5cm 的一行三列的表格布局,,返回了一個 gtable 對象
使用 gtable_show_layout 函數(shù)可以繪制布局結(jié)構(gòu)
gtable_show_layout(a)

每個繪圖對象(grob)都放置在自己的視圖中,并占滿整個視圖,因此對齊方式在這里不會發(fā)揮作用
布局中包含三個基礎(chǔ)組件,表格的規(guī)格(單元格高度和寬度)、布局(對每個 grob 來說就是它的位置,名稱及其他屬性)和全局參數(shù)
每個單元格內(nèi)可以有 0、1 或多個 grob。每個 grob 必須至少屬于一個單元格,但可以跨越多個單元格。
布局的細(xì)節(jié)被存儲在一個數(shù)據(jù)框中,每行表示一個 grob,列包括:
-
t:grob的頂部位置 -
r:grob的右邊位置 -
b:grob的底部位置 -
l:grob的左邊位置 -
z:grob的次序 -
clip:grob的裁剪方式,可以是:"on"、"off"或"inherit" - name: 每個
grob和視圖的名稱
不能直接修改這個數(shù)據(jù)框,需要使用 gtable_add_grob 等操作函數(shù)來修改
2.2 gtable_matrix
gtable_matrix 可以創(chuàng)建矩陣布局,允許為每個單元格設(shè)置不同的的高度和寬度
gtable_matrix(name, grobs, widths = NULL, heights = NULL, z = NULL,
respect = FALSE, clip = "on", vp = NULL)
首先,創(chuàng)建圖形對象
a <- rectGrob(gp = gpar(fill = "red"))
b <- circleGrob()
c <- linesGrob()
row <- matrix(list(a, b, c), nrow = 1)
col <- matrix(list(a, b, c), ncol = 1)
mat <- matrix(list(a, b, c, nullGrob()), nrow = 2)
設(shè)置布局,一行三列
> gtable_matrix("demo", row, unit(c(1, 2, 3), "null"), unit(1, "null"))
TableGrob (1 x 3) "demo": 3 grobs
z cells name grob
1 1 (1-1,1-1) demo rect[GRID.rect.143]
2 2 (1-1,2-2) demo circle[GRID.circle.144]
3 3 (1-1,3-3) demo lines[GRID.lines.145]

三行一列
> gtable_matrix("demo", col, unit(1, "null"), unit(c(1, 2, 3), "null"))
TableGrob (3 x 1) "demo": 3 grobs
z cells name grob
1 1 (1-1,1-1) demo rect[GRID.rect.143]
2 2 (2-2,1-1) demo circle[GRID.circle.144]
3 3 (3-3,1-1) demo lines[GRID.lines.145]

兩行兩列
> gtable_matrix("demo", mat, unit(c(1, 1), "null"), unit(c(1, 1), "null"))
TableGrob (2 x 2) "demo": 4 grobs
z cells name grob
1 1 (1-1,1-1) demo rect[GRID.rect.143]
2 2 (2-2,1-1) demo circle[GRID.circle.144]
3 3 (1-1,2-2) demo lines[GRID.lines.145]
4 4 (2-2,2-2) demo null[GRID.null.146]

設(shè)置繪制順序
> z <- matrix(c(3, 1, 2, 4), nrow = 2)
> gtable_matrix("demo", mat, unit(c(1, 1), "null"), unit(c(1, 1), "null"), z = z)
TableGrob (2 x 2) "demo": 4 grobs
z cells name grob
1 3 (1-1,1-1) demo rect[GRID.rect.143]
2 1 (2-2,1-1) demo circle[GRID.circle.144]
3 2 (1-1,2-2) demo lines[GRID.lines.145]
4 4 (2-2,2-2) demo null[GRID.null.146]
2.3 gtable_col 和 gtable_row
相較于前兩個布局,gtable_col 和 gtable_row 就比較簡單,只是把所有 grob 繪制在一列或一行
gtable_col(name, grobs, width = NULL, heights = NULL, z = NULL,
vp = NULL)
gtable_row(name, grobs, height = NULL, widths = NULL, z = NULL,
vp = NULL)
也是通過 width 和 height 控制寬度和高度,例如,繪制成一列
> gt <- gtable_col("demo", list(a, b, c))
> gt
TableGrob (3 x 1) "demo": 3 grobs
z cells name grob
1 1 (1-1,1-1) demo rect[GRID.rect.143]
2 2 (2-2,1-1) demo circle[GRID.circle.144]
3 3 (3-3,1-1) demo lines[GRID.lines.145]
> plot(gt)

gtable_show_layout(gt)

繪制成一行
> gt <- gtable_row("demo", list(a, b, c))
> plot(gt)

gtable_show_layout(gt)

3. 布局操作
-
gtable_add_grob: 添加一個grob,可以跨越多行或多列 -
gtable_add_cols: 在指定位置添加新列 -
gtable_add_rows: 在指定位置添加新行 -
gtable_add_padding: 在table邊界添加填充 -
gtable_add_col_space和gtable_add_row_space: 添加行/列間距 -
gtable_trim: 刪除空單元格 -
gtable_filter: 通過名稱來篩選單元格
3.1 gtable_add_grob
gtable_add_grob 可以把一個 grob 添加到 table 布局中,但是不會影響 table 布局
在 gtable 中,添加的對象會全部覆蓋整個單元格,所以,如果你要設(shè)置對齊方式,則需要為 grob 設(shè)置絕對大小
使用方式
gtable_add_grob(x, grobs, t, l, b = t, r = l, z = Inf, clip = "on",
name = x$name)
來看下面這個例子,新建一個一行三列的 table 布局,每列的寬度分別為 1、2、3
a <- gtable(unit(1:3, c("cm")), unit(5, "cm"))
gtable_show_layout(a)

為第一列添加一個矩形
> rect <- rectGrob(gp = gpar(fill = "black"))
> a <- gtable_add_grob(a, rect, 1, 1)
> a
TableGrob (1 x 3) "layout": 1 grobs
z cells name grob
1 1 (1-1,1-1) layout rect[GRID.rect.236]
> plot(a)

可以使用 t 對布局進(jìn)行轉(zhuǎn)置
> dim(a)
[1] 1 3
> t(a)
TableGrob (3 x 1) "layout": 1 grobs
z cells name grob
1 1 (1-1,1-1) layout rect[GRID.rect.236]
> dim(t(a))
[1] 3 1
> plot(t(a))

布局索引
> b <- gtable(unit(c(2, 2, 2), "cm"), unit(c(2, 2, 2), "cm"))
> b <- gtable_add_grob(b, rect, 2, 2)
> b[1, ]
TableGrob (1 x 3) "layout": 0 grobs
> b[, 1]
TableGrob (3 x 1) "layout": 0 grobs
> b[2, 2]
TableGrob (1 x 1) "layout": 1 grobs
z cells name grob
1 1 (1-1,1-1) layout rect[GRID.rect.236]
> plot(b)

訪問 gtable 對象的名稱
> rownames(b) <- 1:3
> rownames(b)[2] <- 200
> colnames(b) <- letters[1:3]
> dimnames(b)
[[1]]
[1] 1 200 3
[[2]]
[1] "a" "b" "c"
3.2 gtable_add_cols
gtable_add_cols 函數(shù)可以為 gtable 對象插入新的列,并會調(diào)整相應(yīng)的 grob 的位置。
如果在一個橫跨多列的 grob 中間添加列,grob 將繼續(xù)橫跨所有列。如果列被添加到 grob 的左邊或右邊,grob 將不會跨越新的列。
gtable_add_cols(x, widths, pos = -1)
創(chuàng)建一個 3 行 3 列的 table 布局,并添加三個矩形對象
rect <- rectGrob(gp = gpar(fill = "#00000080"))
tab <- gtable(unit(rep(1, 3), "null"), unit(rep(1, 3), "null"))
# 矩形占據(jù)第一行
tab <- gtable_add_grob(tab, rect, t = 1, l = 1, r = 3)
# 矩形占據(jù)第一列
tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 1)
# 矩形占據(jù)第三列
tab <- gtable_add_grob(tab, rect, t = 1, b = 3, l = 3)
plot(tab)

在中間添加一列
> tab2 <- gtable_add_cols(tab, unit(1, "null"), 1)
> dim(tab2)
[1] 3 4
> plot(tab2)

指定 pos 為 0 在左邊添加列,-1(默認(rèn))在右邊添加列
tab3 <- gtable_add_cols(tab, unit(1, "null"))
tab3 <- gtable_add_cols(tab3, unit(1, "null"), 0)
> dim(tab3)
[1] 3 5
> plot(tab3)

gtable_add_rows 的左右是添加列,這里就不再贅述了
3.3 gtable_add_padding
gtable_add_padding 可以為 gtable 四周添加 padding,使用方式
gtable_add_padding(x, padding)
padding 為長度為 4 的向量,分別表示上、右、下、左的 padding,如果長度不足 4,則會循環(huán)使用
例如,對于這樣一個只包含一個以矩形對象填充的單元格的布局
gt <- gtable(unit(1, "null"), unit(1, "null"))
gt <- gtable_add_grob(gt, rectGrob(gp = gpar(fill = "black")), 1, 1)
plot(gt)

可以使用 rbind 或 cbind 來連接兩個 gtable 對象,繪制的圖形根本看不出有兩個圖形
plot(cbind(gt, gt))

為 gtable 添加 padding,四周的 padding 都是 1cm
pad <- gtable_add_padding(gt, unit(1, "cm"))
plot(pad)

plot(rbind(pad, pad))

plot(cbind(pad, pad))

3.4 gtable_add_col/row_space
為網(wǎng)格的單元格之間添加行距和列距
gtable_add_col_space(x, width)
gtable_add_row_space(x, height)
創(chuàng)建矩陣布局,每個單元格放置一個矩形對象
rect <- rectGrob()
rect_mat <- matrix(rep(list(rect), 9), nrow = 3)
gt <- gtable_matrix(
"rects", rect_mat, widths = unit(rep(1, 3), "null"),
heights = unit(rep(1, 3), "null")
)
plot(gt)
添加行列間距
# 行距 0.5cm
gt <- gtable_add_row_space(gt, unit(0.5, "cm"))
# 列距分別為 0.5cm、1cm
gt <- gtable_add_col_space(gt, unit(c(0.5, 1), "cm"))
plot(gt)
3.5 gtable_trim
gtable_trim 函數(shù)用于刪除不包含 grob 的行或列,如果刪除的行和列的高度或?qū)挾葹?0,則有可能會改變整個布局
rect <- rectGrob(gp = gpar(fill = "black"))
base <- gtable(unit(c(2, 2, 2), "cm"), unit(c(2, 2, 2), "cm"))
center <- gtable_add_grob(base, rect, 2, 2)
plot(center)

gtable_show_layout(center)

刪除空行、空列
plot(gtable_trim(center))

最后只剩下一個單元格
gtable_show_layout(gtable_trim(center))

3.6 gtable_filter
通常來說,gtable 對象在索引時可以將其當(dāng)做一個矩陣,gtable_filter 可以根據(jù)名稱對 grob 進(jìn)行篩選
gtable_filter(x, pattern, fixed = FALSE, trim = TRUE, invert = FALSE)
-
pattern: 可以是正則表達(dá)式,匹配grob名稱 -
fixed: 如果為TRUE,關(guān)閉正則匹配 -
trim: 如果為TRUE,調(diào)用gtable_trim -
invert: 是否逆向匹配
創(chuàng)建 1 行 3 列的布局,并在第一列繪制矩形,第三列繪制圓形
gt <- gtable(unit(rep(5, 3), c("cm")), unit(5, "cm"))
rect <- rectGrob(gp = gpar(fill = "black"))
circ <- circleGrob(gp = gpar(fill = "red"))
gt <- gtable_add_grob(gt, rect, 1, 1, name = "rect")
gt <- gtable_add_grob(gt, circ, 1, 3, name = "circ")
plot(gt)

只顯示矩形
plot(gtable_filter(gt, "rect"))

只顯示圓形
plot(gtable_filter(gt, "circ"))

不刪除空列
plot(gtable_filter(gt, "circ", trim = FALSE))

介紹完了 gtable 之后,下一節(jié)將介紹如何繪制多個 Y 軸